Projekt: überflüssigen Code entfernt
[kivitendo-erp.git] / modules / override / PDF / Table.pm
old mode 100644 (file)
new mode 100755 (executable)
index d89274e..602c887
@@ -8,6 +8,8 @@ use warnings;
 package PDF::Table;
 
 use Carp;
+use List::Util qw(sum);
+
 our $VERSION = '0.10.1';
 
 print __PACKAGE__.' is version: '.$VERSION.$/ if($ENV{'PDF_TABLE_DEBUG'});
@@ -132,8 +134,8 @@ sub text_block
     # Check if any text to display
     unless( defined( $text) and length($text) > 0 )
     {
-        carp "Warning: No input text found. Trying to add dummy '-' and not to break everything.\n";
-        $text = '-';
+        carp "Warning: No input text found. Trying to add dummy '-' and not to break everything.\n";
+        $text = ' ';
     }
 
     # Strip any <CR> and Split the text into paragraphs
@@ -209,8 +211,12 @@ sub text_block
         }
 
         # Lets take from paragraph as many words as we can put into $width - $indent;
-        while ( @paragraph and $text_object->advancewidth( join("\x20", @line)."\x20" . $paragraph[0]) +
-                                $line_width < $width )
+        # Always take at least one word; otherwise we'd end up in an infinite loop.
+        while ( !scalar(@line) || (
+          @paragraph && (
+            $text_object->advancewidth( join("\x20", @line)."\x20" . $paragraph[0]) + $line_width < $width
+          )
+        ))
         {
             push(@line, shift(@paragraph));
         }
@@ -326,6 +332,7 @@ sub table
         max_word_length       => 1,
         cell_render_hook      => 1,
         default_text          => 1,
+        num_header_rows       => 1,
     );
     foreach my $key (keys %arg)
     {
@@ -371,7 +378,7 @@ sub table
     #=====================================
     # 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')
     {
@@ -386,9 +393,8 @@ sub table
         $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;
     }
-
-    my $header_row  = undef;
     #=====================================
     # Other Parameters check
     #=====================================
@@ -431,7 +437,10 @@ sub table
     }
 
     # Copy the header row if header is enabled
-    @$header_row = $$data[0] if defined $header_props;
+    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
@@ -442,7 +451,7 @@ sub table
     #  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_props = [];
+    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 );
@@ -454,6 +463,8 @@ sub table
 
     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;
@@ -494,8 +505,12 @@ sub table
                 $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 )
+            if( $max_word_len > 0 && $data->[$row_idx][$column_idx])
             {
                 $data->[$row_idx][$column_idx] =~ s#(\S{$max_word_len})(?=\S)#$1 #g;
             }
@@ -542,20 +557,25 @@ sub table
         $row_col_widths->[$row_idx] = $column_widths;
 
         # Copy the calculated row properties of header row.
-        @$header_row_props = @$column_widths if(!$row_idx and ref $header_props);
+        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_height = $rows_height->[0];
+    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}))
     {
@@ -570,7 +590,7 @@ sub table
             # 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";
+                #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;
             }
 
@@ -592,23 +612,17 @@ sub table
             # 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";
+                #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'})
             {
-                # 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_col_widths, $hrp;
-                unshift @$rows_height, $header_row_height;
-
-                $first_row = 1; # Means YES
-                $row_index--; # Rollback the row_index because a new header row has been added
+                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;
             }
         }
 
@@ -659,10 +673,15 @@ sub table
 
             # 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 % 2 ? $background_color_even  : $background_color_odd;
-            $font_color       = $row_index % 2 ? $font_color_even        : $font_color_odd;
+            $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;
@@ -680,6 +699,7 @@ sub table
             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++ )
@@ -691,7 +711,7 @@ sub table
                 # look for font information for this cell
                 my ($cell_font, $cell_font_size, $cell_font_color, $cell_font_underline, $justify);
 
