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