File Coverage

blib/lib/PostScript/Simple/Table.pm
Criterion Covered Total %
statement 15 279 5.3
branch 0 108 0.0
condition 0 65 0.0
subroutine 5 11 45.4
pod 3 6 50.0
total 23 469 4.9


line stmt bran cond sub pod time code
1             package PostScript::Simple::Table;
2              
3 1     1   20007 use 5.006;
  1         3  
  1         31  
4 1     1   4 use strict;
  1         1  
  1         29  
5 1     1   5 use warnings;
  1         5  
  1         28  
6 1     1   1099 use PostScript::Simple;
  1         12333  
  1         73  
7 1     1   1148 use PostScript::Metrics;
  1         4826  
  1         2666  
8              
9             our $VERSION = '1.02';
10              
11             sub new
12             {
13 0     0 1   my ($proto) = @_;
14            
15 0   0       my $class = ref($proto) || $proto;
16 0           my $self = {};
17 0           bless ($self, $class);
18 0           return $self;
19             }
20              
21             ############################################################
22             #
23             # text_block - utility method to build multi-paragraph blocks of text
24             #
25             # ($width_of_last_line, $ypos_of_last_line, $left_over_text) = text_block(
26             # $post_script_handler,
27             # $text_to_place,
28             # -x => $left_edge_of_block,
29             # -y => $baseline_of_first_line,
30             # -w => $width_of_block,
31             # -h => $height_of_block,
32             # [-lead => $font_size * 1.2 | $distance_between_lines,]
33             # [-parspace => 0 | $extra_distance_between_paragraphs,]
34             # [-align => "left|right|center|justify|fulljustify",]
35             # [-hang => $optional_hanging_indent,]
36             #);
37             #
38             ############################################################
39              
40             sub text_block {
41 0     0 1   my $self = shift;
42 0           my $ps = shift;
43 0           my $text = shift;
44 0           my %arg = @_;
45            
46 0           my($align,$width,$ypos,$xpos,$line_width,$wordspace, $endw) = (undef,undef,undef,undef,undef,undef,undef,undef);
47 0           my @line = undef;
48 0           my %width = undef;
49             # Get the text in paragraphs
50 0           my @paragraphs = split(/\n/, $text);
51              
52 0   0       $arg{'-lead'} ||= 14;
53            
54             # calculate width of all words
55 0           my $space_width = $self->getStringWidth("\x20");
56 0           my @words = split(/\s+/, $text);
57              
58 0           foreach (@words) {
59 0 0         next if exists $width{$_};
60 0           $width{$_} = $self->getStringWidth($_);
61             }
62              
63 0           $ypos = $arg{'-y'};
64 0           my @paragraph = split(/ /, shift(@paragraphs));
65 0           my $first_line = 1;
66 0           my $first_paragraph = 1;
67              
68             # while we can add another line
69 0           while ( $ypos >= $arg{'-y'} - $arg{'-h'} + $arg{'-lead'} ) {
70              
71 0 0         unless (@paragraph) {
72 0 0         last unless scalar @paragraphs;
73 0           @paragraph = split(/ /, shift(@paragraphs));
74              
75 0 0         $ypos -= $arg{'-parspace'} if $arg{'-parspace'};
76 0 0         last unless $ypos >= $arg{'-y'} - $arg{'-h'};
77 0           $first_line = 1;
78 0           $first_paragraph = 0;
79             }
80              
81 0           $xpos = $arg{'-x'};
82              
83             # while there's room on the line, add another word
84 0           @line = ();
85              
86 0           $line_width =0;
87 0 0 0       if ($first_line && exists $arg{'-hang'}) {
    0 0        
    0 0        
    0          
88 0           my $hang_width = $self->getStringWidth($arg{'-hang'});
89            
90 0           $ps->text($xpos, $ypos, $arg{'-hang'});
91            
92 0           $xpos += $hang_width;
93 0           $line_width += $hang_width;
94 0 0         $arg{'-indent'} += $hang_width if $first_paragraph;
95             } elsif ($first_line && exists $arg{'-flindent'}) {
96 0           $xpos += $arg{'-flindent'};
97 0           $line_width += $arg{'-flindent'};
98             } elsif ($first_paragraph && exists $arg{'-fpindent'}) {
99 0           $xpos += $arg{'-fpindent'};
100 0           $line_width += $arg{'-fpindent'};
101             } elsif (exists $arg{'-indent'}) {
102 0           $xpos += $arg{'-indent'};
103 0           $line_width += $arg{'-indent'};
104             }
105            
106 0   0       while ( @paragraph and $self->getStringWidth(join("\x20", @line)."\x20".$paragraph[0])+$line_width < $arg{'-w'} ) {
107 0           push(@line, shift(@paragraph));
108             }
109 0           $line_width += $self->getStringWidth(join('', @line));
110            
111             # calculate the space width
112 0 0 0       if ($arg{'-align'} eq 'fulljustify' or ($arg{'-align'} eq 'justify' and @paragraph)) {
      0        
113 0 0         if (scalar(@line) == 1) {
114 0           @line = split(//,$line[0]);
115             }
116 0           $wordspace = ($arg{'-w'} - $line_width) / (scalar(@line) - 1);
117 0           $align='justify';
118             } else {
119 0 0         $align=($arg{'-align'} eq 'justify') ? 'left' : $arg{'-align'};
120 0           $wordspace = $space_width;
121             }
122 0           $line_width += $wordspace * (scalar(@line) - 1);
123              
124 0 0         if ($align eq 'justify') {
125 0           foreach my $word (@line) {
126 0           $ps->text($xpos, $ypos, $word);
127 0 0         $xpos += ($width{$word} + $wordspace) if (@line);
128             }
129 0           $endw = $arg{'-w'};
130             } else {
131             # calculate the left hand position of the line
132 0 0         if ($align eq 'right') {
    0          
133 0           $xpos += $arg{'-w'} - $line_width;
134             } elsif ($align eq 'center') {
135 0           $xpos += ($arg{'-w'}/2) - ($line_width / 2);
136             }
137              
138             # render the line
139 0           $endw = $ps->text($xpos, $ypos, join("\x20", @line));
140             }
141 0           $ypos -= $arg{'-lead'};
142 0           $first_line = 0;
143             }
144 0 0         unshift(@paragraphs, join(' ',@paragraph)) if scalar(@paragraph);
145 0           return ($endw, $ypos, join("\n", @paragraphs))
146             }
147              
148              
149             ############################################################
150             #
151             # table - utility method to build multi-row, multicolumn tables
152             #
153             # ($page,$pg_cnt,$cur_y) = table(
154             # $pdf_object,
155             # $page_object_to_start_on,
156             # $table_data, # an arrayref of arrayrefs
157             # -x => $left_edge_of_table,
158             # -start_y => $baseline_of_first_line_on_first_page,
159             # -next_y => $baseline_of_first_line_on_succeeding_pages,
160             # -start_h => $baseline_of_first_line_on_first_page,
161             # -next_h => $baseline_of_first_line_on_succeeding_pages,
162             # [-w => $table_width,] # technically optional, but almost always a good idea to use
163             # [-row_height => $min_row_height,] # minimum height of row
164             # [-padding => $cellpadding,] # default 0,
165             # [-padding_left => $leftpadding,] # overides -padding
166             # [-padding_right => $rightpadding,] # overides -padding
167             # [-padding_top => $toppadding,] # overides -padding
168             # [-padding_bottom => $bottompadding,] # overides -padding
169             # [-border => $border width,] # default 1, use 0 for no border
170             # [-border_color => $border_color,] # default black
171             # [-font => $pdf->corefont,] # default $pdf->corefont('Times',-encode => 'latin1')
172             # [-font_size => $font_sizwe,] # default 12
173             # [-font_color => font_color,] # font color
174             # [-font_color_odd => font_color_odd,] # font color for odd rows
175             # [-font_color_even => font_color_odd,] # font color for odd rows
176             # [-background_color => 'gray',] # cell background color
177             # [-background_color_odd => $background_color_odd,] # cell background color for odd rows
178             # [-background_color_even => $background_color_even,] # cell background color for even rows
179             # [-column_props => [
180             # {width => $col_a_width, # width of column
181             # justify => 'left'|'right', # text justify in cell
182             # font => $pdf->corefont, # font for this column
183             # font_size => $col_a_font_size, # font size for this column
184             # font_color => $col_a_font_color, # font color for this column
185             # background_color => $col_a_background_color # background color for this column
186             # },
187             # ...
188             # ]
189             # ]
190             # # column_props is an arrayref of hashrefs, where each hashref sets properties for a column in the table.
191             # # -All keys in the hashref are optional, with one caveat in the case of 'width'. See below.
192             # # -If used, there should be one hashref for each column, even if it is an empty hashref
193             # # -Column_props take precendence over general or odd/even row properties
194             # # -If using the 'width' property, it is required for all columns and the total of all column widths should
195             # # be equal to the -w parameter (overall table width). In other words, if you are going to set individual column widths,
196             # # set them accurately with respect to overall table width, otherwise behavior will be unpredictable.
197             # # This is a current limitation, not a feature :-)
198             #);
199             #
200             ############################################################
201             sub table {
202 0     0 1   my $self = shift;
203 0           my $ps = shift;
204 0           my $data = shift;
205 0           my %arg = @_;
206            
207             # set default properties
208 0   0       my $fnt_name = $arg{'-font'} || 'Helvetica';
209 0   0       my $fnt_size = $arg{'-font-size'} || 12;
210 0           $ps->setfont($fnt_name,$fnt_size);
211            
212 0   0       my $lead = $arg{'-lead'} || $fnt_size;
213 0   0       my $pad_left = $arg{'-padding_left'} || $arg{'-padding'} || 0;
214 0   0       my $pad_right = $arg{'-padding_right'} || $arg{'-padding'} || 0;
215 0   0       my $pad_top = $arg{'-padding_top'} || $arg{'-padding'} || 0;
216 0   0       my $pad_bot = $arg{'-padding_bottom'} || $arg{'-padding'} || 0;
217 0           my $pad_w = $pad_left+$pad_right;
218 0           my $pad_h = $pad_top+$pad_bot;
219 0 0         my $line_w = defined $arg{'-border'}? $arg{'-border'}:1;
220 0 0 0       my $min_row_h = defined ($arg{'-row_height'}) && ($arg{'-row_height'} > ($fnt_size + $pad_top + $pad_bot))? $arg{'-row_height'}:$fnt_size + $pad_top + $pad_bot;
221 0           my $row_h = $min_row_h;
222 0           my $pg_cnt = 1;
223 0           my $cur_y = $arg{'-start_y'};
224              
225             # Sort out colors
226            
227 0           my @border_color = (0,0,0);
228 0 0         if ($self->getColor($arg{'-border_color'})) {
229 0           @border_color = $self->getColor($arg{'-border_color'});
230             }
231              
232 0           my @background_color_even = undef;
233 0 0         if ($self->getColor($arg{'-background_color_even'})) {
234 0           @background_color_even = $self->getColor($arg{'-background_color_even'});
235             } else {
236 0           @background_color_even = $self->getColor($arg{'-background_color'});
237             }
238              
239 0           my @background_color_odd = undef;
240 0 0         if ($self->getColor($arg{'-background_color_odd'})) {
241 0           @background_color_odd = $self->getColor($arg{'-background_color_odd'});
242             } else {
243 0           @background_color_odd = $self->getColor($arg{'-background_color'});
244             }
245              
246 0           my @font_color_even = (0,0,0);
247 0 0         if ($self->getColor($arg{'-font_color_even'})) {
248 0           @font_color_even = $self->getColor($arg{'-font_color_even'});
249             } else {
250 0           @font_color_even = $self->getColor($arg{'-font_color'});
251             }
252              
253 0           my @font_color_odd = (0,0,0);
254 0 0         if ($self->getColor($arg{'-font_color_odd'})) {
255 0           @font_color_odd = $self->getColor($arg{'-font_color_odd'});
256             } else {
257 0           @font_color_odd = $self->getColor($arg{'-font_color'});
258             }
259              
260             # Build the table
261 0 0         if(ref $data) {
262              
263             # determine column widths based on content
264 0   0       my $col_props = $arg{'-column_props'} || []; # a arrayref whose values are a hashref holding the minimum and maximum width of that column
265 0           my $row_props = []; # an array ref of arrayrefs whose values are the actual widths of the column/row intersection
266 0           my ($total_max_w,$total_min_w) = (0,0); # scalars that hold sum of the maximum and minimum widths of all columns
267 0           my ($max_col_w,$min_col_w) = (0,0);
268 0           my $word_w = {};
269 0           my ($row,$col_name,$col_fnt_size,$space_w);
270 0           my $rcnt = 0;
271 0           foreach $row (@$data) {
272 0           my $foo = []; #holds the widths of each column
273 0           for(my $j =0;$j < scalar(@$row);$j++) {
274              
275             # look for font information for this column
276 0 0         $col_fnt_size = $col_props->[$j]->{'font_size'}? $col_props->[$j]->{'font_size'}:$fnt_size;
277 0 0         if($col_props->[$j]->{'font'}) {
278 0           $ps->setfont($col_props->[$j]->{'font'},$col_fnt_size);
279             } else {
280 0           $ps->setfont($fnt_name,$col_fnt_size);
281             }
282 0           $space_w = $self->getStringWidth("\x20");
283            
284 0           $foo->[$j] = 0;
285 0           $max_col_w = 0;
286 0           $min_col_w = 0;
287 0           my @words = split(/\s+/, $row->[$j]);
288 0           foreach (@words) {
289 0 0         if(!exists $word_w->{$_}) {
290 0           $word_w->{$_} = $self->getStringWidth($_) + $space_w;
291             };
292 0           $foo->[$j] += $word_w->{$_};
293 0 0         $min_col_w = $word_w->{$_} if $word_w->{$_} > $min_col_w;
294 0           $max_col_w += $word_w->{$_};
295             }
296 0           $min_col_w += $pad_w;
297 0           $max_col_w += $pad_w;
298 0           $foo->[$j] += $pad_w;
299              
300             # keep a running total of the overall min and max widths
301 0   0       $col_props->[$j]->{min_w} = $col_props->[$j]->{min_w} || 0;
302 0   0       $col_props->[$j]->{max_w} = $col_props->[$j]->{max_w} || 0;
303 0 0         if($min_col_w > $col_props->[$j]->{min_w}) {
304 0           $total_min_w -= $col_props->[$j]->{min_w};
305 0           $total_min_w += $min_col_w;
306 0           $col_props->[$j]->{min_w} = $min_col_w ;
307             }
308 0 0         if($max_col_w > $col_props->[$j]->{max_w}) {
309 0           $total_max_w -= $col_props->[$j]->{max_w};
310 0           $total_max_w += $max_col_w;
311 0           $col_props->[$j]->{max_w} = $max_col_w ;
312             }
313             }
314 0           $row_props->[$rcnt] = $foo;
315 0           $rcnt++;
316             }
317              
318             # calc real column widths width
319 0           my ($col_widths,$width) = $self->col_widths($col_props, $total_max_w, $total_min_w, $arg{'-w'});
320 0 0         $width = $arg{'-w'} if $arg{'-w'};
321              
322 0           my $comp_cnt = 1;
323 0           my (@background_color, @font_color);
324 0           my ($bot_marg, $table_top_y, $text_start, $record, $record_widths);
325 0           $rcnt=0;
326              
327             # Each iteration adds a new page as neccessary
328 0           while(scalar(@{$data})) {
  0            
329 0 0         if($pg_cnt == 1){
330 0           $table_top_y = $arg{'-start_y'};
331 0           $bot_marg = $table_top_y - $arg{'-start_h'};
332             } else {
333 0           $ps->newpage;
334 0           $table_top_y = $arg{'-next_y'};
335 0           $bot_marg = $table_top_y - $arg{'-next_h'};
336             }
337              
338 0           $ps->setfont($fnt_name, $fnt_size);
339 0           $ps->setcolour(@border_color);
340 0           $ps->setlinewidth($line_w);
341              
342             #draw the top line
343 0           $cur_y = $table_top_y;
344 0           $ps->line($arg{'-x'}, $cur_y, $arg{'-x'} + $width, $cur_y);
345            
346 0           my $safety2 = 20;
347              
348             # Each iteration adds a row to the current page until the page is full or there are no more rows to add
349 0   0       while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg) {
  0            
350             #remove the next item from $data
351 0           $record = shift @{$data};
  0            
352 0           $record_widths = shift @$row_props;
353 0 0         next unless $record;
354              
355             # choose colors for this row
356 0 0         @background_color = $rcnt%2?@background_color_even:@background_color_odd;
357 0 0         @font_color = $rcnt%2?@font_color_even:@font_color_odd;
358              
359 0           my $cur_x = $arg{'-x'};
360              
361             # draw cell bgcolor
362             # this has to be separately from the text loop because we do not know the finel height of the cell until all text has been drawn
363 0 0         if(@background_color) {
364 0           $cur_x = $arg{'-x'};
365 0           for(my $j =0;$j < scalar(@$record);$j++) {
366 0 0         if($col_props->[$j]->{'background_color'}) {
367 0           $ps->setcolour($col_props->[$j]->{'background_color'});
368             } else {
369 0           $ps->setcolour(@background_color);
370             }
371 0           $ps->box({filled => 1}, $cur_x, $cur_y, $cur_x + $col_widths->[$j], $cur_y - $row_h);
372 0           $cur_x += $col_widths->[$j];
373             }
374             }
375              
376             # draw text
377 0           $text_start = $cur_y-$fnt_size-$pad_top;
378 0           $cur_x = $arg{'-x'};
379 0           my $leftovers = undef;
380 0           my $do_leftovers = 0;
381 0           for(my $j =0;$j < scalar(@$record);$j++) {
382 0 0         next unless $col_props->[$j]->{max_w};
383 0           $leftovers->[$j] = undef;
384              
385             # look for column properties that overide row properties
386 0 0         if($col_props->[$j]->{'font_color'}) {
387 0           $ps->setcolour($col_props->[$j]->{'font_color'});
388             } else {
389 0           $ps->setcolour(@font_color);
390             }
391 0 0         $col_fnt_size = $col_props->[$j]->{'font_size'}? $col_props->[$j]->{'font_size'}:$fnt_size;
392 0 0         if($col_props->[$j]->{'font'}) {
393 0           $ps->setfont($col_props->[$j]->{'font'},$col_fnt_size);
394             } else {
395 0           $ps->setfont($fnt_name,$col_fnt_size);
396             }
397 0   0       $col_props->[$j]->{justify} = $col_props->[$j]->{justify} || 'left';
398             # if the contents is wider than the specified width, we need to add the text as a text block
399 0 0 0       if($record_widths->[$j] and ($record_widths->[$j] > $col_widths->[$j])) {
400 0           my($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
401             $ps,
402             $record->[$j],
403             -x => $cur_x+$pad_left,
404             -y => $text_start,
405             -w => $col_widths->[$j] - $pad_w,
406             -h => $cur_y - $bot_marg - $pad_top - $pad_bot,
407             -align => $col_props->[$j]->{justify},
408             -lead => $lead
409             );
410            
411             #$lead is added here because $self->text_block returns the incorrect yposition - it is off by $lead
412 0           my $this_row_h = $cur_y - ($ypos_of_last_line +$lead-$pad_bot);
413 0 0         $row_h = $this_row_h if $this_row_h > $row_h;
414 0 0         if($left_over_text) {
415 0           $leftovers->[$j] = $left_over_text;
416 0           $do_leftovers =1;
417             }
418             } else {
419             # Otherwise just use the $ps->text() method
420 0           my $space = $pad_left;
421 0 0         if($col_props->[$j]->{justify} eq 'right') {
422 0           $space = $col_widths->[$j] - ($self->getStringWidth($record->[$j]) + $pad_right);
423             }
424 0           $ps->text($cur_x + $space, $text_start, $record->[$j]);
425             }
426              
427 0           $cur_x += $col_widths->[$j];
428             }
429 0 0         if($do_leftovers) {
430 0           unshift @$data, $leftovers;
431 0           unshift @$row_props, $record_widths;
432 0           $rcnt--;
433             }
434            
435             # draw horizontal lines
436 0           $cur_y -= $row_h;
437 0           $row_h = $min_row_h;
438 0           $ps->setcolour(@border_color);
439 0           $ps->line($arg{'-x'}, $cur_y, $arg{'-x'} + $width, $cur_y);
440 0           $rcnt++;
441              
442             }
443              
444             # draw vertical lines
445 0           $ps->setcolour(@border_color);
446 0           $ps->line($arg{'-x'}, $table_top_y, $arg{'-x'}, $cur_y);
447 0           my $cur_x = $arg{'-x'};
448 0           for(my $j =0;$j < scalar(@$record);$j++){
449 0           $cur_x += $col_widths->[$j];
450 0           $ps->line($cur_x, $table_top_y, $cur_x, $cur_y);
451             }
452 0           $pg_cnt++;
453             }
454             }
455              
456             #return ($page,--$pg_cnt,$cur_y);
457 0           return $cur_y;
458             }
459              
460            
461             # calculate the column widths
462             sub col_widths {
463 0     0 0   my $self = shift;
464 0           my $col_props = shift;
465 0           my $max_width = shift;
466 0           my $min_width = shift;
467 0           my $avail_width = shift;
468            
469 0           my$calc_widths;
470             my $colname;
471 0           my $total = 0;
472 0           for(my $j =0;$j < scalar(@$col_props);$j++) {
473             #foreach $colname (keys %$col_props){
474            
475 0 0 0       if( $col_props->[$j]->{width}) {
    0 0        
    0          
    0          
476             # if the width is specified, use that
477 0           $calc_widths->[$j] = $col_props->[$j]->{width};
478             } elsif( !$avail_width || !$col_props->[$j]->{max_w}) {
479             # if no avail_width is specified
480             # or there is no max_w for the column specified, use the max width
481 0           $calc_widths->[$j] = $col_props->[$j]->{max_w};
482             } elsif($avail_width > $max_width and $max_width > 0) {
483             # if the available space is more than the max, grow each column proportionally
484 0           $calc_widths->[$j] = $col_props->[$j]->{max_w} * ($avail_width/$max_width);
485             } elsif($min_width > $avail_width) {
486             # if the min width is greater than the available width, return the min width
487 0           $calc_widths->[$j] = $col_props->[$j]->{min_w};
488             } else {
489             # else use the autolayout algorithm from RFC 1942
490 0           $calc_widths->[$j] = $col_props->[$j]->{min_w}+(($col_props->[$j]->{max_w} - $col_props->[$j]->{min_w}) * ($avail_width -$min_width))/ ($max_width -$min_width);
491             }
492 0           $total += $calc_widths->[$j];
493             }
494 0           return ($calc_widths,$total);
495             }
496              
497              
498             sub getStringWidth {
499 0     0 0   my $self = shift;
500 0           my $text = shift;
501              
502 0   0       my $font = $self->{current_font} || 'Helvetica';
503 0   0       my $font_size = $self->{current_font_size} || 12;
504              
505             # check to make sure that this font is supported by Metrics.pm !
506             # Helvetica-Italic is not supported by PostScript::Metrics, therefore
507             # we cannot underline this font
508 0           return PostScript::Metrics::stringwidth($text,$font,$font_size);
509             }
510              
511              
512             sub getColor {
513 0     0 0   my $self = shift;
514 0           my $color_string = shift;
515            
516 0           my @color = ();
517            
518 0 0         if (!defined($color_string)) {
519             # return undef if not defined so a default can be used
520 0           return undef;
521             }
522            
523 0 0         if ($color_string =~ /^\#[0-9a-fA-F]{6}/) {
524             # Given hex string, convert to base 10 array
525 0           $color_string =~ /^\#(..)(..)(..)/;
526 0           $color[0] = eval "0x$1";
527 0           $color[1] = eval "0x$2";
528 0           $color[2] = eval "0x$3";
529            
530             } else {
531 0           warn "found color code";
532             # Given color code, store in array[0]
533 0           @color = ($color_string);
534            
535             }
536            
537 0           return @color;
538             }
539              
540              
541             1;
542             __END__