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