-                if( $first_row and ref $header_props)
+                if( $remaining_header_rows and ref $header_props)
                 {
                     $cell_font           = $header_props->{'font'};
                     $cell_font_size      = $header_props->{'font_size'};
@@ -732,19 +752,34 @@ sub table
                                        //  $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] <= $calc_column_widths->[$column_idx]
+                    $record_widths->[$column_idx] <= $this_width
                 ){
                     my $space = $pad_left;
                     if ($justify eq 'right')
                     {
-                        $space = $calc_column_widths->[$column_idx] -($txt->advancewidth($record->[$column_idx]) + $pad_right);
+                        $space = $this_width -($txt->advancewidth($record->[$column_idx]) + $pad_right);
                     }
                     elsif ($justify eq 'center')
                     {
-                        $space = ($calc_column_widths->[$column_idx] - $txt->advancewidth($record->[$column_idx])) / 2;
+                        $space = ($this_width - $txt->advancewidth($record->[$column_idx])) / 2;
                     }
                     $txt->translate( $cur_x + $space, $text_start );
                     my %text_options;
@@ -759,7 +794,7 @@ sub table
                         $record->[$column_idx],
                         x        => $cur_x + $pad_left,
                         y        => $text_start,
-                        w        => $calc_column_widths->[$column_idx] - $pad_left - $pad_right,
+                        w        => $this_width - $pad_left - $pad_right,
                         h        => $cur_y - $bot_marg - $pad_top - $pad_bot,
                         align    => $justify,
                         lead     => $lead
@@ -793,6 +828,9 @@ sub table
                 }
 
                 $cur_x += $calc_column_widths->[$column_idx];
+
+                push @vertical_lines, (!$colspan || (1 >= $colspan)) ? 1 : 0;
+                $colspan-- if $colspan;
             }
             if( $do_leftovers )
             {
@@ -809,13 +847,13 @@ sub table
             {
                 my $cell_bg_color;
 
-                if( $first_row and ref $header_props)
+                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][$column_idx]->{'background_color'}
+                $cell_bg_color ||= $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{'background_color'}
                                ||  $col_props->[$column_idx]->{'background_color'}
                                ||  $background_color;
 
@@ -826,6 +864,12 @@ sub table
                     $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;
@@ -835,8 +879,12 @@ sub table
                 $gfx->hline( $xbase + $width );
             }
 
-            $row_index++ unless ( $do_leftovers );
             $first_row = 0;
+            if ($remaining_header_rows) {
+              $remaining_header_rows--;
+            } else {
+              $row_index++ unless $do_leftovers;
+            }
         }# End of Row_Loop
 
         if ($gfx)
@@ -846,13 +894,8 @@ sub table
             {
                 $gfx->move(  $xbase, $table_top_y);
                 $gfx->vline( $cur_y );
-                my $cur_x = $xbase;
-                for( my $j = 0; $j < $columns_number; $j++ )
-                {
-                    $cur_x += $calc_column_widths->[$j];
-                    $gfx->move(  $cur_x, $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
@@ -903,32 +946,11 @@ sub CalcColumnWidths
         $calc_widths->[$j] = $col_props->[$j]->{min_w} || 0;;
     }
 
-    # Allow columns to expand to max_w before applying extra space equally.
-    my $is_last_iter;
-    for (;;)
-    {
-        my $span = ($avail_width - $min_width) / scalar( @$col_props);
-        last if $span <= 0;
-
-        $min_width = 0;
-        my $next_will_be_last_iter = 1;
-        for(my $j = 0; $j < scalar(@$col_props); $j++ )
-        {
-            my $new_w = $calc_widths->[$j] + $span;
-
-            if (!$is_last_iter && $new_w > $col_props->[$j]->{max_w})
-            {
-                $new_w = $col_props->[$j]->{max_w}
-            }
-            if ($calc_widths->[$j] != $new_w )
-            {
-                $calc_widths->[$j] = $new_w;
-                $next_will_be_last_iter = 0;
-            }
-            $min_width += $new_w;
-        }
-        last if $is_last_iter;
-        $is_last_iter = $next_will_be_last_iter;
+    my $span = 0;
+    # Calculate how much can be added to every column to fit the available width
+    $span = ($avail_width - $min_width) / scalar( @$col_props);
+    for (my $j = 0; $j < scalar(@$col_props); $j++ ) {
+      $calc_widths->[$j] = $col_props->[$j]->{min_w} + $span;
     }
 
     return ($calc_widths,$avail_width);