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