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