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