- {
- 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 = [];
+{
+ 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_color => 1,
+ font_color_even => 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,
+ num_header_rows => 1,
+ );
+ foreach my $key (keys %arg) {
+ croak "Error: Invalid setting key '$key' received."
+ unless (exists $valid_settings_key{$key});
+ }
+
+ # 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 ){ 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 $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->{'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 $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, $space_w );
+
+ my $word_widths = {};
+ my $rows_height = [];
+
+ 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);
+
+ if( !$row_idx and ref $header_props )
+ {
+ $cell_font = $header_props->{'font'};
+ $cell_font_size = $header_props->{'font_size'};
+ }
+
+ # 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;
+
+ # 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, $columns_number);
+
+ 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'})
+ {
+ 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};
+ }
+ }
+
+ # Check for safety reasons
+ if( $bot_marg < 0 )
+ { # This warning should remain i think
+# carp "!!! 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);
+
+ $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 columns number to know later how many vertical lines to draw
+ # TODO: get the max number of columns per page as currently last row's columns overrides
+ $columns_number = scalar(@$record);
+
+ # 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, $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'};
+ $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;
+
+ $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);
+
+ 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];
+ }