1fd50546c1bf600e24049c94c826e818f9d6f837
[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                 for my $idx (0 .. $header_props->{num_header_rows} - 1) {
588                   unshift @$data,           [ @{ $header_rows[$idx]      } ];
589                   unshift @$row_col_widths, [ @{ $header_row_widths[$idx] } ];
590                   unshift @$rows_height,    $header_row_heights[$idx];
591                 }
592                 $remaining_header_rows = $header_props->{num_header_rows};
593             }
594         }
595
596         # Check for safety reasons
597         if( $bot_marg < 0 )
598         {   # This warning should remain i think
599 #            carp "!!! Warning: !!! Incorrect Table Geometry! Setting bottom margin to end of sheet!\n";
600             $bot_marg = 0;
601         }
602
603         $gfx_bg = $page->gfx;
604         $txt = $page->text;
605         $txt->font($fnt_name, $fnt_size); 
606
607         $cur_y = $table_top_y;
608
609         if ($line_w)
610         {
611             $gfx = $page->gfx;
612             $gfx->strokecolor($border_color);
613             $gfx->linewidth($line_w);
614
615             # Draw the top line
616             if ($horiz_borders) 
617             {
618                 $gfx->move( $xbase , $cur_y );
619                 $gfx->hline($xbase + $width );
620             }
621         }
622         else
623         {
624             $gfx = undef;
625         }
626
627         # Each iteration adds a row to the current page until the page is full 
628         #  or there are no more rows to add
629         # Row_Loop
630         while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
631         {
632             # Remove the next item from $data
633             my $record = shift @{$data};
634             
635             # Get columns number to know later how many vertical lines to draw
636             # TODO: get the max number of columns per page as currently last row's columns overrides
637             $columns_number = scalar(@$record);
638
639             # Get the next set of row related settings
640             # Row Height
641             my $pre_calculated_row_height = shift @$rows_height;
642
643             # Row cell widths
644             my $record_widths = shift @$row_col_widths;
645
646             # Row coloumn props - TODO in another commit
647
648             # Row cell props - TODO in another commit
649
650             # Added to resolve infite loop bug with returned undef values
651             for(my $d = 0; $d < scalar(@{$record}) ; $d++)
652             { 
653                 $record->[$d] = '-' unless( defined $record->[$d]); 
654             }
655
656             # Choose colors for this row
657             $background_color = $row_index % 2 ? $background_color_even  : $background_color_odd;
658             $font_color       = $row_index % 2 ? $font_color_even        : $font_color_odd;
659
660             #Determine current row height
661             my $current_row_height = $pad_top + $pre_calculated_row_height + $pad_bot;
662
663             # $row_h is the calculated global user requested row height.
664             # It will be honored, only if it has bigger value than the calculated one.
665             # TODO: It's questionable if padding should be inclided in this calculation or not
666             if($current_row_height < $row_h){
667                 $current_row_height = $row_h;
668             }
669
670             # Define the font y base position for this line.
671             $text_start      = $cur_y - ($current_row_height - $pad_bot);
672
673             my $cur_x        = $xbase;
674             my $leftovers    = undef;   # Reference to text that is returned from textblock()
675             my $do_leftovers = 0;
676             my ($colspan, @vertical_lines);
677
678             # Process every cell(column) from current row
679             for( my $column_idx = 0; $column_idx < scalar( @$record); $column_idx++ ) 
680             {
681                 next unless $col_props->[$column_idx]->{'max_w'};
682                 next unless $col_props->[$column_idx]->{'min_w'};  
683                 $leftovers->[$column_idx] = undef;
684
685                 # look for font information for this cell
686                 my ($cell_font, $cell_font_size, $cell_font_color, $justify);
687                                     
688                 if( $remaining_header_rows and ref $header_props)
689                 {   
690                     $cell_font       = $header_props->{'font'};
691                     $cell_font_size  = $header_props->{'font_size'};
692                     $cell_font_color = $header_props->{'font_color'};
693                     $justify         = $header_props->{'justify'};
694                 }
695                 
696                 # Get the most specific value if none was already set from header_props
697                 $cell_font       ||= $cell_props->[$row_index][$column_idx]->{'font'} 
698                                  ||  $col_props->[$column_idx]->{'font'}
699                                  ||  $fnt_name;
700                                   
701                 $cell_font_size  ||= $cell_props->[$row_index][$column_idx]->{'font_size'}
702                                  ||  $col_props->[$column_idx]->{'font_size'}
703                                  ||  $fnt_size;
704                                   
705                 $cell_font_color ||= $cell_props->[$row_index][$column_idx]->{'font_color'}
706                                  ||  $col_props->[$column_idx]->{'font_color'}
707                                  ||  $font_color;
708                                 
709                 $justify         ||= $cell_props->[$row_index][$column_idx]->{'justify'}
710                                  ||  $col_props->[$column_idx]->{'justify'}
711                                  ||  $arg{'justify'}
712                                  ||  'left';                                    
713                 
714                 # Init cell font object
715                 $txt->font( $cell_font, $cell_font_size );
716                 $txt->fillcolor($cell_font_color);
717
718                 my $this_width;
719                 if (!$remaining_header_rows && $cell_props->[$row_index][$column_idx]->{colspan}) {
720                     $colspan = $cell_props->[$row_index][$column_idx]->{colspan};
721                 } elsif ($remaining_header_rows && $header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan}) {
722                     $colspan = $header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan};
723                 }
724
725                 if ($colspan) {
726                     $colspan     = $num_cols - $column_idx if (-1 == $colspan);
727                     my $last_idx = $column_idx + $colspan - 1;
728                     $this_width  = sum @{ $calc_column_widths }[$column_idx..$last_idx];
729                 } else {
730                     $this_width = $calc_column_widths->[$column_idx];
731                 }
732  
733                 # If the content is wider than the specified width, we need to add the text as a text block
734                 if( $record->[$column_idx] !~ m/(.\n.)/ and
735                     $record_widths->[$column_idx] and 
736                     $record_widths->[$column_idx] <= $this_width
737                 ){
738                     my $space = $pad_left;
739                     if ($justify eq 'right')
740                     {
741                         $space = $this_width -($txt->advancewidth($record->[$column_idx]) + $pad_right);
742                     }
743                     elsif ($justify eq 'center')
744                     {
745                         $space = ($this_width - $txt->advancewidth($record->[$column_idx])) / 2;
746                     }
747                     $txt->translate( $cur_x + $space, $text_start );
748                     $txt->text( $record->[$column_idx] );
749                 }
750                 # Otherwise just use the $page->text() method
751                 else
752                 {
753                     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
754                         $txt,
755                         $record->[$column_idx],
756                         x        => $cur_x + $pad_left,
757                         y        => $text_start,
758                         w        => $this_width - $pad_left - $pad_right,
759                         h        => $cur_y - $bot_marg - $pad_top - $pad_bot,
760                         align    => $justify,
761                         lead     => $lead
762                     );
763                     # Desi - Removed $lead because of fixed incorrect ypos bug in text_block
764                     my  $current_cell_height = $cur_y - $ypos_of_last_line + $pad_bot;
765                     if( $current_cell_height > $current_row_height )
766                     {
767                         $current_row_height = $current_cell_height;
768                     }
769                     
770                     if( $left_over_text )
771                     {
772                         $leftovers->[$column_idx] = $left_over_text;
773                         $do_leftovers = 1;
774                     }
775                 }
776                 $cur_x += $calc_column_widths->[$column_idx];
777
778                 push @vertical_lines, (!$colspan || (1 >= $colspan)) ? 1 : 0;
779                 $colspan-- if $colspan;
780             }
781             if( $do_leftovers )
782             {
783                 unshift @$data, $leftovers;
784                 unshift @$row_col_widths, $record_widths;
785                 unshift @$rows_height, $pre_calculated_row_height;
786             }
787             
788             # Draw cell bgcolor
789             # This has to be separately from the text loop 
790             #  because we do not know the final height of the cell until all text has been drawn
791             $cur_x = $xbase;
792             for(my $column_idx = 0 ; $column_idx < scalar(@$record) ; $column_idx++)
793             {
794                 my $cell_bg_color;
795                                     
796                 if( $remaining_header_rows and ref $header_props)
797                 {                                  #Compatibility                 Consistency with other props    
798                     $cell_bg_color = $header_props->{'bg_color'} || $header_props->{'background_color'};
799                 }
800                 
801                 # Get the most specific value if none was already set from header_props
802                 $cell_bg_color ||= $cell_props->[$row_index][$column_idx]->{'background_color'} 
803                                ||  $col_props->[$column_idx]->{'background_color'}
804                                ||  $background_color;
805
806                 if ($cell_bg_color)
807                 {
808                     $gfx_bg->rect( $cur_x, $cur_y-$current_row_height, $calc_column_widths->[$column_idx], $current_row_height);
809                     $gfx_bg->fillcolor($cell_bg_color);
810                     $gfx_bg->fill();
811                 }
812                 $cur_x += $calc_column_widths->[$column_idx];
813
814                 if ($line_w && $vertical_lines[$column_idx] && ($column_idx != (scalar(@{ $record }) - 1))) {
815                     $gfx->move($cur_x, $cur_y);
816                     $gfx->vline($cur_y - $current_row_height);
817                     $gfx->fillcolor($border_color);
818                 }
819             }#End of for(my $column_idx....
820
821             $cur_y -= $current_row_height;
822             if ($gfx && $horiz_borders)
823             {
824                 $gfx->move(  $xbase , $cur_y );
825                 $gfx->hline( $xbase + $width );
826             }
827
828             if ($remaining_header_rows) {
829               $remaining_header_rows--;
830             } else {
831               $row_index++ unless $do_leftovers;
832             }
833         }# End of Row_Loop
834
835         if ($gfx)
836         {
837             # Draw vertical lines
838             if ($vert_borders) 
839             {
840                 $gfx->move(  $xbase, $table_top_y);
841                 $gfx->vline( $cur_y );
842                 $gfx->move($xbase + sum(@{ $calc_column_widths }[0..$num_cols - 1]), $table_top_y);
843                 $gfx->vline( $cur_y );
844             }
845
846             # ACTUALLY draw all the lines
847             $gfx->fillcolor( $border_color);
848             $gfx->stroke;
849         }
850         $pg_cnt++;
851     }# End of while(scalar(@{$data}))
852
853     return ($page,--$pg_cnt,$cur_y);
854 }
855
856
857 # calculate the column widths
858 sub CalcColumnWidths
859 {
860     my $col_props   = shift;
861     my $avail_width = shift;
862     my $min_width   = 0;
863
864     my $calc_widths ;
865
866     for(my $j = 0; $j < scalar( @$col_props); $j++)
867     {
868         $min_width += $col_props->[$j]->{min_w} || 0;
869     }
870
871     # I think this is the optimal variant when good view can be guaranateed
872     if($avail_width < $min_width)
873     {
874         carp "!!! Warning !!!\n Calculated Mininal width($min_width) > Table width($avail_width).\n",
875             ' Expanding table width to:',int($min_width)+1,' but this could lead to unexpected results.',"\n",
876             ' Possible solutions:',"\n",
877             '  0)Increase table width.',"\n",
878             '  1)Decrease font size.',"\n",
879             '  2)Choose a more narrow font.',"\n",
880             '  3)Decrease "max_word_length" parameter.',"\n",
881             '  4)Rotate page to landscape(if it is portrait).',"\n",
882             '  5)Use larger paper size.',"\n",
883             '!!! --------- !!!',"\n";
884         $avail_width = int( $min_width) + 1;
885
886     }
887
888     # Calculate how much can be added to every column to fit the available width.
889     for(my $j = 0; $j < scalar(@$col_props); $j++ )
890     {
891         $calc_widths->[$j] = $col_props->[$j]->{min_w} || 0;;
892     }
893
894     # Allow columns to expand to max_w before applying extra space equally.
895     my $is_last_iter;
896     for (;;)
897     {
898         my $span = ($avail_width - $min_width) / scalar( @$col_props);
899         last if $span <= 0;
900
901         $min_width = 0;
902         my $next_will_be_last_iter = 1;
903         for(my $j = 0; $j < scalar(@$col_props); $j++ )
904         {
905             my $new_w = $calc_widths->[$j] + $span;
906
907             if (!$is_last_iter && $new_w > $col_props->[$j]->{max_w})
908             {
909                 $new_w = $col_props->[$j]->{max_w}
910             }
911             if ($calc_widths->[$j] != $new_w )
912             {
913                 $calc_widths->[$j] = $new_w;
914                 $next_will_be_last_iter = 0;
915             }
916             $min_width += $new_w;
917         }
918         last if $is_last_iter;
919         $is_last_iter = $next_will_be_last_iter;
920     }
921
922     return ($calc_widths,$avail_width);
923 }
924 1;
925
926 __END__
927
928 =pod
929
930 =head1 NAME
931
932 PDF::Table - A utility class for building table layouts in a PDF::API2 object.
933
934 =head1 SYNOPSIS
935
936  use PDF::API2;
937  use PDF::Table;
938
939  my $pdftable = new PDF::Table;
940  my $pdf = new PDF::API2(-file => "table_of_lorem.pdf");
941  my $page = $pdf->page;
942
943  # some data to layout
944  my $some_data =[
945     ["1 Lorem ipsum dolor",
946     "Donec odio neque, faucibus vel",
947     "consequat quis, tincidunt vel, felis."],
948     ["Nulla euismod sem eget neque.",
949     "Donec odio neque",
950     "Sed eu velit."],
951     #... and so on
952  ];
953
954  $left_edge_of_table = 50;
955  # build the table layout
956  $pdftable->table(
957      # required params
958      $pdf,
959      $page,
960      $some_data,
961      x => $left_edge_of_table,
962      w => 495,
963      start_y => 500,
964      start_h => 300,
965      # some optional params
966      next_y  => 750,
967      next_h  => 500,
968      padding => 5,
969      padding_right => 10,
970      background_color_odd  => "gray",
971      background_color_even => "lightblue", #cell background color for even rows
972   );
973
974  # do other stuff with $pdf
975  $pdf->saveas();
976 ...
977
978 =head1 EXAMPLE
979
980 For a complete working example or initial script look into distribution`s 'examples' folder.
981
982
983 =head1 DESCRIPTION
984
985 This class is a utility for use with the PDF::API2 module from CPAN. 
986 It can be used to display text data in a table layout within a PDF. 
987 The text data must be in a 2D array (such as returned by a DBI statement handle fetchall_arrayref() call). 
988 The PDF::Table will automatically add as many new pages as necessary to display all of the data. 
989 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. 
990 Also a (non)repeated header row with different layout properties can be specified. 
991
992 See the L</METHODS> section for complete documentation of every parameter.
993
994 =head1 METHODS
995
996 =head2 new()
997
998     my $pdf_table = new PDF::Table;
999
1000 =over
1001
1002 =item Description
1003
1004 Creates a new instance of the class. (to be improved)
1005
1006 =item Parameters
1007
1008 There are no parameters. 
1009
1010 =item Returns
1011
1012 Reference to the new instance
1013
1014 =back
1015
1016 =head2 table()
1017
1018     my ($final_page, $number_of_pages, $final_y) = table($pdf, $page, $data, %settings)
1019     
1020 =over
1021
1022 =item Description
1023
1024 Generates a multi-row, multi-column table into an existing PDF document based on provided data set and settings.
1025
1026 =item Parameters
1027
1028     $pdf      - a PDF::API2 instance representing the document being created
1029     $page     - a PDF::API2::Page instance representing the current page of the document
1030     $data     - an ARRAY reference to a 2D data structure that will be used to build the table
1031     %settings - HASH with geometry and formatting parameters. 
1032
1033 For full %settings description see section L</Table settings> below.
1034
1035 This method will add more pages to the pdf instance as required based on the formatting options and the amount of data.
1036
1037 =item Reuturns
1038
1039 The return value is a 3 items list where 
1040
1041     $final_page - The first item is a PDF::API2::Page instance that the table ends on
1042     $number_of_pages - The second item is the count of pages that the table spans on
1043     $final_y - The third item is the Y coordinate of the table bottom so that additional content can be added in the same document.
1044
1045 =item Example
1046
1047     my $pdf  = new PDF::API2;
1048     my $page = $pdf->page();
1049     my $data = [
1050         ['foo1','bar1','baz1'],
1051         ['foo2','bar2','baz2']
1052     ];
1053     my %settings = (
1054         x       => 10,
1055         w       => 570,
1056         start_y => 220,
1057         start_h => 180,
1058     );
1059     
1060     my ($final_page, $number_of_pages, $final_y) = $pdftable->table( $pdf, $page, $data, %options );
1061
1062 =back
1063
1064 =head3 Table settings
1065
1066 =head4 Mandatory
1067
1068 There are some mandatory parameteres for setting table geometry and position across page(s)
1069
1070 =over 
1071
1072 =item B<x> - X coordinate of upper left corner of the table. Left edge of the sheet is 0.
1073
1074 B<Value:> can be any whole number satisfying 0 =< X < PageWidth
1075 B<Default:> No default value 
1076
1077     x => 10
1078
1079 =item B<start_y> - Y coordinate of upper left corner of the table at the initial page.
1080
1081 B<Value:> can be any whole number satisfying 0 < start_y < PageHeight (depending on space availability when embedding a table)
1082 B<Default:> No default value
1083
1084     start_y => 327
1085
1086 =item B<w> - width of the table starting from X.
1087
1088 B<Value:> can be any whole number satisfying 0 < w < PageWidth - x
1089 B<Default:> No default value
1090
1091     w  => 570
1092
1093 =item B<start_h> - Height of the table on the initial page
1094
1095 B<Value:> can be any whole number satisfying 0 < start_h < PageHeight - Current Y position
1096 B<Default:> No default value
1097
1098     start_h => 250
1099     
1100 =back
1101
1102 =head4 Optional
1103
1104 =over
1105
1106 =item B<next_h> - Height of the table on any additional page
1107
1108 B<Value:> can be any whole number satisfying 0 < next_h < PageHeight
1109 B<Default:> Value of param B<'start_h'>
1110
1111     next_h  => 700
1112
1113 =item B<next_y> - Y coordinate of upper left corner of the table at any additional page.
1114
1115 B<Value:> can be any whole number satisfying 0 < next_y < PageHeight
1116 B<Default:> Value of param B<'start_y'>
1117
1118     next_y  => 750
1119
1120 =item B<max_word_length> - Breaks long words (like serial numbers hashes etc.) by adding a space after every Nth symbol 
1121
1122 B<Value:> can be any whole positive number
1123 B<Default:> 20
1124
1125     max_word_length => 20    # Will add a space after every 20 symbols
1126
1127 =item B<padding> - Padding applied to every cell 
1128
1129 =item B<padding_top>    - top cell padding, overrides 'padding'
1130
1131 =item B<padding_right>  - right cell padding, overrides 'padding'
1132
1133 =item B<padding_left>   - left cell padding, overrides 'padding'
1134
1135 =item B<padding_bottom> - bottom padding, overrides 'padding'
1136
1137 B<Value:> can be any whole positive number
1138
1139 B<Default padding:> 0
1140
1141 B<Default padding_*> $padding
1142     
1143     padding        => 5      # all sides cell padding
1144     padding_top    => 8,     # top cell padding, overrides 'padding'
1145     padding_right  => 6,     # right cell padding, overrides 'padding'
1146     padding_left   => 2,     # left cell padding, overrides 'padding'
1147     padding_bottom => undef  # bottom padding will be 5 as it will fallback to 'padding'
1148
1149 =item B<border> - Width of table border lines. 
1150
1151 =item B<horizontal_borders> - Width of horizontal border lines. Overrides 'border' value.
1152
1153 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
1154
1155 B<Value:> can be any whole positive number. When set to 0 will disable border lines.
1156 B<Default:> 1
1157       
1158     border             => 3     # border width is 3
1159     horizontal_borders => 1     # horizontal borders will be 1 overriding 3
1160     vertical_borders   => undef # vertical borders will be 3 as it will fallback to 'border'
1161
1162 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
1163
1164 B<Value:> Color specifier as 'name' or 'HEX'
1165 B<Default:> 'black'
1166
1167     border_color => 'red'
1168
1169 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the table
1170
1171 B<Value:> can be any PDF::API2::Resource::* type of font
1172 B<Default:> 'Times' with UTF8 encoding
1173
1174     font => $pdf->corefont("Helvetica", -encoding => "utf8")
1175
1176 =item B<font_size> - Default size of the font that will be used across the table
1177
1178 B<Value:> can be any positive number
1179 B<Default:> 12
1180     
1181     font_size => 16
1182
1183 =item B<font_color> - Font color for all rows
1184
1185 =item B<font_color_odd> - Font color for odd rows
1186
1187 =item B<font_color_even> - Font color for even rows 
1188
1189 =item B<background_color_odd> - Background color for odd rows
1190
1191 =item B<background_color_even> - Background color for even rows
1192
1193 B<Value:> Color specifier as 'name' or 'HEX'
1194 B<Default:> 'black' font on 'white' background
1195
1196     font_color            => '#333333'
1197     font_color_odd        => 'purple'
1198     font_color_even       => '#00FF00'
1199     background_color_odd  => 'gray'     
1200     background_color_even => 'lightblue'
1201
1202 =item B<row_height> - Desired row height but it will be honored only if row_height > font_size + padding_top + padding_bottom
1203
1204 B<Value:> can be any whole positive number
1205 B<Default:> font_size + padding_top + padding_bottom
1206     
1207     row_height => 24
1208  
1209 =item B<new_page_func> - CODE reference to a function that returns a PDF::API2::Page instance.
1210
1211 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.
1212 For example you can use it to put Page Title, Page Frame, Page Numbers and other staff that you need.
1213 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.
1214 Don't forget that your function must return a page object created with PDF::API2 page() method.
1215
1216     new_page_func  => $code_ref
1217     
1218 =item B<header_props> - HASH reference to specific settings for the Header row of the table. See section L</Header Row Properties> below
1219     
1220     header_props => $hdr_props
1221
1222 =item B<column_props> - HASH reference to specific settings for each column of the table. See section L</Column Properties> below
1223
1224     column_props => $col_props
1225
1226 =item B<cell_props> - HASH reference to specific settings for each column of the table. See section L</Cell Properties> below
1227     
1228     cell_props => $cel_props
1229
1230 =back
1231
1232 =head4 Header Row Properties
1233
1234 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.
1235 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.
1236
1237 =over
1238
1239 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the header row
1240
1241 B<Value:> can be any PDF::API2::Resource::* type of font
1242 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1243
1244 =item B<font_size> - Font size of the header row
1245
1246 B<Value:> can be any positive number
1247 B<Default:> 'font_size' of the table + 2  
1248
1249 =item B<font_color> - Font color of the header row
1250
1251 B<Value:> Color specifier as 'name' or 'HEX'
1252 B<Default:> '#000066'
1253
1254 =item B<bg_color> - Background color of the header row
1255
1256 B<Value:> Color specifier as 'name' or 'HEX'
1257 B<Default:> #FFFFAA
1258
1259 =item B<repeat> - Flag showing if header row should be repeated on every new page
1260
1261 B<Value:> 0,1   1-Yes/True, 0-No/False 
1262 B<Default:> 0
1263
1264 =item B<justify> - Alignment of text in the header row.
1265
1266 B<Value:> One of 'left', 'right', 'center'
1267 B<Default:> Same as column alignment (or 'left' if undefined)
1268
1269     my $hdr_props = 
1270     {
1271         font       => $pdf->corefont("Helvetica", -encoding => "utf8"),
1272         font_size  => 18,
1273         font_color => '#004444',
1274         bg_color   => 'yellow', 
1275         repeat     => 1,    
1276         justify    => 'center'
1277     };
1278
1279 =back
1280
1281 =head4 Column Properties
1282
1283 If the 'column_props' parameter is used, it should be an arrayref of hashrefs, 
1284 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. 
1285 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.
1286
1287 Each hashref can contain any of the keys shown below:
1288
1289 =over
1290
1291 =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.
1292
1293 B<Value:> can be any whole number satisfying 0 < min_w < w
1294 B<Default:> Auto calculated
1295
1296 =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.
1297
1298 B<Value:> can be any whole number satisfying 0 < max_w < w
1299 B<Default:> Auto calculated
1300
1301 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this column
1302
1303 B<Value:> can be any PDF::API2::Resource::* type of font
1304 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1305
1306 =item B<font_size> - Font size of this column
1307
1308 B<Value:> can be any positive number
1309 B<Default:> 'font_size' of the table.
1310
1311 =item B<font_color> - Font color of this column
1312
1313 B<Value:> Color specifier as 'name' or 'HEX'
1314 B<Default:> 'font_color' of the table.
1315
1316 =item B<background_color> - Background color of this column
1317
1318 B<Value:> Color specifier as 'name' or 'HEX'
1319 B<Default:> undef
1320
1321 =item B<justify> - Alignment of text in this column
1322
1323 B<Value:> One of 'left', 'right', 'center'
1324 B<Default:> 'left'
1325
1326 Example:
1327
1328     my $col_props = [
1329         {},# This is an empty hash so the next one will hold the properties for the second column from left to right.
1330         {
1331             min_w => 100,       # Minimum column width of 100.
1332             max_w => 150,       # Maximum column width of 150 .
1333             justify => 'right', # Right text alignment
1334             font => $pdf->corefont("Helvetica", -encoding => "latin1"),
1335             font_size => 10,
1336             font_color=> 'blue',
1337             background_color => '#FFFF00',
1338         },
1339         # etc.
1340     ];
1341
1342 =back
1343
1344 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.
1345 When this happens a warning will be issued with some advises what can be done.
1346 In cases of a conflict between column formatting and odd/even row formatting, 'col_props' will override odd/even.
1347
1348 =head4 Cell Properties
1349
1350 If the 'cell_props' parameter is used, it should be an arrayref with arrays of hashrefs
1351 (of the same dimension as the data array) with one hashref for each cell of the table.
1352
1353 Each hashref can contain any of the keys shown below:
1354
1355 =over
1356
1357 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this cell
1358
1359 B<Value:> can be any PDF::API2::Resource::* type of font
1360 B<Default:> 'font' of the table. See table parameter 'font' for more details.
1361
1362 =item B<font_size> - Font size of this cell
1363
1364 B<Value:> can be any positive number
1365 B<Default:> 'font_size' of the table.
1366
1367 =item B<font_color> - Font color of this cell
1368
1369 B<Value:> Color specifier as 'name' or 'HEX'
1370 B<Default:> 'font_color' of the table.
1371
1372 =item B<background_color> - Background color of this cell
1373
1374 B<Value:> Color specifier as 'name' or 'HEX'
1375 B<Default:> undef
1376
1377 =item B<justify> - Alignment of text in this cell
1378
1379 B<Value:> One of 'left', 'right', 'center'
1380 B<Default:> 'left'
1381
1382 Example:
1383
1384     my $cell_props = [
1385         [ #This array is for the first row. If header_props is defined it will overwrite these settings.
1386             {    #Row 1 cell 1
1387                 background_color => '#AAAA00',
1388                 font_color       => 'yellow',
1389             },
1390
1391             # etc.
1392         ],
1393         [#Row 2
1394             {    #Row 2 cell 1
1395                 background_color => '#CCCC00',
1396                 font_color       => 'blue',
1397             },
1398             {    #Row 2 cell 2
1399                 background_color => '#BBBB00',
1400                 font_color       => 'red',
1401             },
1402             # etc.
1403         ],
1404         # etc.
1405     ];
1406
1407     OR
1408     
1409     my $cell_props = [];
1410     $cell_props->[1][0] = {
1411         #Row 2 cell 1
1412         background_color => '#CCCC00',
1413         font_color       => 'blue',
1414     };
1415
1416 =back
1417     
1418 NOTE: In case of a conflict between column, odd/even and cell formating, cell formating will overwrite the other two.
1419 In case of a conflict between header row and cell formating, header formating will override cell.
1420
1421 =head2 text_block()
1422
1423     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = text_block( $txt, $data, %settings)
1424
1425 =over
1426
1427 =item Description
1428
1429 Utility method to create a block of text. The block may contain multiple paragraphs.
1430 It is mainly used internaly but you can use it from outside for placing formated text anywhere on the sheet.
1431
1432 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.
1433 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.
1434
1435 =item Parameters
1436
1437     $txt  - a PDF::API2::Page::Text instance representing the text tool
1438     $data - a string that will be placed inside the block
1439     %settings - HASH with geometry and formatting parameters.
1440      
1441 =item Reuturns
1442
1443 The return value is a 3 items list where 
1444
1445     $width_of_last_line - Width of last line in the block
1446     $final_y - The Y coordinate of the block bottom so that additional content can be added after it
1447     $left_over_text - Text that was did not fit in the provided box geometry.
1448     
1449 =item Example
1450
1451     # PDF::API2 objects
1452     my $page = $pdf->page;
1453     my $txt  = $page->text;
1454
1455     my %settings = (
1456         x => 10,
1457         y => 570,
1458         w => 220,
1459         h => 180
1460         
1461         #OPTIONAL PARAMS
1462         lead     => $font_size | $distance_between_lines,
1463         align    => "left|right|center|justify|fulljustify",
1464         hang     => $optional_hanging_indent,
1465         Only one of the subsequent 3params can be given. 
1466         They override each other.-parspace is the weightest
1467         parspace => $optional_vertical_space_before_first_paragraph,
1468         flindent => $optional_indent_of_first_line,
1469         fpindent => $optional_indent_of_first_paragraph,
1470         indent   => $optional_indent_of_text_to_every_non_first_line,
1471     );
1472     
1473     my ( $width_of_last_line, $final_y, $left_over_text ) = $pdftable->text_block( $txt, $data, %settings );
1474  
1475 =back
1476
1477 =head1 VERSION
1478
1479 0.9.7
1480
1481 =head1 AUTHOR
1482
1483 Daemmon Hughes
1484
1485 =head1 DEVELOPMENT
1486
1487 Further development since Ver: 0.02 - Desislav Kamenov
1488
1489 =head1 COPYRIGHT AND LICENSE
1490
1491 Copyright (C) 2006 by Daemmon Hughes, portions Copyright 2004 Stone
1492 Environmental Inc. (www.stone-env.com) All Rights Reserved.
1493
1494 This library is free software; you can redistribute it and/or modify
1495 it under the same terms as Perl itself, either Perl version 5.8.4 or,
1496 at your option, any later version of Perl 5 you may have available.
1497
1498 =head1 PLUGS
1499
1500 =over 
1501
1502 =item by Daemmon Hughes
1503
1504 Much of the work on this module was sponsered by
1505 Stone Environmental Inc. (www.stone-env.com).
1506
1507 The text_block() method is a slightly modified copy of the one from
1508 Rick Measham's PDF::API2 L<tutorial|http://rick.measham.id.au/pdf-api2>.
1509
1510 =item by Desislav Kamenov (@deskata on Twitter)
1511
1512 The development of this module was supported by SEEBURGER AG (www.seeburger.com) till year 2007
1513
1514 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
1515
1516 Thanks to all GitHub contributors!
1517
1518 =back
1519
1520 =head1 CONTRIBUTION
1521
1522 Hey PDF::Table is on GitHub. You are more than welcome to contribute!
1523
1524 https://github.com/kamenov/PDF-Table
1525
1526 =head1 SEE ALSO
1527
1528 L<PDF::API2>
1529
1530 =cut
1531