PDF::Table: Warnungen und "-" Artefakte bei undef entfernt
[kivitendo-erp.git] / modules / override / PDF / Table.pm
1 package PDF::Table;
2
3 use 5.006;
4 use strict;
5 use warnings;
6 use Carp;
7 use List::Util qw(sum);
8 our $VERSION = '0.9.10';
9
10 print __PACKAGE__.' is version: '.$VERSION.$/ if($ENV{'PDF_TABLE_DEBUG'});
11
12 ############################################################
13 #
14 # new - Constructor
15 #
16 # Parameters are meta information about the PDF
17 #
18 # $pdf = PDF::Table->new();
19 #
20 ############################################################
21
22 sub new
23 {
24     my $type = shift(@_);
25     my $class = ref($type) || $type;
26     my $self  = {};
27     bless ($self, $class);
28
29     # Pass all the rest to init for validation and initialisation
30     $self->_init(@_);
31
32     return $self;
33 }
34
35 sub _init
36 {
37     my ($self, $pdf, $page, $data, %options ) = @_;
38
39     # Check and set default values 
40     $self->set_defaults();
41
42     # Check and set mandatory params
43     $self->set_pdf($pdf);
44     $self->set_page($page);
45     $self->set_data($data);
46     $self->set_options(\%options);
47
48     return;
49 }
50
51 sub set_defaults{
52         my $self = shift;
53         
54         $self->{'font_size'} = 12;
55 }
56
57 sub set_pdf{
58     my ($self, $pdf) = @_;
59     $self->{'pdf'} = $pdf;
60 }
61
62 sub set_page{
63     my ($self, $page) = @_;
64     if ( defined($page) && ref($page) ne 'PDF::API2::Page' ){
65
66         if( ref($self->{'pdf'}) eq 'PDF::API2' ){
67             $self->{'page'} = $self->{'pdf'}->page();
68         } else {
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!'.$/;
71         }
72         return;
73     }
74     $self->{'page'} = $page;
75
76 }
77
78 sub set_data{
79     my ($self, $data) = @_;
80     #TODO: implement
81 }
82
83 sub set_options{
84     my ($self, $options) = @_;
85     #TODO: implement
86 }
87
88 ############################################################
89 #
90 # text_block - utility method to build multi-paragraph blocks of text
91 #
92 ############################################################
93
94 sub text_block
95 {
96     my $self        = shift;
97     my $text_object = shift;
98     my $text        = shift;    # The text to be displayed
99     my %arg         = @_;       # Additional Arguments
100
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
105
106     # Try to provide backward compatibility
107     foreach my $key (keys %arg)
108     {
109         my $newkey = $key;
110         if($newkey =~ s#^-##)
111         {
112             $arg{$newkey} = $arg{$key};
113             delete $arg{$key};
114         }
115     }
116     #####
117
118     #---
119     # Lets check mandatory parameters with no default values
120     #---
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 )
131     {
132 #         carp "Warning: No input text found. Trying to add dummy '-' and not to break everything.\n";
133         $text = ' ';
134     }
135
136     # Strip any <CR> and Split the text into paragraphs
137     $text =~ s/\r//g;
138     my @paragraphs  = split(/\n/, $text);
139
140     # Width between lines in pixels
141     my $line_space = defined $arg{'lead'} && $arg{'lead'} > 0 ? $arg{'lead'} : 12;
142
143     # Calculate width of all words
144     my $space_width = $text_object->advancewidth("\x20");
145     my @words = split(/\s+/, $text);
146     foreach (@words) 
147     {
148         next if exists $width{$_};
149         $width{$_} = $text_object->advancewidth($_);
150     }
151
152     my @paragraph = split(' ', shift(@paragraphs));
153     my $first_line = 1;
154     my $first_paragraph = 1;
155
156     # Little Init
157     $xpos = $xbase;
158     $ypos = $ybase;
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 ) 
163     {
164         # Is there any text to render ?
165         unless (@paragraph) 
166         {
167             # Finish if nothing left
168             last unless scalar @paragraphs;
169             # Else take one line from the text
170             @paragraph = split(' ', shift( @paragraphs ) );
171
172             $ypos -= $arg{'parspace'} if $arg{'parspace'};
173             last unless $ypos >= $bottom_border;
174         }
175         $ypos -= $line_space;
176         $xpos = $xbase;
177
178         # While there's room on the line, add another word
179         @line = ();
180         $line_width = 0;
181         if( $first_line && exists $arg{'hang'} ) 
182         {
183             my $hang_width = $text_object->advancewidth($arg{'hang'});
184     
185             $text_object->translate( $xpos, $ypos );
186             $text_object->text( $arg{'hang'} );
187     
188             $xpos         += $hang_width;
189             $line_width   += $hang_width;
190             $arg{'indent'} += $hang_width if $first_paragraph;
191         }
192         elsif( $first_line && exists $arg{'flindent'} && $arg{'flindent'} > 0 ) 
193         {
194             $xpos += $arg{'flindent'};
195             $line_width += $arg{'flindent'};
196         }
197         elsif( $first_paragraph && exists $arg{'fpindent'} && $arg{'fpindent'} > 0 ) 
198         {
199             $xpos += $arg{'fpindent'};
200             $line_width += $arg{'fpindent'};
201         }
202         elsif (exists $arg{'indent'} && $arg{'indent'} > 0 ) 
203         {
204             $xpos += $arg{'indent'};
205             $line_width += $arg{'indent'};
206         }
207     
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) || (
211           @paragraph && (
212             $text_object->advancewidth( join("\x20", @line)."\x20" . $paragraph[0]) + $line_width < $width
213           )
214         ))
215         {
216             push(@line, shift(@paragraph));
217         }
218         $line_width += $text_object->advancewidth(join('', @line));
219             
220         # calculate the space width
221         if( $arg{'align'} eq 'fulljustify' or ($arg{'align'} eq 'justify' and @paragraph)) 
222         {
223             @line = split(//,$line[0]) if (scalar(@line) == 1) ;
224             $wordspace = ($width - $line_width) / (scalar(@line) - 1);
225             $align='justify';
226         } 
227         else 
228         {
229             $align=($arg{'align'} eq 'justify') ? 'left' : $arg{'align'};
230             $wordspace = $space_width;
231         }
232         $line_width += $wordspace * (scalar(@line) - 1);
233     
234         if( $align eq 'justify') 
235         {
236             foreach my $word (@line) 
237             {
238                 $text_object->translate( $xpos, $ypos );
239                 $text_object->text( $word );
240                 $xpos += ($width{$word} + $wordspace) if (@line);
241             }
242             $endw = $width;
243         } 
244         else 
245         {
246             # calculate the left hand position of the line
247             if( $align eq 'right' ) 
248             {
249                 $xpos += $width - $line_width;
250             } 
251             elsif( $align eq 'center' ) 
252             {
253                 $xpos += ( $width / 2 ) - ( $line_width / 2 );
254             }
255     
256             # render the line
257             $text_object->translate( $xpos, $ypos );
258             $endw = $text_object->text( join("\x20", @line));
259         }
260         $first_line = 0;
261     }#End of while(
262     unshift(@paragraphs, join(' ',@paragraph)) if scalar(@paragraph);
263     return ($endw, $ypos, join("\n", @paragraphs))
264 }
265
266
267 ################################################################
268 # table - utility method to build multi-row, multicolumn tables
269 ################################################################
270 sub table
271 {
272     my $self    = shift;
273     my $pdf     = shift;
274     my $page    = shift;
275     my $data    = shift;
276     my %arg     = @_;
277
278     #=====================================
279     # Mandatory Arguments Section
280     #=====================================
281     unless($pdf and $page and $data)
282     {
283         carp "Error: Mandatory parameter is missing pdf/page/data object!\n";
284         return;
285     }
286
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));
292
293     # Validate settings key
294     my %valid_settings_key = (
295         x                     => 1,
296         w                     => 1,
297         start_y               => 1,
298         start_h               => 1,
299         next_y                => 1,
300         next_h                => 1,
301         lead                  => 1,
302         padding               => 1,
303         padding_right         => 1,
304         padding_left          => 1,
305         padding_top           => 1,
306         padding_bottom        => 1,
307         background_color      => 1,
308         background_color_odd  => 1,
309         background_color_even => 1,
310         border                => 1,
311         border_color          => 1,
312         horizontal_borders    => 1,
313         vertical_borders      => 1,
314         font                  => 1,
315         font_size             => 1,
316         font_color            => 1,
317         font_color_even       => 1,
318         background_color_odd  => 1,
319         background_color_even => 1,
320         row_height            => 1,
321         new_page_func         => 1,
322         header_props          => 1,
323         column_props          => 1,
324         cell_props            => 1,
325         max_word_length       => 1,
326         num_header_rows       => 1,
327     );
328     foreach my $key (keys %arg) {
329         croak "Error: Invalid setting key '$key' received." 
330             unless (exists $valid_settings_key{$key});
331     }
332
333     # Try to provide backward compatibility
334     foreach my $key (keys %arg)
335     {
336         my $newkey = $key;
337         if($newkey =~ s#^-##)
338         {
339             $arg{$newkey} = $arg{$key};
340             delete $arg{$key};
341         }
342     }
343     
344     ######
345     #TODO: Add code for header props compatibility and col_props comp....
346     ######
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;
353
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; }
359
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;
363
364     # Create Text Object
365     my $txt     = $page->text;
366
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;
371
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')
380     {
381         # Transfer the reference to local variable
382         $header_props = $arg{'header_props'};
383
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;
392     }
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'}
404         : $line_w;
405     my $vert_borders  = defined $arg{'vertical_borders'}
406         ? $arg{'vertical_borders'}
407         : $line_w;
408     
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';
414
415     my $min_row_h   = $fnt_size + $pad_top + $pad_bot;
416     my $row_h       = defined ($arg{'row_height'}) 
417                                 && 
418                     ($arg{'row_height'} > $min_row_h) 
419                                 ? 
420                      $arg{'row_height'} : $min_row_h;
421
422     my $pg_cnt      = 1;
423     my $cur_y       = $ybase;
424     my $cell_props  = $arg{cell_props} || [];   # per cell properties
425
426     #If there is no valid data array reference warn and return!
427     if(ref $data ne 'ARRAY')
428     {
429         carp "Passed table data is not an ARRAY reference. It's actually a ref to ".ref($data);
430         return ($page,0,$cur_y);
431     }
432
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);
437     }
438     # Determine column widths based on content
439
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'} || [];
443
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;
449  
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 );
453
454     my $word_widths  = {};
455     my $rows_height  = [];
456
457     for( my $row_idx = 0; $row_idx < scalar(@$data) ; $row_idx++ )
458     {
459         push @header_row_widths, [] if $row_idx < $header_props->{num_header_rows};
460
461         my $column_widths = []; #holds the width of each column
462         # Init the height for this row
463         $rows_height->[$row_idx] = 0;
464         
465         for( my $column_idx = 0; $column_idx < scalar(@{$data->[$row_idx]}) ; $column_idx++ )
466         {
467             # look for font information for this column
468             my ($cell_font, $cell_font_size);
469             
470             if( !$row_idx and ref $header_props )
471             {   
472                 $cell_font      = $header_props->{'font'};
473                 $cell_font_size = $header_props->{'font_size'};
474             }
475             
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'}
479                             ||  $fnt_name;
480                               
481             $cell_font_size ||= $cell_props->[$row_idx][$column_idx]->{'font_size'}
482                             ||  $col_props->[$column_idx]->{'font_size'}
483                             ||  $fnt_size;
484                               
485             # Set Font
486             $txt->font( $cell_font, $cell_font_size ); 
487             
488             # Set row height to biggest font size from row's cells
489             if( $cell_font_size  > $rows_height->[$row_idx] )
490             {   
491                 $rows_height->[$row_idx] = $cell_font_size;
492             }
493
494             # This should fix a bug with very long words like serial numbers etc.
495             if( $max_word_len > 0 )
496             {
497                 $data->[$row_idx][$column_idx] =~ s#(\S{$max_word_len})(?=\S)#$1 #g;
498             }
499
500             # Init cell size limits
501             $space_w                      = $txt->advancewidth( "\x20" );
502             $column_widths->[$column_idx] = 0;
503             $max_col_w                    = 0;
504             $min_col_w                    = 0;
505
506             my @words = split( /\s+/, $data->[$row_idx][$column_idx] );
507
508             foreach( @words ) 
509             {
510                 unless( exists $word_widths->{$_} )
511                 {   # Calculate the width of every word and add the space width to it
512                     $word_widths->{$_} = $txt->advancewidth( $_ ) + $space_w;
513                 }
514                 
515                 $column_widths->[$column_idx] += $word_widths->{$_};
516                 $min_col_w                     = $word_widths->{$_} if( $word_widths->{$_} > $min_col_w );
517                 $max_col_w                    += $word_widths->{$_};
518             }
519             
520             $min_col_w                    += $pad_left + $pad_right;
521             $max_col_w                    += $pad_left + $pad_right;
522             $column_widths->[$column_idx] += $pad_left + $pad_right;
523
524             # Keep a running total of the overall min and max widths
525             $col_props->[$column_idx]->{'min_w'} ||= 0;
526             $col_props->[$column_idx]->{'max_w'} ||= 0;
527
528             if( $min_col_w > $col_props->[$column_idx]->{'min_w'} )
529             {   # Calculated Minimum Column Width is more than user-defined
530                 $col_props->[$column_idx]->{'min_w'} = $min_col_w ;
531             }
532             
533             if( $max_col_w > $col_props->[$column_idx]->{'max_w'} )
534             {   # Calculated Maximum Column Width is more than user-defined
535                 $col_props->[$column_idx]->{'max_w'} = $max_col_w ;
536             }
537         }#End of for(my $column_idx....
538         
539         $row_col_widths->[$row_idx] = $column_widths;
540         
541         # Copy the calculated row properties of header row. 
542         if (ref $header_props && $row_idx < $header_props->{num_header_rows}) {
543           push @header_row_widths, [ @{ $column_widths } ];
544         }
545     }
546
547     # Calc real column widths and expand table width if needed.
548     my $calc_column_widths; 
549     ($calc_column_widths, $width) = CalcColumnWidths( $col_props, $width );
550     my $num_cols = scalar @{ $calc_column_widths };
551
552     # Lets draw what we have!
553     my $row_index    = 0;
554     # Store header row height for later use if headers have to be repeated
555     my @header_row_heights = @$rows_height[0 .. $header_props->{num_header_rows}-1];
556
557     my ( $gfx, $gfx_bg, $background_color, $font_color, $bot_marg, $table_top_y, $text_start);
558
559     my $remaining_header_rows = $header_props ? $header_props->{num_header_rows} : 0;
560
561     # Each iteration adds a new page as neccessary
562     while(scalar(@{$data}))
563     {
564         my ($page_header, $columns_number);
565
566         if($pg_cnt == 1)
567         {
568             $table_top_y = $ybase;
569             $bot_marg = $table_top_y - $height;
570         }
571         else
572         {
573             if(ref $arg{'new_page_func'})
574             {   
575                 $page = &{$arg{'new_page_func'}};   
576             }
577             else
578             {   
579                 $page = $pdf->page; 
580             }
581     
582             $table_top_y = $next_y;
583             $bot_marg = $table_top_y - $next_h;
584
585             if( ref $header_props and $header_props->{'repeat'})
586             {
587                 unshift @$data,           @header_rows;
588                 unshift @$row_col_widths, @header_row_widths;
589                 unshift @$rows_height,    @header_row_heights;
590                 $remaining_header_rows = $header_props->{num_header_rows};
591             }
592         }
593
594         # Check for safety reasons
595         if( $bot_marg < 0 )
596         {   # This warning should remain i think
597 #            carp "!!! Warning: !!! Incorrect Table Geometry! Setting bottom margin to end of sheet!\n";
598             $bot_marg = 0;
599         }
600
601         $gfx_bg = $page->gfx;
602         $txt = $page->text;
603         $txt->font($fnt_name, $fnt_size); 
604
605         $cur_y = $table_top_y;
606
607         if ($line_w)
608         {
609             $gfx = $page->gfx;
610             $gfx->strokecolor($border_color);
611             $gfx->linewidth($line_w);
612
613             # Draw the top line
614             if ($horiz_borders) 
615             {
616                 $gfx->move( $xbase , $cur_y );
617                 $gfx->hline($xbase + $width );
618             }
619         }
620         else
621         {
622             $gfx = undef;
623         }
624
625         # Each iteration adds a row to the current page until the page is full 
626         #  or there are no more rows to add
627         # Row_Loop
628         while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
629         {
630             # Remove the next item from $data
631             my $record = shift @{$data};
632             
633             # Get columns number to know later how many vertical lines to draw
634             # TODO: get the max number of columns per page as currently last row's columns overrides
635             $columns_number = scalar(@$record);
636
637             # Get the next set of row related settings
638             # Row Height
639             my $pre_calculated_row_height = shift @$rows_height;
640
641             # Row cell widths
642             my $record_widths = shift @$row_col_widths;
643
644             # Row coloumn props - TODO in another commit
645
646             # Row cell props - TODO in another commit
647
648             # Added to resolve infite loop bug with returned undef values
649             for(my $d = 0; $d < scalar(@{$record}) ; $d++)
650             { 
651                 $record->[$d] = ' ' unless( defined $record->[$d]);
652             }
653
654             # Choose colors for this row
655             $background_color = ($row_index - $header_props->{num_header_rows}) % 2 ? $background_color_even  : $background_color_odd;
656             $font_color       = ($row_index - $header_props->{num_header_rows}) % 2 ? $font_color_even        : $font_color_odd;
657
658             #Determine current row height
659             my $current_row_height = $pad_top + $pre_calculated_row_height + $pad_bot;
660
661             # $row_h is the calculated global user requested row height.
662             # It will be honored, only if it has bigger value than the calculated one.
663             # TODO: It's questionable if padding should be inclided in this calculation or not
664             if($current_row_height < $row_h){
665                 $current_row_height = $row_h;
666             }
667
668             # Define the font y base position for this line.
669             $text_start      = $cur_y - ($current_row_height - $pad_bot);
670
671             my $cur_x        = $xbase;
672             my $leftovers    = undef;   # Reference to text that is returned from textblock()
673             my $do_leftovers = 0;
674             my ($colspan, @vertical_lines);
675
676             # Process every cell(column) from current row
677             for( my $column_idx = 0; $column_idx < scalar( @$record); $column_idx++ ) 
678             {
679                 next unless $col_props->[$column_idx]->{'max_w'};
680                 next unless $col_props->[$column_idx]->{'min_w'};  
681                 $leftovers->[$column_idx] = undef;
682
683                 # look for font information for this cell
684                 my ($cell_font, $cell_font_size, $cell_font_color, $justify);
685                                     
686                 if( $remaining_header_rows and ref $header_props)
687                 {   
688                     $cell_font       = $header_props->{'font'};
689                     $cell_font_size  = $header_props->{'font_size'};
690                     $cell_font_color = $header_props->{'font_color'};
691                     $justify         = $header_props->{'justify'};
692                 }
693                 
694                 # Get the most specific value if none was already set from header_props
695                 $cell_font       ||= $cell_props->[$row_index][$column_idx]->{'font'} 
696                                  ||  $col_props->[$column_idx]->{'font'}
697                                  ||  $fnt_name;
698                                   
699                 $cell_font_size  ||= $cell_props->[$row_index][$column_idx]->{'font_size'}
700                                  ||  $col_props->[$column_idx]->{'font_size'}
701                                  ||  $fnt_size;
702                                   
703                 $cell_font_color ||= $cell_props->[$row_index][$column_idx]->{'font_color'}
704                                  ||  $col_props->[$column_idx]->{'font_color'}
705                                  ||  $font_color;
706                                 
707                 $justify         ||= $cell_props->[$row_index][$column_idx]->{'justify'}
708                                  ||  $col_props->[$column_idx]->{'justify'}
709                                  ||  $arg{'justify'}
710                                  ||  'left';                                    
711                 
712                 # Init cell font object
713                 $txt->font( $cell_font, $cell_font_size );
714                 $txt->fillcolor($cell_font_color);
715
716                 my $this_width;
717                 if (!$remaining_header_rows && $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan}) {
718                     $colspan = $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan};
719                 } elsif ($remaining_header_rows && ($header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan})) {
720                     $colspan = $header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan};
721                 }
722
723                 if ($colspan) {
724                     $colspan     = $num_cols - $column_idx if (-1 == $colspan);
725                     my $last_idx = $column_idx + $colspan - 1;
726                     $this_width  = sum @{ $calc_column_widths }[$column_idx..$last_idx];
727                 } else {
728                     $this_width = $calc_column_widths->[$column_idx];
729                 }
730  
731                 # If the content is wider than the specified width, we need to add the text as a text block
732                 if( $record->[$column_idx] !~ m/(.\n.)/ and
733                     $record_widths->[$column_idx] and 
734                     $record_widths->[$column_idx] <= $this_width
735                 ){
736                     my $space = $pad_left;
737                     if ($justify eq 'right')
738                     {
739                         $space = $this_width -($txt->advancewidth($record->[$column_idx]) + $pad_right);
740                     }
741                     elsif ($justify eq 'center')
742                     {
743                         $space = ($this_width - $txt->advancewidth($record->[$column_idx])) / 2;
744                     }
745                     $txt->translate( $cur_x + $space, $text_start );
746                     $txt->text( $record->[$column_idx] );
747                 }
748                 # Otherwise just use the $page->text() method
749                 else
750                 {
751                     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
752                         $txt,
753                         $record->[$column_idx],
754                         x        => $cur_x + $pad_left,
755                         y        => $text_start,
756                         w        => $this_width - $pad_left - $pad_right,
757                         h        => $cur_y - $bot_marg - $pad_top - $pad_bot,
758                         align    => $justify,
759                         lead     => $lead
760                     );
761                     # Desi - Removed $lead because of fixed incorrect ypos bug in text_block
762                     my  $current_cell_height = $cur_y - $ypos_of_last_line + $pad_bot;
763                     if( $current_cell_height > $current_row_height )
764                     {
765                         $current_row_height = $current_cell_height;
766                     }
767                     
768                     if( $left_over_text )
769                     {
770                         $leftovers->[$column_idx] = $left_over_text;
771                         $do_leftovers = 1;
772                     }
773                 }
774                 $cur_x += $calc_column_widths->[$column_idx];
775
776                 push @vertical_lines, (!$colspan || (1 >= $colspan)) ? 1 : 0;
777                 $colspan-- if $colspan;
778             }
779             if( $do_leftovers )
780             {
781                 unshift @$data, $leftovers;
782                 unshift @$row_col_widths, $record_widths;
783                 unshift @$rows_height, $pre_calculated_row_height;
784             }
785             
786             # Draw cell bgcolor
787             # This has to be separately from the text loop 
788             #  because we do not know the final height of the cell until all text has been drawn
789             $cur_x = $xbase;
790             for(my $column_idx = 0 ; $column_idx < scalar(@$record) ; $column_idx++)
791             {
792                 my $cell_bg_color;
793                                     
794                 if( $remaining_header_rows and ref $header_props)
795                 {                                  #Compatibility                 Consistency with other props    
796                     $cell_bg_color = $header_props->{'bg_color'} || $header_props->{'background_color'};
797                 }
798                 
799                 # Get the most specific value if none was already set from header_props
800                 $cell_bg_color ||= $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{'background_color'}
801                                ||  $col_props->[$column_idx]->{'background_color'}
802                                ||  $background_color;
803
804                 if ($cell_bg_color)
805                 {
806                     $gfx_bg->rect( $cur_x, $cur_y-$current_row_height, $calc_column_widths->[$column_idx], $current_row_height);
807                     $gfx_bg->fillcolor($cell_bg_color);
808                     $gfx_bg->fill();
809                 }
810                 $cur_x += $calc_column_widths->[$column_idx];
811
812                 if ($line_w && $vertical_lines[$column_idx] && ($column_idx != (scalar(@{ $record }) - 1))) {
813                     $gfx->move($cur_x, $cur_y);
814                     $gfx->vline($cur_y - $current_row_height);
815                     $gfx->fillcolor($border_color);
816                 }
817             }#End of for(my $column_idx....
818
819             $cur_y -= $current_row_height;
820             if ($gfx && $horiz_borders)
821             {
822                 $gfx->move(  $xbase , $cur_y );
823                 $gfx->hline( $xbase + $width );
824             }
825
826             if ($remaining_header_rows) {
827               $remaining_header_rows--;
828             } else {
829               $row_index++ unless $do_leftovers;
830             }
831         }# End of Row_Loop
832
833         if ($gfx)
834         {
835             # Draw vertical lines
836             if ($vert_borders) 
837             {
838                 $gfx->move(  $xbase, $table_top_y);
839                 $gfx->vline( $cur_y );
840                 $gfx->move($xbase + sum(@{ $calc_column_widths }[0..$num_cols - 1]), $table_top_y);
841                 $gfx->vline( $cur_y );
842             }
843
844             # ACTUALLY draw all the lines
845             $gfx->fillcolor( $border_color);
846             $gfx->stroke;
847         }
848         $pg_cnt++;
849     }# End of while(scalar(@{$data}))
850
851     return ($page,--$pg_cnt,$cur_y);
852 }
853
854
855 # calculate the column widths
856 sub CalcColumnWidths
857 {
858     my $col_props   = shift;
859     my $avail_width = shift;
860     my $min_width   = 0;
861
862     my $calc_widths ;
863
864     for(my $j = 0; $j < scalar( @$col_props); $j++)
865     {
866         $min_width += $col_props->[$j]->{min_w} || 0;
867     }
868
869     # I think this is the optimal variant when good view can be guaranateed
870     if($avail_width < $min_width)
871     {
872         carp "!!! Warning !!!\n Calculated Mininal width($min_width) > Table width($avail_width).\n",
873             ' Expanding table width to:',int($min_width)+1,' but this could lead to unexpected results.',"\n",
874             ' Possible solutions:',"\n",
875             '  0)Increase table width.',"\n",
876             '  1)Decrease font size.',"\n",
877             '  2)Choose a more narrow font.',"\n",
878             '  3)Decrease "max_word_length" parameter.',"\n",
879             '  4)Rotate page to landscape(if it is portrait).',"\n",
880             '  5)Use larger paper size.',"\n",
881             '!!! --------- !!!',"\n";
882         $avail_width = int( $min_width) + 1;
883
884     }
885
886     # Calculate how much can be added to every column to fit the available width.
887     for(my $j = 0; $j < scalar(@$col_props); $j++ )
888     {
889         $calc_widths->[$j] = $col_props->[$j]->{min_w} || 0;;
890     }
891
892     my $span = 0;
893     # Calculate how much can be added to every column to fit the available width
894     $span = ($avail_width - $min_width) / scalar( @$col_props);
895     for (my $j = 0; $j < scalar(@$col_props); $j++ ) {
896       $calc_widths->[$j] = $col_props->[$j]->{min_w} + $span;
897     }
898
899     return ($calc_widths,$avail_width);
900 }
901 1;
902
903 __END__
904
905 =pod
906
907 =head1 NAME
908
909 PDF::Table - A utility class for building table layouts in a PDF::API2 object.
910
911 =head1 SYNOPSIS
912
913  use PDF::API2;
914  use PDF::Table;
915
916  my $pdftable = new PDF::Table;
917  my $pdf = new PDF::API2(-file => "table_of_lorem.pdf");
918  my $page = $pdf->page;
919
920  # some data to layout
921  my $some_data =[
922     ["1 Lorem ipsum dolor",
923     "Donec odio neque, faucibus vel",
924     "consequat quis, tincidunt vel, felis."],
925     ["Nulla euismod sem eget neque.",
926     "Donec odio neque",
927     "Sed eu velit."],
928     #... and so on
929  ];
930
931  $left_edge_of_table = 50;
932  # build the table layout
933  $pdftable->table(
934      # required params
935      $pdf,
936      $page,
937      $some_data,
938      x => $left_edge_of_table,
939      w => 495,
940      start_y => 500,
941      start_h => 300,
942      # some optional params
943      next_y  => 750,
944      next_h  => 500,
945      padding => 5,
946      padding_right => 10,
947      background_color_odd  => "gray",
948      background_color_even => "lightblue", #cell background color for even rows
949   );
950
951  # do other stuff with $pdf
952  $pdf->saveas();
953 ...
954
955 =head1 EXAMPLE
956
957 For a complete working example or initial script look into distribution`s 'examples' folder.
958
959
960 =head1 DESCRIPTION
961
962 This class is a utility for use with the PDF::API2 module from CPAN. 
963 It can be used to display text data in a table layout within a PDF. 
964 The text data must be in a 2D array (such as returned by a DBI statement handle fetchall_arrayref() call). 
965 The PDF::Table will automatically add as many new pages as necessary to display all of the data. 
966 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. 
967 Also a (non)repeated header row with different layout properties can be specified. 
968
969 See the L</METHODS> section for complete documentation of every parameter.
970
971 =head1 METHODS
972
973 =head2 new()
974
975     my $pdf_table = new PDF::Table;
976
977 =over
978
979 =item Description
980
981 Creates a new instance of the class. (to be improved)
982
983 =item Parameters
984
985 There are no parameters. 
986
987 =item Returns
988
989 Reference to the new instance
990
991 =back
992
993 =head2 table()
994
995     my ($final_page, $number_of_pages, $final_y) = table($pdf, $page, $data, %settings)
996     
997 =over
998
999 =item Description
1000
1001 Generates a multi-row, multi-column table into an existing PDF document based on provided data set and settings.
1002
1003 =item Parameters
1004
1005     $pdf      - a PDF::API2 instance representing the document being created
1006     $page     - a PDF::API2::Page instance representing the current page of the document
1007     $data     - an ARRAY reference to a 2D data structure that will be used to build the table
1008     %settings - HASH with geometry and formatting parameters. 
1009
1010 For full %settings description see section L</Table settings> below.
1011
1012 This method will add more pages to the pdf instance as required based on the formatting options and the amount of data.
1013
1014 =item Reuturns
1015
1016 The return value is a 3 items list where 
1017
1018     $final_page - The first item is a PDF::API2::Page instance that the table ends on
1019     $number_of_pages - The second item is the count of pages that the table spans on
1020     $final_y - The third item is the Y coordinate of the table bottom so that additional content can be added in the same document.
1021
1022 =item Example
1023
1024     my $pdf  = new PDF::API2;
1025     my $page = $pdf->page();
1026     my $data = [
1027         ['foo1','bar1','baz1'],
1028         ['foo2','bar2','baz2']
1029     ];
1030     my %settings = (
1031         x       => 10,
1032         w       => 570,
1033         start_y => 220,
1034         start_h => 180,
1035     );
1036     
1037     my ($final_page, $number_of_pages, $final_y) = $pdftable->table( $pdf, $page, $data, %options );
1038
1039 =back
1040
1041 =head3 Table settings
1042
1043 =head4 Mandatory
1044
1045 There are some mandatory parameteres for setting table geometry and position across page(s)
1046
1047 =over 
1048
1049 =item B<x> - X coordinate of upper left corner of the table. Left edge of the sheet is 0.
1050
1051 B<Value:> can be any whole number satisfying 0 =< X < PageWidth
1052 B<Default:> No default value 
1053
1054     x => 10
1055
1056 =item B<start_y> - Y coordinate of upper left corner of the table at the initial page.
1057
1058 B<Value:> can be any whole number satisfying 0 < start_y < PageHeight (depending on space availability when embedding a table)
1059 B<Default:> No default value
1060
1061     start_y => 327
1062
1063 =item B<w> - width of the table starting from X.
1064
1065 B<Value:> can be any whole number satisfying 0 < w < PageWidth - x
1066 B<Default:> No default value
1067
1068     w  => 570
1069
1070 =item B<start_h> - Height of the table on the initial page
1071
1072 B<Value:> can be any whole number satisfying 0 < start_h < PageHeight - Current Y position
1073 B<Default:> No default value
1074
1075     start_h => 250
1076     
1077 =back
1078
1079 =head4 Optional
1080
1081 =over
1082
1083 =item B<next_h> - Height of the table on any additional page
1084
1085 B<Value:> can be any whole number satisfying 0 < next_h < PageHeight
1086 B<Default:> Value of param B<'start_h'>
1087
1088     next_h  => 700
1089
1090 =item B<next_y> - Y coordinate of upper left corner of the table at any additional page.
1091
1092 B<Value:> can be any whole number satisfying 0 < next_y < PageHeight
1093 B<Default:> Value of param B<'start_y'>
1094
1095     next_y  => 750
1096
1097 =item B<max_word_length> - Breaks long words (like serial numbers hashes etc.) by adding a space after every Nth symbol 
1098
1099 B<Value:> can be any whole positive number
1100 B<Default:> 20
1101
1102     max_word_length => 20    # Will add a space after every 20 symbols
1103
1104 =item B<padding> - Padding applied to every cell 
1105
1106 =item B<padding_top>    - top cell padding, overrides 'padding'
1107
1108 =item B<padding_right>  - right cell padding, overrides 'padding'
1109
1110 =item B<padding_left>   - left cell padding, overrides 'padding'
1111
1112 =item B<padding_bottom> - bottom padding, overrides 'padding'
1113
1114 B<Value:> can be any whole positive number
1115
1116 B<Default padding:> 0
1117
1118 B<Default padding_*> $padding
1119     
1120     padding        => 5      # all sides cell padding
1121     padding_top    => 8,     # top cell padding, overrides 'padding'
1122     padding_right  => 6,     # right cell padding, overrides 'padding'
1123     padding_left   => 2,     # left cell padding, overrides 'padding'
1124     padding_bottom => undef  # bottom padding will be 5 as it will fallback to 'padding'
1125
1126 =item B<border> - Width of table border lines. 
1127
1128 =item B<horizontal_borders> - Width of horizontal border lines. Overrides 'border' value.
1129
1130 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
1131
1132 B<Value:> can be any whole positive number. When set to 0 will disable border lines.
1133 B<Default:> 1
1134       
1135     border             => 3     # border width is 3
1136     horizontal_borders => 1     # horizontal borders will be 1 overriding 3
1137     vertical_borders   => undef # vertical borders will be 3 as it will fallback to 'border'
1138
1139 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
1140
1141 B<Value:> Color specifier as 'name' or 'HEX'
1142 B<Default:> 'black'
1143
1144     border_color => 'red'
1145
1146 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the table
1147
1148 B<Value:> can be any PDF::API2::Resource::* type of font
1149 B<Default:> 'Times' with UTF8 encoding
1150
1151     font => $pdf->corefont("Helvetica", -encoding => "utf8")
1152
1153 =item B<font_size> - Default size of the font that will be used across the table
1154
1155 B<Value:> can be any positive number
1156 B<Default:> 12
1157     
1158     font_size => 16
1159
1160 =item B<font_color> - Font color for all rows
1161
1162 =item B<font_color_odd> - Font color for odd rows
1163
1164 =item B<font_color_even> - Font color for even rows 
1165
1166 =item B<background_color_odd> - Background color for odd rows
1167
1168 =item B<background_color_even> - Background color for even rows
1169
1170 B<Value:> Color specifier as 'name' or 'HEX'
1171 B<Default:> 'black' font on 'white' background
1172
1173     font_color            => '#333333'
1174     font_color_odd        => 'purple'
1175     font_color_even       => '#00FF00'
1176     background_color_odd  => 'gray'     
1177     background_color_even => 'lightblue'
1178
1179 =item B<row_height> - Desired row height but it will be honored only if row_height > font_size + padding_top + padding_bottom
1180
1181 B<Value:> can be any whole positive number
1182 B<Default:> font_size + padding_top + padding_bottom
1183     
1184     row_height => 24
1185  
1186 =item B<new_page_func> - CODE reference to a function that returns a PDF::API2::Page instance.
1187
1188 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.
1189 For example you can use it to put Page Title, Page Frame, Page Numbers and other staff that you need.
1190 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.
1191 Don't forget that your function must return a page object created with PDF::API2 page() method.
1192
1193     new_page_func  => $code_ref
1194     
1195 =item B<header_props> - HASH reference to specific settings for the Header row of the table. See section L</Header Row Properties> below
1196     
1197     header_props => $hdr_props
1198
1199 =item B<column_props> - HASH reference to specific settings for each column of the table. See section L</Column Properties> below
1200
1201     column_props => $col_props
1202
1203 =item B<cell_props> - HASH reference to specific settings for each column of the table. See section L</Cell Properties> below
1204     
1205     cell_props => $cel_props
1206
1207 =back
1208
1209 =head4 Header Row Properties
1210
1211 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.
1212 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.
1213
1214 =over
1215
1216 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the header row
1217
1218 B<Value:> can be any PDF::API2::Resource::* type of font
1219 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1220
1221 =item B<font_size> - Font size of the header row
1222
1223 B<Value:> can be any positive number
1224 B<Default:> 'font_size' of the table + 2  
1225
1226 =item B<font_color> - Font color of the header row
1227
1228 B<Value:> Color specifier as 'name' or 'HEX'
1229 B<Default:> '#000066'
1230
1231 =item B<bg_color> - Background color of the header row
1232
1233 B<Value:> Color specifier as 'name' or 'HEX'
1234 B<Default:> #FFFFAA
1235
1236 =item B<repeat> - Flag showing if header row should be repeated on every new page
1237
1238 B<Value:> 0,1   1-Yes/True, 0-No/False 
1239 B<Default:> 0
1240
1241 =item B<justify> - Alignment of text in the header row.
1242
1243 B<Value:> One of 'left', 'right', 'center'
1244 B<Default:> Same as column alignment (or 'left' if undefined)
1245
1246     my $hdr_props = 
1247     {
1248         font       => $pdf->corefont("Helvetica", -encoding => "utf8"),
1249         font_size  => 18,
1250         font_color => '#004444',
1251         bg_color   => 'yellow', 
1252         repeat     => 1,    
1253         justify    => 'center'
1254     };
1255
1256 =back
1257
1258 =head4 Column Properties
1259
1260 If the 'column_props' parameter is used, it should be an arrayref of hashrefs, 
1261 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. 
1262 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.
1263
1264 Each hashref can contain any of the keys shown below:
1265
1266 =over
1267
1268 =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.
1269
1270 B<Value:> can be any whole number satisfying 0 < min_w < w
1271 B<Default:> Auto calculated
1272
1273 =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.
1274
1275 B<Value:> can be any whole number satisfying 0 < max_w < w
1276 B<Default:> Auto calculated
1277
1278 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this column
1279
1280 B<Value:> can be any PDF::API2::Resource::* type of font
1281 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1282
1283 =item B<font_size> - Font size of this column
1284
1285 B<Value:> can be any positive number
1286 B<Default:> 'font_size' of the table.
1287
1288 =item B<font_color> - Font color of this column
1289
1290 B<Value:> Color specifier as 'name' or 'HEX'
1291 B<Default:> 'font_color' of the table.
1292
1293 =item B<background_color> - Background color of this column
1294
1295 B<Value:> Color specifier as 'name' or 'HEX'
1296 B<Default:> undef
1297
1298 =item B<justify> - Alignment of text in this column
1299
1300 B<Value:> One of 'left', 'right', 'center'
1301 B<Default:> 'left'
1302
1303 Example:
1304
1305     my $col_props = [
1306         {},# This is an empty hash so the next one will hold the properties for the second column from left to right.
1307         {
1308             min_w => 100,       # Minimum column width of 100.
1309             max_w => 150,       # Maximum column width of 150 .
1310             justify => 'right', # Right text alignment
1311             font => $pdf->corefont("Helvetica", -encoding => "latin1"),
1312             font_size => 10,
1313             font_color=> 'blue',
1314             background_color => '#FFFF00',
1315         },
1316         # etc.
1317     ];
1318
1319 =back
1320
1321 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.
1322 When this happens a warning will be issued with some advises what can be done.
1323 In cases of a conflict between column formatting and odd/even row formatting, 'col_props' will override odd/even.
1324
1325 =head4 Cell Properties
1326
1327 If the 'cell_props' parameter is used, it should be an arrayref with arrays of hashrefs
1328 (of the same dimension as the data array) with one hashref for each cell of the table.
1329
1330 Each hashref can contain any of the keys shown below:
1331
1332 =over
1333
1334 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this cell
1335
1336 B<Value:> can be any PDF::API2::Resource::* type of font
1337 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1338
1339 =item B<font_size> - Font size of this cell
1340
1341 B<Value:> can be any positive number
1342 B<Default:> 'font_size' of the table.
1343
1344 =item B<font_color> - Font color of this cell
1345
1346 B<Value:> Color specifier as 'name' or 'HEX'
1347 B<Default:> 'font_color' of the table.
1348
1349 =item B<background_color> - Background color of this cell
1350
1351 B<Value:> Color specifier as 'name' or 'HEX'
1352 B<Default:> undef
1353
1354 =item B<justify> - Alignment of text in this cell
1355
1356 B<Value:> One of 'left', 'right', 'center'
1357 B<Default:> 'left'
1358
1359 Example:
1360
1361     my $cell_props = [
1362         [ #This array is for the first row. If header_props is defined it will overwrite these settings.
1363             {    #Row 1 cell 1
1364                 background_color => '#AAAA00',
1365                 font_color       => 'yellow',
1366             },
1367
1368             # etc.
1369         ],
1370         [#Row 2
1371             {    #Row 2 cell 1
1372                 background_color => '#CCCC00',
1373                 font_color       => 'blue',
1374             },
1375             {    #Row 2 cell 2
1376                 background_color => '#BBBB00',
1377                 font_color       => 'red',
1378             },
1379             # etc.
1380         ],
1381         # etc.
1382     ];
1383
1384     OR
1385     
1386     my $cell_props = [];
1387     $cell_props->[1][0] = {
1388         #Row 2 cell 1
1389         background_color => '#CCCC00',
1390         font_color       => 'blue',
1391     };
1392
1393 =back
1394     
1395 NOTE: In case of a conflict between column, odd/even and cell formating, cell formating will overwrite the other two.
1396 In case of a conflict between header row and cell formating, header formating will override cell.
1397
1398 =head2 text_block()
1399
1400     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = text_block( $txt, $data, %settings)
1401
1402 =over
1403
1404 =item Description
1405
1406 Utility method to create a block of text. The block may contain multiple paragraphs.
1407 It is mainly used internaly but you can use it from outside for placing formated text anywhere on the sheet.
1408
1409 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.
1410 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.
1411
1412 =item Parameters
1413
1414     $txt  - a PDF::API2::Page::Text instance representing the text tool
1415     $data - a string that will be placed inside the block
1416     %settings - HASH with geometry and formatting parameters.
1417      
1418 =item Reuturns
1419
1420 The return value is a 3 items list where 
1421
1422     $width_of_last_line - Width of last line in the block
1423     $final_y - The Y coordinate of the block bottom so that additional content can be added after it
1424     $left_over_text - Text that was did not fit in the provided box geometry.
1425     
1426 =item Example
1427
1428     # PDF::API2 objects
1429     my $page = $pdf->page;
1430     my $txt  = $page->text;
1431
1432     my %settings = (
1433         x => 10,
1434         y => 570,
1435         w => 220,
1436         h => 180
1437         
1438         #OPTIONAL PARAMS
1439         lead     => $font_size | $distance_between_lines,
1440         align    => "left|right|center|justify|fulljustify",
1441         hang     => $optional_hanging_indent,
1442         Only one of the subsequent 3params can be given. 
1443         They override each other.-parspace is the weightest
1444         parspace => $optional_vertical_space_before_first_paragraph,
1445         flindent => $optional_indent_of_first_line,
1446         fpindent => $optional_indent_of_first_paragraph,
1447         indent   => $optional_indent_of_text_to_every_non_first_line,
1448     );
1449     
1450     my ( $width_of_last_line, $final_y, $left_over_text ) = $pdftable->text_block( $txt, $data, %settings );
1451  
1452 =back
1453
1454 =head1 VERSION
1455
1456 0.9.7
1457
1458 =head1 AUTHOR
1459
1460 Daemmon Hughes
1461
1462 =head1 DEVELOPMENT
1463
1464 Further development since Ver: 0.02 - Desislav Kamenov
1465
1466 =head1 COPYRIGHT AND LICENSE
1467
1468 Copyright (C) 2006 by Daemmon Hughes, portions Copyright 2004 Stone
1469 Environmental Inc. (www.stone-env.com) All Rights Reserved.
1470
1471 This library is free software; you can redistribute it and/or modify
1472 it under the same terms as Perl itself, either Perl version 5.8.4 or,
1473 at your option, any later version of Perl 5 you may have available.
1474
1475 =head1 PLUGS
1476
1477 =over 
1478
1479 =item by Daemmon Hughes
1480
1481 Much of the work on this module was sponsered by
1482 Stone Environmental Inc. (www.stone-env.com).
1483
1484 The text_block() method is a slightly modified copy of the one from
1485 Rick Measham's PDF::API2 L<tutorial|http://rick.measham.id.au/pdf-api2>.
1486
1487 =item by Desislav Kamenov (@deskata on Twitter)
1488
1489 The development of this module was supported by SEEBURGER AG (www.seeburger.com) till year 2007
1490
1491 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
1492
1493 Thanks to all GitHub contributors!
1494
1495 =back
1496
1497 =head1 CONTRIBUTION
1498
1499 Hey PDF::Table is on GitHub. You are more than welcome to contribute!
1500
1501 https://github.com/kamenov/PDF-Table
1502
1503 =head1 SEE ALSO
1504
1505 L<PDF::API2>
1506
1507 =cut
1508