File Coverage

blib/lib/PDF/Table.pm
Criterion Covered Total %
statement 538 658 81.7
branch 168 264 63.6
condition 157 263 59.7
subroutine 17 17 100.0
pod 0 9 0.0
total 880 1211 72.6


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             # vim: softtabstop=4 tabstop=4 shiftwidth=4 ft=perl expandtab smarttab
3              
4 4     4   205123 use 5.010;
  4         37  
5 4     4   19 use strict;
  4         5  
  4         75  
6 4     4   13 use warnings;
  4         6  
  4         117  
7              
8             package PDF::Table;
9              
10 4     4   18 use Carp;
  4         4  
  4         299  
11 4     4   21 use List::Util qw[min max]; # core
  4         9  
  4         327  
12              
13 4     4   1279 use PDF::Table::ColumnWidth;
  4         9  
  4         114  
14 4     4   1340 use PDF::Table::Settings;
  4         8  
  4         19477  
15             # can't move text_block() b/c many globals referenced
16              
17             our $VERSION = '1.003'; # fixed, read by Makefile.PL
18             our $LAST_UPDATE = '1.003'; # manually update whenever code is changed
19             # don't forget to update VERSION down in POD area
20              
21             my $compat_mode = 0; # 0 = new behaviors, 1 = compatible with old
22             # NOTE that a number of t-tests will FAIL in mode 1 (compatible with old)
23             # due to slightly different text placements
24              
25             # ================ COMPATIBILITY WITH OLDER VERSIONS ===============
26             my $repeat_default = 1; # header repeat: old = change to 0
27             my $oddeven_default = 1; # odd/even lines, use old method = change to 0
28             my $padding_default = 2; # 2 points of padding. old = 0 (no padding)
29             # ==================================================================
30             if ($compat_mode) { # 1: be compatible with older PDF::Table behavior
31             ($repeat_default, $oddeven_default, $padding_default) = (0, 0, 0);
32             } else { # 0: do not force compatibility with older PDF::Table behavior
33             ($repeat_default, $oddeven_default, $padding_default) = (1, 1, 2);
34             }
35              
36             # ================ OTHER GLOBAL DEFAULTS =========================== per #7
37             my $fg_color_default = 'black'; # foreground text color
38             # no bg_color_default (defaults to transparent background)
39             my $h_fg_color_default = '#000066'; # fg text color for header
40             my $h_bg_color_default = '#FFFFAA'; # bg color for header
41             my $font_size_default = 12; # base font size
42             my $leading_ratio = 1.25; # leading/font_size ratio (if 'lead' not given)
43             my $border_w_default = 1; # line width for borders
44             my $max_wordlen_default = 20; # split any run of 20 non-space chars
45             my $empty_cell_text = '-'; # something to put in an empty cell
46             my $dashed_rule_default = 2; # dash/space pattern length for broken rows
47             my $min_col_width = 2; # absolute minimum width of a column, > 0
48             # ==================================================================
49              
50             print __PACKAGE__.' is version: '.$VERSION.$/ if ($ENV{'PDF_TABLE_DEBUG'});
51              
52             ############################################################
53             #
54             # new - Constructor
55             #
56             # Parameters are meta information about the PDF. They may be
57             # omitted, so long as the information is passed instead to
58             # the table() method.
59             #
60             # $pdf = PDF::Table->new();
61             # $page = $pdf->page();
62             # $data
63             # %options
64             #
65             ############################################################
66              
67             sub new {
68 7     7 0 2909 my $type = shift(@_);
69 7   33     30 my $class = ref($type) || $type;
70 7         10 my $self = {};
71 7         9 bless ($self, $class);
72              
73             # Pass all the rest to init for validation and initialization
74 7         21 $self->_init(@_);
75              
76 7         35 return $self;
77             }
78              
79             sub _init {
80 7     7   15 my ($self, $pdf, $page, $data, %options ) = @_;
81              
82             # Check and set default values
83 7         16 $self->set_defaults();
84              
85             # Check and set mandatory parameters
86 7         16 $self->set_pdf($pdf);
87 7         16 $self->set_page($page);
88 7         16 $self->set_data($data);
89 7         17 $self->set_options(\%options);
90              
91 7         10 return;
92             }
93              
94             sub set_defaults {
95 7     7 0 9 my $self = shift;
96              
97 7         18 $self->{'font_size'} = $font_size_default;
98 7         18 $min_col_width = max($min_col_width, 1); # minimum width
99 7         10 return;
100             }
101              
102             sub set_pdf {
103 7     7 0 12 my ($self, $pdf) = @_;
104 7         11 $self->{'pdf'} = $pdf;
105 7         7 return;
106             }
107              
108             sub set_page {
109 7     7 0 12 my ($self, $page) = @_;
110 7 50 33     32 if ( defined($page) && ref($page) ne 'PDF::API2::Page'
      33        
111             && ref($page) ne 'PDF::Builder::Page' ) {
112              
113 0 0 0     0 if (ref($self->{'pdf'}) eq 'PDF::API2' ||
114             ref($self->{'pdf'}) eq 'PDF::Builder') {
115 0         0 $self->{'page'} = $self->{'pdf'}->page();
116             } else {
117 0         0 carp 'Warning: Page must be a PDF::API2::Page or PDF::Builder::Page object but it seems to be: '.ref($page).$/;
118 0         0 carp 'Error: Cannot set page from passed PDF object either, as it is invalid!'.$/;
119             }
120 0         0 return;
121             }
122 7         12 $self->{'page'} = $page;
123 7         14 return;
124             }
125              
126             sub set_data {
127 7     7 0 12 my ($self, $data) = @_;
128             # TODO: implement
129 7         9 return;
130             }
131              
132             sub set_options {
133 7     7 0 9 my ($self, $options) = @_;
134             # TODO: implement
135 7         9 return;
136             }
137              
138             ################################################################
139             # table - utility method to build multi-row, multicolumn tables
140             ################################################################
141              
142             sub table {
143             #use Storable qw( dclone );
144             # can't use Storable::dclone because can't handle CODE. would like to deep
145             # clone %arg so that modifications (remove leading '-' and/or substitute for
146             # deprecated names) won't modify original %arg hash on the outside.
147 14     14 0 4660 my $self = shift;
148 14         17 my $pdf = shift;
149 14         16 my $page = shift;
150 14         16 my $data = shift;
151 14         53 my %arg = @_;
152              
153             #=====================================
154             # Mandatory Arguments Section
155             #=====================================
156 13 50 33     83 unless ($pdf and $page and $data) {
      33        
157 0         0 carp "Error: Mandatory parameter is missing PDF/page/data object!\n";
158 0         0 return ($page, 0, 0);
159             }
160              
161             # Validate mandatory argument data type
162 13 100 66     180 croak "Error: Invalid PDF object received."
163             unless (ref($pdf) eq 'PDF::API2' ||
164             ref($pdf) eq 'PDF::Builder');
165 12 100 66     80 croak "Error: Invalid page object received."
166             unless (ref($page) eq 'PDF::API2::Page' ||
167             ref($page) eq 'PDF::Builder::Page');
168 11 100 100     99 croak "Error: Invalid data received."
169             unless ((ref($data) eq 'ARRAY') && scalar(@$data));
170 10 50       29 croak "Error: Missing required settings."
171             unless (scalar(keys %arg));
172              
173             # ==================================================================
174             # did client code ask to redefine?
175             ($repeat_default, $oddeven_default, $padding_default) =
176 10 50       22 @{$arg{'compatibility'}} if defined $arg{'compatibility'};
  0         0  
177              
178             # set some defaults !!!!
179 10   100     43 $arg{'cell_render_hook' } ||= undef;
180              
181             # Validate settings key
182 10         185 my %valid_settings_key = (
183             'x' => 1, # global, mandatory
184             'w' => 1, # global, mandatory
185             'y' => 1, # global, mandatory
186             'start_y' => 1, # deprecated
187             'h' => 1, # global, mandatory
188             'start_h' => 1, # deprecated
189             'next_y' => 1, # global
190             'next_h' => 1, # global
191             'leading' => 1, # text_block
192             'lead' => 1, # deprecated
193             'padding' => 1, # global
194             'padding_right' => 1, # global
195             'padding_left' => 1, # global
196             'padding_top' => 1, # global
197             'padding_bottom' => 1, # global
198             'bg_color' => 1, # global, header, row, column, cell
199             'background_color' => 1, # deprecated
200             'bg_color_odd' => 1, # global, column, cell
201             'background_color_odd'=> 1, # deprecated
202             'bg_color_even' => 1, # global, column, cell
203             'background_color_even'=> 1, # deprecated
204             'fg_color' => 1, # global, header, row, column, cell
205             'font_color' => 1, # deprecated
206             'fg_color_odd' => 1, # global, column, cell
207             'font_color_odd' => 1, # deprecated
208             'fg_color_even' => 1, # global, column, cell
209             'font_color_even' => 1, # deprecated
210             'border_w' => 1, # global
211             'border' => 1, # deprecated
212             'h_border_w' => 1, # global
213             'horizontal_borders' => 1, # deprecated
214             'v_border_w' => 1, # global
215             'vertical_borders' => 1, # deprecated
216             'border_c' => 1, # global
217             'border_color' => 1, # deprecated
218             # possibly in future, separate h_border_c and v_border_c
219             'rule_w' => 1, # global, row, column, cell
220             'h_rule_w' => 1, # global, row, column, cell
221             'v_rule_w' => 1, # global, row, column, cell
222             'rule_c' => 1, # global, row, column, cell
223             'h_rule_c' => 1, # global, row, column, cell
224             'v_rule_c' => 1, # global, row, column, cell
225             'font' => 1, # global, header, row, column, cell
226             'font_size' => 1, # global, header, row, column, cell
227             'underline' => 1, # global, header, row, column, cell
228             'font_underline' => 1, # deprecated
229             'min_w' => 1, # global, header, row, column, cell
230             'max_w' => 1, # global, header, row, column, cell
231             'min_rh' => 1, # global, header, row, column, cell
232             'row_height' => 1, # deprecated
233             'new_page_func' => 1, # global
234             'header_props' => 1, # includes sub-settings like repeat
235             'row_props' => 1, # includes sub-settings like fg_color
236             'column_props' => 1, # includes sub-settings like fg_color
237             'cell_props' => 1, # includes sub-settings like fg_color
238             'max_word_length' => 1, # global, text_block
239             'cell_render_hook' => 1, # global
240             'default_text' => 1, # global
241             'justify' => 1, # global
242             # 'repeat' # header
243             # 'align' # text_block
244             # 'parspace' # text_block
245             # 'hang' # text_block
246             # 'flindent' # text_block
247             # 'fpindent' # text_block
248             # 'indent' # text_block
249             'size' => 1, # global
250             );
251 10         28 foreach my $key (keys %arg) {
252             # Provide backward compatibility
253 90 50       126 $arg{$key} = delete $arg{"-$key"} if $key =~ s/^-//;
254              
255             croak "Error: Invalid setting key '$key' received."
256 90 50       153 unless exists $valid_settings_key{$key};
257             }
258              
259              
260 10         27 my ( $xbase, $ybase, $width, $height ) = ( undef, undef, undef, undef );
261             # TBD eventually deprecated start_y and start_h go away
262             # special treatment here because haven't yet copied deprecated names
263 10   50     21 $xbase = $arg{'x'} || -1;
264 10   50     27 $ybase = $arg{'y'} || $arg{'start_y'} || -1;
265 10   50     18 $width = $arg{'w'} || -1;
266 10   50     31 $height = $arg{'h'} || $arg{'start_h'} || -1;
267              
268             # Global geometry parameters are also mandatory.
269 10 50       19 unless ( $xbase > 0 ) {
270 0         0 carp "Error: Left Edge of Table is NOT defined!\n";
271 0         0 return ($page, 0, $ybase);
272             }
273 10 50       28 unless ( $ybase > 0 ) {
274 0         0 carp "Error: Base Line of Table is NOT defined!\n";
275 0         0 return ($page, 0, $ybase);
276             }
277 10 50       19 unless ( $width > 0 ) {
278 0         0 carp "Error: Width of Table is NOT defined!\n";
279 0         0 return ($page, 0, $ybase);
280             }
281 10 50       17 unless ( $height > 0 ) {
282 0         0 carp "Error: Height of Table is NOT defined!\n";
283 0         0 return ($page, 0, $ybase);
284             }
285              
286 10         11 my $pg_cnt = 1;
287 10         12 my $cur_y = $ybase;
288 10   100     25 my $cell_props = $arg{'cell_props'} || []; # per cell properties
289              
290             # If there is no valid data array reference, warn and return!
291 10 50       24 if (ref $data ne 'ARRAY') {
292 0         0 carp "Passed table data is not an ARRAY reference. It's actually a ref to ".ref($data);
293 0         0 return ($page, 0, $cur_y);
294             }
295              
296             # Ensure default values for next_y and next_h
297 10   50     20 my $next_y = $arg{'next_y'} || undef;
298 10   50     23 my $next_h = $arg{'next_h'} || undef;
299 10   50     36 my $size = $arg{'size'} || undef;
300              
301             # Create Text Object
302 10         28 my $txt = $page->text();
303              
304             #=====================================
305             # Table Header Section
306             #
307             # order of precedence: header_props, column_props, globals, defaults
308             # here, header settings are initialized to globals/defaults
309             #=====================================
310             # Disable header row into the table
311 10         103 my $header_props = undef;
312 10         14 my $do_headers = 0; # not doing headers
313              
314             # Check if the user enabled it ?
315 10 100 66     29 if (defined $arg{'header_props'} and ref( $arg{'header_props'}) eq 'HASH') {
316             # Transfer the reference to local variable
317 1         2 $header_props = $arg{'header_props'};
318              
319             # Check other parameters and put defaults if needed
320 1   33     3 $header_props->{'repeat' } ||= $repeat_default;
321              
322 1         1 $do_headers = 1; # do headers, no repeat
323 1 50       3 $do_headers = 2 if $header_props->{'repeat'}; # do headers w/ repeat
324             }
325              
326 10         16 my $header_row = undef;
327             # Copy the header row (text) if header is enabled
328 10 100       25 @$header_row = $$data[0] if $do_headers;
329             # Determine column widths based on content
330              
331             # an arrayref whose values are a hashref holding
332             # the minimum and maximum width of that column
333 10   100     24 my $col_props = $arg{'column_props'} || [];
334              
335             # an arrayref whose values are a hashref holding
336             # various row settings for a specific row
337 10   50     35 my $row_props = $arg{'row_props'} || [];
338              
339             # deprecated setting (globals) names, copy to new names
340 10         35 PDF::Table::Settings::deprecated_settings(
341             $data, $row_props, $col_props, $cell_props, $header_props, \%arg);
342             # check settings values as much as possible
343 10         35 PDF::Table::Settings::check_settings(%arg);
344              
345             #=====================================
346             # Set Global Default Properties
347             #=====================================
348             # geometry-related global settings checked, last value for find_value()
349 10   33     44 my $fnt_obj = $arg{'font' } ||
350             $pdf->corefont('Times',-encode => 'latin1');
351 10   66     74 my $fnt_size = $arg{'font_size' } || $font_size_default;
352 10         22 my $min_leading = $fnt_size * $leading_ratio;
353 10   33     32 my $leading = $arg{'leading'} || $min_leading;
354 10 50       44 if ($leading < $fnt_size) {
355 0         0 carp "Warning: Global leading value $leading is less than font size $fnt_size, increased to $min_leading\n";
356 0         0 $arg{'leading'} = $leading = $min_leading;
357             }
358              
359             # can't condense $border_w to || because border_w=>0 gets default of 1!
360 10 100       48 my $border_w = defined $arg{'border_w'}? $arg{'border_w'}: 1;
361 10   66     35 my $h_border_w = $arg{'h_border_w'} || $border_w;
362 10   66     35 my $v_border_w = $arg{'v_border_w'} || $border_w;
363              
364             # non-geometry global settings
365 10   66     39 my $border_c = $arg{'border_c'} || $fg_color_default;
366             # global fallback values for find_value() call
367 10   50     26 my $underline = $arg{'underline' } ||
368             undef; # merely stating undef is the intended default
369 10   66     26 my $max_word_len = $arg{'max_word_length' } || $max_wordlen_default;
370 10   33     28 my $default_text = $arg{'default_text' } || $empty_cell_text;
371              
372             # An array ref of arrayrefs whose values are
373             # the actual widths of the column/row intersection
374 10         13 my $row_col_widths = [];
375             # An array ref with the widths of the header row
376 10         13 my $h_row_widths = [];
377              
378             # Scalars that hold sum of the maximum and minimum widths of all columns
379 10         15 my ( $max_col_w, $min_col_w ) = ( 0,0 );
380 10         13 my ( $row, $space_w );
381              
382 10         17 my $word_widths = {};
383 10         31 my $rows_height = [];
384 10         18 my $first_row = 1;
385 10         11 my $is_header_row = 0;
386              
387             # per-cell values
388 10         15 my ($cell_font, $cell_font_size, $cell_underline, $cell_justify,
389             $cell_height, $cell_pad_top, $cell_pad_right, $cell_pad_bot,
390             $cell_pad_left, $cell_leading, $cell_max_word_len, $cell_bg_color,
391             $cell_fg_color, $cell_bg_color_even, $cell_bg_color_odd,
392             $cell_fg_color_even, $cell_fg_color_odd, $cell_min_w, $cell_max_w,
393             $cell_h_rule_w, $cell_v_rule_w, $cell_h_rule_c, $cell_v_rule_c,
394             $cell_def_text);
395              
396             # for use by find_value()
397 10         20 my $GLOBALS = [$cell_props, $col_props, $row_props, -1, -1, \%arg];
398             # ----------------------------------------------------------------------
399             # GEOMETRY
400             # figure row heights and column widths,
401             # update overall table width if necessary
402             # here we're only interested in things that affect the table geometry
403             #
404             # $rows_height->[$row_idx] array overall height of each row
405             # $calc_column_widths overall width of each column
406 10         11 my $col_min_width = []; # holds the running width of each column
407 10         12 my $col_max_content = []; # min and max (min_w & longest word,
408             # length of content)
409 10         12 my $max_w = []; # each column's max_w, if defined
410 10         23 for ( my $row_idx = 0; $row_idx < scalar(@$data) ; $row_idx++ ) {
411 19         26 $GLOBALS->[3] = $row_idx;
412 19         24 my $column_widths = []; # holds the width of each column
413             # initialize the height for this row
414 19         25 $rows_height->[$row_idx] = 0;
415              
416 19         23 for ( my $col_idx = 0;
417 69         117 $col_idx < scalar(@{$data->[$row_idx]});
418             $col_idx++ ) {
419 50         54 $GLOBALS->[4] = $col_idx;
420             # initialize min and max column content widths to 0
421 50 100       73 $col_min_width->[$col_idx]=0 if !defined $col_min_width->[$col_idx];
422 50 100       69 $col_max_content->[$col_idx]=0 if !defined $col_max_content->[$col_idx];
423              
424 50 100 100     110 if ( !$row_idx && $do_headers ) {
425             # header row
426 3         3 $is_header_row = 1;
427 3         4 $GLOBALS->[3] = 0;
428 3         4 $cell_font = $header_props->{'font'};
429 3         3 $cell_font_size = $header_props->{'font_size'};
430 3         4 $cell_leading = $header_props->{'leading'};
431 3         3 $cell_height = $header_props->{'min_rh'};
432             $cell_pad_top = $header_props->{'padding_top'} ||
433 3   33     5 $header_props->{'padding'};
434             $cell_pad_right = $header_props->{'padding_right'} ||
435 3   33     7 $header_props->{'padding'};
436             $cell_pad_bot = $header_props->{'padding_bottom'} ||
437 3   33     7 $header_props->{'padding'};
438             $cell_pad_left = $header_props->{'padding_left'} ||
439 3   33     7 $header_props->{'padding'};
440 3         3 $cell_max_word_len = $header_props->{'max_word_length'};
441 3         3 $cell_min_w = $header_props->{'min_w'};
442 3         2 $cell_max_w = $header_props->{'max_w'};
443 3         3 $cell_def_text = $header_props->{'default_text'};
444             # items not of interest for determining geometry
445             #$cell_underline = $header_props->{'underline'};
446             #$cell_justify = $header_props->{'justify'};
447             #$cell_bg_color = $header_props->{'bg_color'};
448             #$cell_fg_color = $header_props->{'fg_color'};
449             #$cell_bg_color_even= undef;
450             #$cell_bg_color_odd = undef;
451             #$cell_fg_color_even= undef;
452             #$cell_fg_color_odd = undef;
453             #$cell_h_rule_w = header_props->{'h_rule_w'};
454             #$cell_v_rule_w = header_props->{'v_rule_w'};
455             #$cell_h_rule_c = header_props->{'h_rule_c'};
456             #$cell_v_rule_c = header_props->{'v_rule_c'};
457             } else {
458             # not a header row, so uninitialized
459 47         51 $is_header_row = 0;
460 47         46 $cell_font = undef;
461 47         44 $cell_font_size = undef;
462 47         67 $cell_leading = undef;
463 47         42 $cell_height = undef;
464 47         38 $cell_pad_top = undef;
465 47         34 $cell_pad_right = undef;
466 47         45 $cell_pad_bot = undef;
467 47         45 $cell_pad_left = undef;
468 47         49 $cell_max_word_len = undef;
469 47         41 $cell_min_w = undef;
470 47         37 $cell_max_w = undef;
471 47         40 $cell_def_text = undef;
472             # items not of interest for determining geometry
473             #$cell_underline = undef;
474             #$cell_justify = undef;
475             #$cell_bg_color = undef;
476             #$cell_fg_color = undef;
477             #$cell_bg_color_even= undef;
478             #$cell_bg_color_odd = undef;
479             #$cell_fg_color_even= undef;
480             #$cell_fg_color_odd = undef;
481             #$cell_h_rule_w = undef;
482             #$cell_v_rule_w = undef;
483             #$cell_h_rule_c = undef;
484             #$cell_v_rule_c = undef;
485             }
486              
487             # Get the most specific value if none was already set from header_props
488             # TBD should header_props be treated like a row_props (taking
489             # precedence over row_props), but otherwise like a row_props? or
490             # should anything in header_props take absolute precedence as now?
491              
492 50         78 $cell_font = find_value($cell_font,
493             'font', '', $fnt_obj, $GLOBALS);
494 50         71 $cell_font_size = find_value($cell_font_size,
495             'font_size', '', 0, $GLOBALS);
496 50 100       81 if ($cell_font_size == 0) {
497 22 50       34 if ($is_header_row) {
498 0         0 $cell_font_size = $fnt_size + 2;
499             } else {
500 22         23 $cell_font_size = $fnt_size;
501             }
502             }
503 50         62 $cell_leading = find_value($cell_leading, 'leading',
504             '', -1, $GLOBALS);
505 50         65 $cell_height = find_value($cell_height,
506             'min_rh', '', 0, $GLOBALS);
507 50         59 $cell_pad_top = find_value($cell_pad_top, 'padding_top',
508             'padding', $padding_default,
509             $GLOBALS);
510 50         59 $cell_pad_right = find_value($cell_pad_right, 'padding_right',
511             'padding', $padding_default,
512             $GLOBALS);
513 50         61 $cell_pad_bot = find_value($cell_pad_bot, 'padding_bottom',
514             'padding', $padding_default,
515             $GLOBALS);
516 50         62 $cell_pad_left = find_value($cell_pad_left, 'padding_left',
517             'padding', $padding_default,
518             $GLOBALS);
519 50         58 $cell_max_word_len = find_value($cell_max_word_len, 'max_word_len',
520             '', $max_word_len, $GLOBALS);
521 50         63 $cell_min_w = find_value($cell_min_w, 'min_w',
522             '', undef, $GLOBALS);
523 50         80 $cell_max_w = find_value($cell_max_w, 'max_w',
524             '', undef, $GLOBALS);
525 50 50 33     79 if (defined $cell_max_w && defined $cell_min_w) {
526 0         0 $cell_max_w = max($cell_max_w, $cell_min_w);
527             }
528 50         61 $cell_def_text = find_value($cell_def_text, 'default_text', '',
529             $default_text, $GLOBALS);
530             # items not of interest for determining geometry
531             #$cell_underline = find_value($cell_underline,
532             # 'underline', '', $underline, $GLOBALS);
533             #$cell_justify = find_value($cell_justify,
534             # 'justify', '', 'left', $GLOBALS);
535             #$cell_bg_color = find_value($cell_bg_color, 'bg_color',
536             # '', undef, $GLOBALS);
537             #$cell_fg_color = find_value($cell_fg_color, 'fg_color',
538             # '', $fg_color_default, $GLOBALS);
539             #$cell_bg_color_even = find_value($cell_bg_color_even,
540             # 'bg_color_even', '', undef, $GLOBALS);
541             #$cell_bg_color_odd = find_value($cell_bg_color_odd,
542             # 'bg_color_odd', '', undef, $GLOBALS);
543             #$cell_fg_color_even = find_value($cell_fg_color_even,
544             # 'fg_color_even', '', undef, $GLOBALS);
545             #$cell_fg_color_odd = find_value($cell_fg_color_odd,
546             # 'fg_color_odd', '', undef, $GLOBALS);
547             #$cell_h_rule_w = find_value($cell_h_rule_w, 'h_rule_w',
548             # 'rule_w', $h_border_w, $GLOBALS);
549             #$cell_v_rule_w = find_value($cell_v_rule_w, 'v_rule_w',
550             # 'rule_w', $v_border_w, $GLOBALS);
551             #$cell_h_rule_c = find_value($cell_h_rule_c, 'h_rule_c',
552             # 'rule_c', $border_c, $GLOBALS);
553             #$cell_v_rule_c = find_value($cell_v_rule_c, 'v_rule_c',
554             # 'rule_c', $border_c, $GLOBALS);
555              
556 50         64 my $min_leading = $cell_font_size * $leading_ratio;
557 50 50       71 if ($cell_leading <= 0) {
558             # leading left at default, silently set to minimum
559 50         51 $cell_leading = $min_leading;
560             } else {
561             # leading specified, but is too small?
562 0 0       0 if ($cell_leading < $cell_font_size) {
563 0         0 carp "Warning: Cell[$row_idx][$col_idx] leading value $cell_leading is less than font size $cell_font_size, increased to $min_leading\n";
564 0         0 $cell_leading = $min_leading;
565             }
566             }
567              
568             # Set Font
569 50         109 $txt->font( $cell_font, $cell_font_size );
570              
571             # Set row height to biggest font size from row's cells
572             # Note that this assumes just one line of text per cell
573 50         300 $rows_height->[$row_idx] = max($rows_height->[$row_idx],
574             $cell_leading + $cell_pad_top + $cell_pad_bot, $cell_height);
575              
576             # This should fix a bug with very long words like serial numbers,
577             # etc. TBD: consider splitting ONLY on end of line, and adding a
578             # hyphen (dash) at split. would have to track split words (by
579             # index numbers?) and glue them back together when there's space
580             # to do so (including hyphen).
581 50 100 66     148 if ( $cell_max_word_len > 0 && $data->[$row_idx][$col_idx]) {
582 48         233 $data->[$row_idx][$col_idx] =~ s#(\S{$cell_max_word_len})(?=\S)#$1 #g;
583             }
584              
585             # Init cell size limits (per row)
586 50         94 $space_w = $txt->advancewidth( "\x20" );
587             # font/size can change for each cell, so space width can vary
588 50         137 $column_widths->[$col_idx] = 0; # per-row basis
589 50         48 $max_col_w = 0;
590 50         45 $min_col_w = 0;
591              
592 50         54 my @words;
593 50 100       155 @words = split( /\s+/, $data->[$row_idx][$col_idx] )
594             if $data->[$row_idx][$col_idx];
595             # TBD count up spaces instead of assuming one between each word,
596             # don't know what to do about \t (not defined!). NBSP would
597             # be treated as non-space for these calculations, not sure
598             # how it would render. \r, \n, etc. no space? then there is
599             # check how text is split into lines in text_block if
600             # multiple spaces between words.
601              
602             # for cell, minimum width is longest word, maximum is entire text
603             # treat header row like any data row for this
604             # increase minimum width to (optional) specified column min width
605             # keep (optional) specified column max width separate
606             # NOTE that cells with only blanks will be treated as empty (no
607             # words) and have only L+R padding for a width!
608 50         73 foreach ( @words ) {
609 60 100       94 unless ( exists $word_widths->{$_} ) {
610             # Calculate the width of every word and add the space width to it
611             # caching each word so only figure width once
612 58         86 $word_widths->{$_} = $txt->advancewidth($_);
613             }
614              
615             # minimum width is longest word or fragment
616 60         220 $min_col_w = max($min_col_w, $word_widths->{$_});
617             # maximum width is total text in cell
618 60 100       73 if ($max_col_w) {
619             # already have text, so add a space first
620             # note that multiple spaces between words become one!
621 12         12 $max_col_w += $space_w;
622             } else {
623             # first word, so no space [before]
624             }
625 60         78 $max_col_w += $word_widths->{$_};
626             }
627              
628             # don't forget any default text! it's not split on max_word_len
629             # TBD should default_text be split like other text?
630 50         74 $min_col_w = max($min_col_w, $txt->advancewidth($cell_def_text));
631              
632             # at this point we have longest word (min_col_w), overall length
633             # (max_col_w) of this cell. add L+R padding
634             # cell_min/max_w are optional settings
635             # TBD what if $cell_def_text is longer?
636 50         132 $min_col_w += $cell_pad_left + $cell_pad_right;
637 50 50       73 $min_col_w = max($min_col_w, $cell_min_w) if defined $cell_min_w;
638 50         54 $max_col_w += $cell_pad_left + $cell_pad_right;
639 50         67 $max_col_w = max($min_col_w, $max_col_w);
640 50         71 $col_min_width->[$col_idx] = max($col_min_width->[$col_idx],
641             $min_col_w);
642 50         64 $col_max_content->[$col_idx] = max($col_max_content->[$col_idx],
643             $max_col_w);
644              
645 50 100       101 if (!defined $max_w->[$col_idx]) { $max_w->[$col_idx] = -1; }
  27         35  
646 50 50       63 $max_w->[$col_idx] = max($max_w->[$col_idx], $cell_max_w) if
647             defined $cell_max_w; # otherwise -1
648 50         87 $column_widths->[$col_idx] = $col_max_content->[$col_idx];
649              
650             } # (End of cols) for (my $col_idx....
651              
652 19         24 $row_col_widths->[$row_idx] = $column_widths;
653              
654             # Copy the calculated row properties of header row.
655 19 100 100     67 @$h_row_widths = @$column_widths if !$row_idx && $do_headers;
656              
657             } # (End of rows) for ( my $row_idx row heights and column widths
658              
659             # Calc real column widths and expand table width if needed.
660 10         13 my $calc_column_widths;
661 10         18 my $em_size = $txt->advancewidth('M');
662 10         32 my $ex_size = $txt->advancewidth('x');
663              
664 10 50       33 if (defined $size) {
665 0         0 ($calc_column_widths, $width) =
666             PDF::Table::ColumnWidth::SetColumnWidths(
667             $width, $size, $em_size, $ex_size );
668             } else {
669 10         26 ($calc_column_widths, $width) =
670             PDF::Table::ColumnWidth::CalcColumnWidths(
671             $width, $col_min_width, $col_max_content, $max_w );
672             }
673              
674             # ----------------------------------------------------------------------
675             # Let's draw what we have!
676 10         16 my $row_idx = 0; # first row (might be header)
677 10         11 my $row_is_odd = 0; # first data row output (row 0) is "even"
678             # Store header row height for later use if headers have to be repeated
679 10         13 my $header_min_rh = $rows_height->[0]; # harmless if no header
680             # kind of top border to draw, depending on start or continuation
681 10         12 my $next_top_border = 0;
682              
683 10         16 my ( $gfx, $gfx_bg, $bg_color, $fg_color,
684             $bot_margin, $table_top_y, $text_start_y);
685              
686             # Each iteration adds a new page as necessary
687 10         12 while (scalar(@{$data})) { # still row(s) remaining to output
  21         40  
688 11         12 my ($page_header, $columns_number);
689              
690 11 100       24 if ($pg_cnt == 1) {
691             # on first page output
692 10         10 $table_top_y = $ybase;
693 10         13 $bot_margin = $table_top_y - $height;
694              
695             # Check for safety reasons
696 10 50       21 if ( $bot_margin < 0 ) {
697 0         0 carp "!!! Warning: !!! Incorrect Table Geometry! h ($height) greater than remaining page space y ($table_top_y). Reducing height to fit on page.\n";
698 0         0 $bot_margin = 0;
699 0         0 $height = $table_top_y;
700             }
701              
702             } else {
703             # on subsequent (overflow) pages output
704 1 50       3 if (ref $arg{'new_page_func'}) {
705 0         0 $page = &{ $arg{'new_page_func'} };
  0         0  
706             } else {
707 1         3 $page = $pdf->page();
708             }
709              
710             # we NEED next_y and next_h! if undef, complain and use
711             # 90% and 80% respectively of page height
712 1 50       12 if (!defined $next_y) {
713 0         0 my @page_dim = $page->mediabox();
714 0         0 $next_y = ($page_dim[3] - $page_dim[1]) * 0.9;
715 0         0 carp "!!! Error: !!! Table spills to next page, but no next_y was given! Using $next_y.\n";
716             }
717 1 50       4 if (!defined $next_h) {
718 0         0 my @page_dim = $page->mediabox();
719 0         0 $next_h = ($page_dim[3] - $page_dim[1]) * 0.8;
720 0         0 carp "!!! Error: !!! Table spills to next page, but no next_h was given! Using $next_h.\n";
721             }
722              
723 1         1 $table_top_y = $next_y;
724 1         1 $bot_margin = $table_top_y - $next_h;
725              
726             # Check for safety reasons
727 1 50       2 if ( $bot_margin < 0 ) {
728 0         0 carp "!!! Warning: !!! Incorrect Table Geometry! next_h ($next_h) greater than remaining page space next_y ($next_y), must be reduced to fit on page.\n";
729 0         0 $bot_margin = 0;
730 0         0 $next_h = $table_top_y;
731             }
732              
733             # push copy of header onto remaining table data, if repeated hdr
734 1 50       13 if ( $do_headers == 2 ) {
735             # Copy Header Data
736 0         0 @$page_header = @$header_row;
737 0         0 my $hrw ;
738 0         0 @$hrw = @$h_row_widths ;
739             # Then prepend it to master data array
740 0         0 unshift @$data, @$page_header;
741 0         0 unshift @$row_col_widths, $hrw;
742 0         0 unshift @$rows_height, $header_min_rh;
743              
744 0         0 $first_row = 1; # Means YES
745             # Roll back the row_idx because a new header row added
746 0         0 $row_idx--;
747             }
748            
749             }
750             # ----------------------------------------------------------------
751             # should be at top of table for current page
752             # either start of table, or continuation
753             # pg_cnt >= 1
754             # do_headers = 0 not doing headers
755             # 1 non-repeating header
756             # 2 repeating header
757              
758             # check if enough vertical space for first data row (row 0 or 1), AND
759             # for header (0) if doing a header row! increase height, decrease
760             # bot_margin. possible that bot_margin goes < 0 (warning message).
761             # TBD if first page (pg_cnt==1), and sufficient space on next page,
762             # just skip first page and go on to second
763             # For degenerate cases where there is only a header row and no data
764             # row(s), don't try to make use of missing rows height [1]
765 11         14 my $min_height = $rows_height->[0];
766 11 50 100     77 $min_height += $rows_height->[1] if
      66        
767             ($do_headers && $pg_cnt==1 || $do_headers==2 && $pg_cnt>1) &&
768             defined $rows_height->[1];
769 11 50       28 if ($min_height >= $table_top_y - $bot_margin) {
770             # Houston, we have a problem. height isn't enough
771 0         0 my $delta = $min_height - ($table_top_y - $bot_margin) + 1;
772 0 0       0 if ($delta > $bot_margin) {
773 0         0 carp "!! Error !! Insufficient space (by $delta) to get minimum number of row(s) on page. Some content may be lost off page bottom";
774             } else {
775 0         0 carp "!! Warning !! Need to expand allotted vertical height by $delta to fit minimum number of row(s) on page";
776             }
777 0         0 $bot_margin -= $delta;
778 0 0       0 if ($pg_cnt == 1) {
779 0         0 $height += $delta;
780             } else {
781 0         0 $next_h += $delta;
782             }
783             }
784              
785             # order is important -- cell background layer must be rendered
786             # before text layer and then other graphics (rules, borders)
787 11         29 $gfx_bg = $page->gfx();
788 11         109 $txt = $page->text();
789              
790 11         77 $cur_y = $table_top_y;
791              
792             # let's just always go ahead and create $gfx (for drawing borders
793             # and rules), as it will almost always be needed
794 11         20 $gfx = $page->gfx(); # for borders, rules, etc.
795 11         102 $gfx->strokecolor($border_c);
796              
797             # Draw the top line (border), only if h_border_w > 0, as we
798             # don't know what rules are doing
799 11 100       63 if ($h_border_w) {
800 8 50       22 if ($next_top_border == 0) {
    0          
801             # first top border (page 1), use specified border
802 8         23 $gfx->linewidth($h_border_w);
803             } elsif ($next_top_border == 1) {
804             # solid thin line at start of a row
805 0         0 $gfx->linewidth($border_w_default);
806             } else { # == 2
807             # dashed thin line at continuation in middle of row
808 0         0 $gfx->linewidth($border_w_default);
809 0         0 $gfx->linedash($dashed_rule_default);
810             }
811 8         54 $gfx->move( $xbase-$v_border_w/2 , $cur_y );
812 8         44 $gfx->hline($xbase + $width + $v_border_w/2);
813 8         36 $gfx->stroke();
814 8         31 $gfx->linedash();
815             }
816              
817 11         61 my @actual_column_widths;
818             my %colspanned;
819              
820             # Each iteration adds a row to the current page until the page is full
821             # or there are no more rows to add
822             # Row_Loop
823 11   100     16 while (scalar(@{$data}) and $cur_y-$rows_height->[0] > $bot_margin) {
  30         96  
824             # Remove the next item from $data
825 19         21 my $data_row = shift @{$data};
  19         26  
826              
827             # Get max columns number to know later how many vertical lines to draw
828 19         22 $columns_number = scalar(@$data_row);
829              
830             # Get the next set of row related settings
831             # Row Height (starting point for $current_min_rh)
832 19         20 my $current_min_rh = shift @$rows_height;
833 19         20 my $actual_row_height = $current_min_rh;
834              
835             # Row cell widths
836 19         24 my $data_row_widths = shift @$row_col_widths;
837              
838             # remember, don't have cell_ stuff yet, just row items ($row_idx)!
839 19         19 my $cur_x = $xbase;
840 19         18 my $leftovers = undef; # Reference to text that is returned from text_block()
841 19         21 my $do_leftovers = 0; # part of a row spilled to next page
842              
843             # Process every cell(column) from current row
844             # due to colspan, some rows have fewer columns than others
845 19         32 my @save_bg_color; # clear out for each row
846             my @save_fg_color;
847 19         0 my (@save_v_rule_w, @save_v_rule_c, @save_h_rule_w, @save_h_rule_c);
848 19         41 for ( my $col_idx = 0; $col_idx < $columns_number; $col_idx++ ) {
849 50         56 $GLOBALS->[3] = $row_idx;
850 50         49 $GLOBALS->[4] = $col_idx;
851             # now have each cell[$row_idx][$col_idx]
852 50 100       113 next if $colspanned{$row_idx.'_'.$col_idx};
853 49         74 $leftovers->[$col_idx] = undef;
854              
855             # look for font information for this cell
856 49         63 my ($cell_font, $cell_font_size, $cell_leading, $cell_underline,
857             $cell_pad_top, $cell_pad_right, $cell_pad_bot,
858             $cell_pad_left, $cell_justify, $cell_fg_color,
859             $cell_bg_color, $cell_def_text, $cell_min_w, $cell_max_w);
860              
861 49 100 100     122 if ($first_row and $do_headers) {
862 3         4 $is_header_row = 1;
863 3         4 $GLOBALS->[3] = 0;
864 3         3 $cell_font = $header_props->{'font'};
865 3         2 $cell_font_size = $header_props->{'font_size'};
866 3         3 $cell_leading = $header_props->{'leading'};
867 3         3 $cell_height = $header_props->{'min_rh'};
868             $cell_pad_top = $header_props->{'padding_top'} ||
869 3   33     7 $header_props->{'padding'};
870             $cell_pad_right = $header_props->{'padding_right'} ||
871 3   33     7 $header_props->{'padding'};
872             $cell_pad_bot = $header_props->{'padding_bottom'} ||
873 3   33     14 $header_props->{'padding'};
874             $cell_pad_left = $header_props->{'padding_left'} ||
875 3   33     6 $header_props->{'padding'};
876 3         3 $cell_max_word_len = $header_props->{'max_word_length'};
877 3         3 $cell_min_w = $header_props->{'min_w'};
878 3         4 $cell_max_w = $header_props->{'max_w'};
879 3         9 $cell_underline = $header_props->{'underline'};
880 3         9 $cell_def_text = $header_props->{'default_text'};
881 3         2 $cell_justify = $header_props->{'justify'};
882 3         3 $cell_bg_color = $header_props->{'bg_color'};
883 3         4 $cell_fg_color = $header_props->{'fg_color'};
884 3         2 $cell_bg_color_even= undef;
885 3         3 $cell_bg_color_odd = undef;
886 3         3 $cell_fg_color_even= undef;
887 3         4 $cell_fg_color_odd = undef;
888 3         2 $cell_h_rule_w = $header_props->{'h_rule_w'};
889 3         3 $cell_v_rule_w = $header_props->{'v_rule_w'};
890 3         3 $cell_h_rule_c = $header_props->{'h_rule_c'};
891 3         3 $cell_v_rule_c = $header_props->{'v_rule_c'};
892             } else {
893             # not header row, so initialize to undefined
894 46         94 $is_header_row = 0;
895 46         45 $cell_font = undef;
896 46         46 $cell_font_size = undef;
897 46         38 $cell_leading = undef;
898 46         46 $cell_height = undef;
899 46         35 $cell_pad_top = undef;
900 46         38 $cell_pad_right = undef;
901 46         38 $cell_pad_bot = undef;
902 46         45 $cell_pad_left = undef;
903 46         60 $cell_max_word_len = undef;
904 46         36 $cell_min_w = undef;
905 46         37 $cell_max_w = undef;
906 46         43 $cell_underline = undef;
907 46         34 $cell_def_text = undef;
908 46         39 $cell_justify = undef;
909 46         39 $cell_bg_color = undef;
910 46         37 $cell_fg_color = undef;
911 46         36 $cell_bg_color_even= undef;
912 46         42 $cell_bg_color_odd = undef;
913 46         35 $cell_fg_color_even= undef;
914 46         36 $cell_fg_color_odd = undef;
915 46         43 $cell_h_rule_w = undef;
916 46         38 $cell_v_rule_w = undef;
917 46         43 $cell_h_rule_c = undef;
918 46         41 $cell_v_rule_c = undef;
919             }
920              
921             # Get the most specific value if none was already set from header_props
922 49         76 $cell_font = find_value($cell_font,
923             'font', '', $fnt_obj, $GLOBALS);
924 49         65 $cell_font_size = find_value($cell_font_size,
925             'font_size', '', 0, $GLOBALS);
926 49 100       79 if ($cell_font_size == 0) {
927 21 50       25 if ($is_header_row) {
928 0         0 $cell_font_size = $fnt_size + 2;
929             } else {
930 21         28 $cell_font_size = $fnt_size;
931             }
932             }
933 49         66 $cell_leading = find_value($cell_leading, 'leading',
934             'leading', -1, $GLOBALS);
935 49 50       89 if ($cell_leading <= 0) {
936 49         86 $cell_leading = $cell_font_size * $leading_ratio;
937             }
938 49         60 $cell_height = find_value($cell_height,
939             'min_rh', '', 0, $GLOBALS);
940 49         66 $cell_pad_top = find_value($cell_pad_top, 'padding_top',
941             'padding', $padding_default,
942             $GLOBALS);
943 49         63 $cell_pad_right = find_value($cell_pad_right, 'padding_right',
944             'padding', $padding_default,
945             $GLOBALS);
946 49         88 $cell_pad_bot = find_value($cell_pad_bot, 'padding_bottom',
947             'padding', $padding_default,
948             $GLOBALS);
949 49         62 $cell_pad_left = find_value($cell_pad_left, 'padding_left',
950             'padding', $padding_default,
951             $GLOBALS);
952 49         62 $cell_max_word_len = find_value($cell_max_word_len,
953             'max_word_len', '',
954             $max_word_len, $GLOBALS);
955 49         62 $cell_min_w = find_value($cell_min_w, 'min_w',
956             '', undef, $GLOBALS);
957 49         93 $cell_max_w = find_value($cell_max_w, 'max_w',
958             '', undef, $GLOBALS);
959 49 50 33     102 if (defined $cell_max_w && defined $cell_min_w) {
960 0         0 $cell_max_w = max($cell_max_w, $cell_min_w);
961             }
962 49         64 $cell_underline = find_value($cell_underline,
963             'underline', '', $underline,
964             $GLOBALS);
965 49         67 $cell_def_text = find_value($cell_def_text, 'default_text',
966             '', $default_text, $GLOBALS);
967 49         59 $cell_justify = find_value($cell_justify, 'justify',
968             'justify', 'left', $GLOBALS);
969              
970             # cell bg may still be undef after this, fg must be defined
971 49 100       62 if ($is_header_row) {
972 3         4 $cell_bg_color = find_value($cell_bg_color, 'bg_color',
973             '', $h_bg_color_default,
974             $GLOBALS);
975 3         4 $cell_fg_color = find_value($cell_fg_color, 'fg_color',
976             '', $h_fg_color_default,
977             $GLOBALS);
978             # don't use even/odd colors in header
979             } else {
980 46         66 $cell_bg_color = find_value($cell_bg_color, 'bg_color',
981             '', undef, $GLOBALS);
982 46         75 $cell_fg_color = find_value($cell_fg_color, 'fg_color',
983             '', undef, $GLOBALS);
984 46         70 $cell_bg_color_even = find_value($cell_bg_color_even,
985             'bg_color_even', '', undef, $GLOBALS);
986 46         53 $cell_bg_color_odd = find_value($cell_bg_color_odd,
987             'bg_color_odd', '', undef, $GLOBALS);
988 46         58 $cell_fg_color_even = find_value($cell_fg_color_even,
989             'fg_color_even', '', undef, $GLOBALS);
990 46         61 $cell_fg_color_odd = find_value($cell_fg_color_odd,
991             'fg_color_odd', '', undef, $GLOBALS);
992             }
993 49         104 $cell_h_rule_w = find_value($cell_h_rule_w, 'h_rule_w',
994             'rule_w', $h_border_w, $GLOBALS);
995 49         65 $cell_v_rule_w = find_value($cell_v_rule_w, 'v_rule_w',
996             'rule_w', $v_border_w, $GLOBALS);
997 49         56 $cell_h_rule_c = find_value($cell_h_rule_c, 'h_rule_c',
998             'rule_c', $border_c, $GLOBALS);
999 49         57 $cell_v_rule_c = find_value($cell_v_rule_c, 'v_rule_c',
1000             'rule_c', $border_c, $GLOBALS);
1001              
1002             # Choose colors for this row. may still be 'undef' after this!
1003             # cell, column, row, global color settings always override
1004             # whatever _even/odd sets
1005 49         52 $bg_color = $cell_bg_color;
1006 49         47 $fg_color = $cell_fg_color;
1007 49 50       62 if ($oddeven_default) { # new method with consistent odd/even
1008 49 100       71 if (!defined $bg_color) {
1009 31 100       44 $bg_color = $row_is_odd ? $cell_bg_color_odd : $cell_bg_color_even;
1010             }
1011 49 100       59 if (!defined $fg_color) {
1012 37 100       50 $fg_color = $row_is_odd ? $cell_fg_color_odd : $cell_fg_color_even;
1013             }
1014             # don't toggle odd/even yet, wait til end of row
1015             } else { # old method with inconsistent odd/even
1016 0 0       0 if (!defined $bg_color) {
1017 0 0       0 $bg_color = $row_idx % 2 ? $cell_bg_color_even : $cell_bg_color_odd;
1018             }
1019 0 0       0 if (!defined $fg_color) {
1020 0 0       0 $fg_color = $row_idx % 2 ? $cell_fg_color_even : $cell_fg_color_odd;
1021             }
1022             }
1023             # force fg_color to have a value, but bg_color may remain undef
1024 49   66     114 $fg_color ||= $fg_color_default;
1025              
1026             ## check if so much padding that baseline forced below cell
1027             ## bottom, possibly resulting in infinite loop!
1028             #if ($cell_pad_top + $cell_pad_bot + $cell_leading > $cell_height) {
1029             # my $reduce = $cell_pad_top + $cell_pad_bot -
1030             # ($cell_height - $cell_leading);
1031             # carp "Warning! Vertical padding reduced by $reduce to fit cell[$row_idx][$col_idx]";
1032             # $cell_pad_top -= $reduce/2;
1033             # $cell_pad_bot -= $reduce/2;
1034             #}
1035              
1036             # Define the font y base position for this line.
1037 49         146 $text_start_y = $cur_y - $cell_pad_top - $cell_font_size;
1038              
1039             # VARIOUS WIDTHS:
1040             # $col_min_w->[$col_idx] the minimum needed for a column,
1041             # based on requested min_w and maximum word size (longest
1042             # word just fits). this is the running minimum, not the
1043             # per-row value.
1044             # $col_max_w->[$col_idx] the maximum needed for a column,
1045             # based on requested max_w and total length of text, as if
1046             # the longest entire cell is to be written out as one line.
1047             # this is the running maximum, not the per-row value.
1048             #
1049             # $calc_column_widths->[$col_idx] = calculated column widths
1050             # (at least the minimum requested and maximum word size)
1051             # apportioned across the full requested width. these are the
1052             # column widths you'll actually see drawn (before colspan).
1053             # $actual_column_widths[$row_idx][$col_idx] = calculated width
1054             # for this cell, increased by colspan (cols to right).
1055             #
1056             # $data_row_widths->[$col_idx] = cell content width list for
1057             # a row, first element of row_col_widths. could vary down a
1058             # column due to differing length of content.
1059             # $row_col_widths->[$row_idx] = list of max widths per row,
1060             # which can vary down a column due to differing length of
1061             # content.
1062             # $column_widths->[$col_idx] = list of maximum cell widths
1063             # across this row, used to load up $row_col_widths and
1064             # $h_row_widths (header).
1065              
1066             # Initialize cell font object
1067 49         158 $txt->font( $cell_font, $cell_font_size );
1068 49         271 $txt->fillcolor($fg_color);
1069              
1070             # make sure cell's text is never undef
1071 49   33     205 $data_row->[$col_idx] //= $cell_def_text;
1072              
1073             # Handle colspan
1074 49         50 my $c_cell_props = $cell_props->[$row_idx][$col_idx];
1075 49         53 my $this_cell_width = $calc_column_widths->[$col_idx];
1076 49 100 66     140 if ($c_cell_props && $c_cell_props->{'colspan'} && $c_cell_props->{'colspan'} > 1) {
      66        
1077 1         2 my $colspan = $c_cell_props->{'colspan'};
1078 1         3 for my $offset (1 .. $colspan - 1) {
1079 1 50       3 $this_cell_width += $calc_column_widths->[$col_idx + $offset]
1080             if $calc_column_widths->[$col_idx + $offset];
1081 1         3 $colspanned{$row_idx.'_'.($col_idx + $offset)} = 1;
1082             }
1083             }
1084 49         88 $this_cell_width = max($this_cell_width, $min_col_width);
1085 49         74 $actual_column_widths[$row_idx][$col_idx] = $this_cell_width;
1086              
1087 49         49 my %text_options;
1088 49 50       67 if ($cell_underline) {
1089 0         0 $text_options{'-underline'} = $cell_underline;
1090 0         0 $text_options{'-strokecolor'} = $fg_color;
1091             }
1092             # If the content is wider than the specified width,
1093             # we need to add the text as a text block
1094             # Otherwise just use the $page->text() method
1095 49         56 my $content = $data_row->[$col_idx];
1096 49 50       84 $content = $cell_def_text if $content eq '';
1097             # empty content? doesn't seem to do any harm
1098 49 100 33     223 if ( $content !~ m/(.\n.)/ and
      66        
1099             $data_row_widths->[$col_idx] and
1100             $data_row_widths->[$col_idx] <=
1101             $actual_column_widths[$row_idx][$col_idx] ) {
1102             # no embedded newlines (no multiple lines)
1103             # and the content width is <= calculated column width?
1104             # content will fit on one line, use text_* calls
1105 46 100       74 if ($cell_justify eq 'right') {
    100          
1106             # right justified before right padding
1107 5         18 $txt->translate($cur_x + $actual_column_widths[$row_idx][$col_idx] - $cell_pad_right, $text_start_y);
1108 5         26 $txt->text_right($content, %text_options);
1109             } elsif ($cell_justify eq 'center') {
1110             # center text within the margins (padding)
1111 6         20 $txt->translate($cur_x + $cell_pad_left + ($actual_column_widths[$row_idx][$col_idx] - $cell_pad_left - $cell_pad_right)/2, $text_start_y);
1112 6         30 $txt->text_center($content, %text_options);
1113             } else {
1114             # left justified after left padding
1115             # (text_left alias for text, in PDF::Builder only)
1116 35         76 $txt->translate($cur_x + $cell_pad_left, $text_start_y);
1117 35         178 $txt->text($content, %text_options);
1118             }
1119            
1120             } else {
1121 3         15 my ($width_of_last_line, $ypos_of_last_line,
1122             $left_over_text)
1123             = $self->text_block(
1124             $txt,
1125             $content,
1126             $row_idx, $col_idx,
1127             # mandatory args
1128             'x' => $cur_x + $cell_pad_left,
1129             'y' => $text_start_y,
1130             'w' => $actual_column_widths[$row_idx][$col_idx] -
1131             $cell_pad_left - $cell_pad_right,
1132             'h' => $cur_y - $bot_margin -
1133             $cell_pad_top - $cell_pad_bot,
1134             # non-mandatory args
1135             'font_size' => $cell_font_size,
1136             'leading' => $cell_leading,
1137             'align' => $cell_justify,
1138             'text_opt' => \%text_options,
1139             );
1140             # Desi - Removed $leading because of
1141             # fixed incorrect ypos bug in text_block
1142 3         10 $actual_row_height = max($actual_row_height,
1143             $cur_y - $ypos_of_last_line + $cell_pad_bot +
1144             ($cell_leading - $cell_font_size)*2.5);
1145             # 2.5 multiplier is a good-looking fudge factor to add a
1146             # little space between bottom of text and bottom of cell
1147              
1148             # at this point, actual_row_height is the used
1149             # height of this row, for purposes of background cell
1150             # color and left rule drawing. current_min_rh is left as
1151             # the height of one line + padding.
1152              
1153 3 50       5 if ( $left_over_text ) {
1154 0         0 $leftovers->[$col_idx] = $left_over_text;
1155 0         0 $do_leftovers = 1;
1156             }
1157             }
1158              
1159             # Hook to pass coordinates back - http://www.perlmonks.org/?node_id=754777
1160 49 100       213 if (ref $arg{'cell_render_hook'} eq 'CODE') {
1161 9         20 $arg{'cell_render_hook'}->(
1162             $page,
1163             $first_row,
1164             $row_idx,
1165             $col_idx,
1166             $cur_x,
1167             $cur_y-$actual_row_height,
1168             $actual_column_widths[$row_idx][$col_idx],
1169             $actual_row_height
1170             );
1171             }
1172              
1173 49         89 $cur_x += $actual_column_widths[$row_idx][$col_idx];
1174             # otherwise lose track of column-related settings
1175 49         58 $save_bg_color[$col_idx] = $bg_color;
1176 49         54 $save_fg_color[$col_idx] = $fg_color;
1177 49         52 $save_v_rule_w[$col_idx] = $cell_v_rule_w;
1178 49         56 $save_h_rule_w[$col_idx] = $cell_h_rule_w;
1179 49         70 $save_v_rule_c[$col_idx] = $cell_v_rule_c;
1180 49         127 $save_h_rule_c[$col_idx] = $cell_h_rule_c;
1181             } # done looping through columns for this row
1182 19 50       33 if ( $do_leftovers ) {
1183             # leftover text in row to output later as new-ish row?
1184 0         0 unshift @$data, $leftovers;
1185 0         0 unshift @$row_col_widths, $data_row_widths;
1186 0         0 unshift @$rows_height, $current_min_rh;
1187             # if push actual_row_height back onto rows_height, it will be
1188             # far too much in some cases, resulting in excess blank space at bottom.
1189             }
1190 19 50       33 if ($oddeven_default) { # new method with consistent odd/even
1191 19 100 100     47 if ( !($first_row and $do_headers) ) {
1192             # only toggle if not a header
1193 18         24 $row_is_odd = ! $row_is_odd;
1194             }
1195             }
1196              
1197             # Draw cell bgcolor
1198             # This has to be done separately from the text loop
1199             # because we do not know the final height of the cell until
1200             # all text has been drawn. Nevertheless, it ($gfx_bg) will
1201             # still be rendered before text ($txt).
1202 19         20 $cur_x = $xbase;
1203 19         38 for (my $col_idx = 0;
1204             $col_idx < scalar(@$data_row);
1205             $col_idx++) {
1206             # restore cell_bg_color, etc.
1207 50         56 $bg_color = $save_bg_color[$col_idx];
1208 50         48 $fg_color = $save_fg_color[$col_idx];
1209 50         43 $cell_v_rule_w = $save_v_rule_w[$col_idx];
1210 50         42 $cell_h_rule_w = $save_h_rule_w[$col_idx];
1211 50         51 $cell_v_rule_c = $save_v_rule_c[$col_idx];
1212 50         47 $cell_h_rule_c = $save_h_rule_c[$col_idx];
1213              
1214             # TBD rowspan!
1215 50 100 66     121 if (defined $bg_color &&
1216             !$colspanned{$row_idx.'_'.$col_idx}) {
1217 18         52 $gfx_bg->rect( $cur_x, $cur_y-$actual_row_height,
1218             $actual_column_widths[$row_idx][$col_idx], $actual_row_height);
1219 18         92 $gfx_bg->fillcolor($bg_color);
1220 18         76 $gfx_bg->fill();
1221             }
1222              
1223             # draw left vertical border of this cell unless leftmost
1224 50 100 66     226 if ($gfx && $cell_v_rule_w && $col_idx &&
      100        
      66        
1225             !$colspanned{$row_idx.'_'.$col_idx}) {
1226 23         42 $gfx->linewidth($cell_v_rule_w);
1227 23         95 $gfx->strokecolor($cell_v_rule_c);
1228 23         107 $gfx->move($cur_x, $cur_y-$actual_row_height);
1229 23 100       125 $gfx->vline( $cur_y - ($row_idx? 0: $h_border_w/2));
1230 23         82 $gfx->stroke(); # don't confuse different widths and colors
1231             }
1232              
1233             # draw bottom horizontal rule of this cell unless bottom
1234             # of page (no more data or not room for at least one line).
1235             # TBD fix up when implement rowspan
1236 50 100 66     195 if ($gfx && $cell_h_rule_w && scalar(@{$data}) &&
  36   100     88  
      66        
1237             $cur_y-$actual_row_height-$current_min_rh > $bot_margin ) {
1238 15         24 $gfx->linewidth($cell_h_rule_w);
1239 15         76 $gfx->strokecolor($cell_h_rule_c);
1240 15         65 $gfx->move($cur_x, $cur_y-$actual_row_height);
1241 15         69 $gfx->hline( $cur_x + $actual_column_widths[$row_idx][$col_idx] );
1242 15         60 $gfx->stroke(); # don't confuse different widths and colors
1243             }
1244              
1245 50         126 $cur_x += $calc_column_widths->[$col_idx];
1246             } # End of for (my $col_idx....
1247              
1248 19         22 $cur_y -= $actual_row_height;
1249              
1250 19 50       47 if ($do_leftovers) {
1251             # a row has been split across pages. undo bg toggle
1252 0         0 $row_is_odd = !$row_is_odd;
1253 0         0 $next_top_border = 2; # dashed line
1254             } else {
1255 19         22 $row_idx++;
1256 19         22 $next_top_border = 1; # solid line
1257             }
1258 19         56 $first_row = 0;
1259             } # End of Row_Loop for this page, and possibly whole table
1260              
1261             # draw bottom border on this page. first, is this very last row?
1262             # The line overlays and hides any odd business with vertical rules
1263             # in the last row
1264 11 100       13 if (!scalar(@{$data})) { $next_top_border = 0; }
  11         20  
  10         13  
1265 11 100 66     35 if ($gfx && $h_border_w) {
1266 8 50       19 if ($next_top_border == 0) {
    0          
1267             # last bottom border, use specified border
1268 8         21 $gfx->linewidth($h_border_w);
1269             } elsif ($next_top_border == 1) {
1270             # solid thin line at start of a row
1271 0         0 $gfx->linewidth($border_w_default);
1272             } else { # == 2
1273             # dashed thin line at contination in middle of row
1274 0         0 $gfx->linewidth($border_w_default);
1275 0         0 $gfx->linedash($dashed_rule_default);
1276             }
1277             # leave next_top_border for next page top of continued table
1278 8         47 $gfx->strokecolor($border_c);
1279 8         41 $gfx->move( $xbase-$v_border_w/2 , $cur_y );
1280 8         52 $gfx->hline($xbase + $width + $v_border_w/2);
1281 8         35 $gfx->stroke();
1282 8         31 $gfx->linedash();
1283             }
1284              
1285 11 50       40 if ($gfx) {
1286 11 100       19 if ($v_border_w) {
1287             # Draw left and right table borders
1288             # These overlay and hide any odd business with horizontal
1289             # rules at the left or right edge
1290 8         16 $gfx->linewidth($v_border_w);
1291 8         53 $gfx->move( $xbase, $table_top_y);
1292 8         41 $gfx->vline( $cur_y );
1293 8         42 $gfx->move( $xbase + $width, $table_top_y);
1294 8         41 $gfx->vline( $cur_y );
1295             }
1296              
1297             # draw all the unrendered lines
1298 11         61 $gfx->stroke();
1299             }
1300 11         60 $pg_cnt++; # on a spillover page
1301             } # End of while (scalar(@{$data})) next row, adding new page if necessary
1302              
1303 10         152 return ($page, --$pg_cnt, $cur_y);
1304             } # end of table()
1305              
1306             ############################################################
1307             # find a value that might be set in a default or in a global
1308             # or column/row/cell specific parameter. fixed order of search
1309             # is cell/header properties, column properties, row properties,
1310             # fallback sequences (e.g., padding_left inherits from padding),
1311             # global default
1312             ############################################################
1313              
1314             sub find_value {
1315 1764     1764 0 2132 my ($cell_val, $name, $fallback, $default, $GLOBALS) = @_;
1316             # $fallback can be '' (will be skipped)
1317              
1318 1764         1963 my ($cell_props, $col_props, $row_props, $row_idx, $col_idx, $argref) =
1319             @$GLOBALS;
1320             # $row_idx should be 0 for a header entry
1321 1764         4267 my %arg = %$argref;
1322             # $default should never be undefined, except for specific cases!
1323 1764 50 100     4722 if (!defined $default &&
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      66        
1324             ($name ne 'underline' &&
1325             $name ne 'bg_color' && $name ne 'fg_color' &&
1326             $name ne 'bg_color_even' && $name ne 'bg_color_odd' &&
1327             $name ne 'fg_color_even' && $name ne 'fg_color_odd' &&
1328             $name ne 'min_w' && $name ne 'max_w') ) {
1329 0         0 carp "Error! find_value() default value undefined for '$name'\n";
1330             }
1331              
1332             # upon entry, $cell_val is usually either undefined (data row) or
1333             # header property setting (in which case, already set and we're done here)
1334 1764 50       2336 $cell_val = $cell_props->[$row_idx][$col_idx]->{$name} if
1335             !defined $cell_val;
1336 1764 100 100     3358 $cell_val = $cell_props->[$row_idx][$col_idx]->{$fallback} if
1337             !defined $cell_val && $fallback ne '';
1338 1764 100       2107 $cell_val = $col_props->[$col_idx]->{$name} if
1339             !defined $cell_val;
1340 1764 100 100     3179 $cell_val = $col_props->[$col_idx]->{$fallback} if
1341             !defined $cell_val && $fallback ne '';
1342 1764 100       2044 $cell_val = $row_props->[$row_idx]->{$name} if
1343             !defined $cell_val;
1344 1764 100 100     3085 $cell_val = $row_props->[$row_idx]->{$fallback} if
1345             !defined $cell_val && $fallback ne '';
1346 1764 100       2119 $cell_val = $arg{$name} if
1347             !defined $cell_val;
1348 1764 100 100     3070 $cell_val = $arg{$fallback} if
1349             !defined $cell_val && $fallback ne '';
1350              
1351             # final court of appeal is the global default (usually defined)
1352 1764 100       1966 if (!defined $cell_val) {
1353 1663         1467 $cell_val = $default;
1354             }
1355              
1356 1764         2899 return $cell_val;
1357             } # end of find_value()
1358              
1359             ############################################################
1360             # text_block - utility method to build multi-paragraph blocks of text
1361             #
1362             # Parameters:
1363             # $text_object the TEXT object used to output to the PDF
1364             # $text the text to be formatted
1365             # %arg settings to control the formatting and
1366             # output.
1367             # mandatory: x, y, w, h (block position and dimensions)
1368             # defaults are provided for:
1369             # font_size (global $font_size_default)
1370             # leading (font_size * global $leading_ratio)
1371             # no defaults for:
1372             # text_opt (such as underline flag and color)
1373             # parspace (extra vertical space before a paragraph)
1374             # hang (text for ?)
1375             # indent (indentation amount)
1376             # fpindent (first paragraph indent amount)
1377             # flindent (first line indent amount)
1378             # align (justification left|center|right|fulljustify|justify)
1379             #
1380             # $text comes in as one string, possibly with \n embedded.
1381             # split at \n to form 2 or more @paragraphs. each @paragraph
1382             # is a @paragraphs element split on ' ' (list of words to
1383             # fill the available width). one word at a time is moved
1384             # from @paragraph to @line, until the width of the joined
1385             # @line (with ' ' between words) can't be any larger.
1386             # TBD: deal with multiple spaces between words
1387             ############################################################
1388              
1389             sub text_block {
1390 3     3 0 5 my $self = shift;
1391 3         2 my $text_object = shift;
1392 3         4 my $text = shift; # The text to be displayed
1393 3         4 my $row_idx = shift; # cell row,col for debug
1394 3         3 my $col_idx = shift;
1395 3         11 my %arg = @_; # Additional Arguments
1396              
1397 3         6 my ( $align, $xpos, $ypos, $xbase, $ybase, $line_width, $wordspace, $endw , $width, $height) =
1398             ( undef , undef, undef, undef , undef , undef , undef , undef , undef , undef );
1399 3         6 my @line = (); # Temp data array with words on one line
1400 3         2 my %width = (); # The width of every unique word in the given text
1401 3         3 my %text_options = %{ $arg{'text_opt'} };
  3         6  
1402              
1403             # Try to provide backward compatibility. "-" starting key name is optional
1404 3         16 foreach my $key (keys %arg) {
1405 24         19 my $newkey = $key;
1406 24 50       37 if ($newkey =~ s#^-##) {
1407 0         0 $arg{$newkey} = $arg{$key};
1408 0         0 delete $arg{$key};
1409             }
1410             }
1411             #####
1412              
1413             #---
1414             # Let's check mandatory parameters with no default values
1415             #---
1416 3   50     8 $xbase = $arg{'x'} || -1;
1417 3   50     7 $ybase = $arg{'y'} || -1;
1418 3   50     6 $width = $arg{'w'} || -1;
1419 3   50     10 $height = $arg{'h'} || -1;
1420 3 50       7 unless ( $xbase > 0 ) {
1421 0         0 carp "Error: Left Edge of Block is NOT defined!\n";
1422 0         0 return (0, $ybase, '');
1423             }
1424 3 50       12 unless ( $ybase > 0 ) {
1425 0         0 carp "Error: Base Line of Block is NOT defined!\n";
1426 0         0 return (0, $ybase, '');
1427             }
1428 3 50       6 unless ( $width > 0 ) {
1429 0         0 carp "Error: Width of Block is NOT defined!\n";
1430 0         0 return (0, $ybase, '');
1431             }
1432 3 50       4 unless ( $height > 0 ) {
1433 0         0 carp "Error: Height of Block is NOT defined!\n";
1434 0         0 return (0, $ybase, '');
1435             }
1436              
1437             # Check if any text to display. If called from table(), should have
1438             # default text by the time of the call, so this is really as a failsafe
1439             # for standalone text_block() calls. Note that '' won't work!
1440 3 50 33     16 unless ( defined( $text) and length($text) > 0 ) {
1441             # carp "Warning: No input text found. Use dummy '-'.\n";
1442             # $text = $empty_cell_text;
1443 0         0 $text = ' ';
1444             }
1445              
1446             # Strip any and Split the text into paragraphs
1447             # if you're on a platform that uses \r to end a line (old Macs?)...
1448             # we're in text_block() only if long line or \n's seen
1449             # @paragraphs is list of paragraphs (long lines)
1450             # @paragraph is list of words within present paragraph (long line)
1451 3         4 $text =~ s/\r//g;
1452 3         11 my @paragraphs = split(/\n/, $text);
1453              
1454             # Width between lines (leading) in points
1455 3   33     6 my $font_size = $arg{'font_size'} || $font_size_default;
1456 3 50 33     11 my $line_space = defined $arg{'leading'} && $arg{'leading'} > 0 ? $arg{'leading'} : undef;
1457 3   33     4 $line_space ||= $font_size * $leading_ratio;
1458             # leading must be at least font size
1459 3 50       6 $line_space = $font_size * $leading_ratio if $font_size > $line_space;
1460              
1461             # Calculate width of all words
1462 3         6 my $space_width = $text_object->advancewidth("\x20");
1463 3         10 my %word_width;
1464 3         25 my @text_words = split(/\s+/, $text);
1465 3         5 foreach (@text_words) {
1466 13 50       35 next if exists $word_width{$_};
1467 13         19 $word_width{$_} = $text_object->advancewidth($_);
1468             }
1469              
1470             # get word list for first paragraph
1471 3         14 my @paragraph = split(' ', shift(@paragraphs));
1472 3         4 my $first_line = 1; # first line of THIS paragraph
1473 3         4 my $paragraph_number = 1;
1474              
1475             # Little Init
1476 3         2 $xpos = $xbase;
1477 3         4 $ypos = $ybase;
1478 3         12 $ypos = $ybase + $line_space;
1479             # bottom_border doesn't need to consider pad_bot, as we're only considering
1480             # the space actually available within the cell, already reduced by padding.
1481 3         4 my $bottom_border = $ypos - $height;
1482              
1483             # While we can add another line. No handling of widows and orphans.
1484 3         5 while ( $ypos >= $bottom_border + $line_space ) {
1485             # Is there any text to render ?
1486 7 100       11 unless (@paragraph) {
1487             # Finish if nothing left of all the paragraphs in text
1488 3 50       4 last unless scalar @paragraphs; # another paragraph to process?
1489             # Else take one paragraph (long line) from the text
1490 0         0 @paragraph = split(' ', shift( @paragraphs ) );
1491 0         0 $paragraph_number++;
1492              
1493             # extra space between paragraphs? only if a previous paragraph
1494 0 0 0     0 $ypos -= $arg{'parspace'} if $arg{'parspace'} and
1495             $paragraph_number > 1;
1496 0 0       0 last unless $ypos >= $bottom_border;
1497             }
1498 4         5 $ypos -= $line_space;
1499 4         5 $xpos = $xbase;
1500              
1501             # While there's room on the line, add another word
1502 4         5 @line = ();
1503 4         5 $line_width = 0;
1504             # TBD what exactly is hang supposed to do, interaction with
1505             # indent, flindent, fpindent AND effect on min cell width
1506 4 50 66     39 if ( $first_line && exists $arg{'hang'} ) {
    50 66        
    50 33        
    50 33        
      33        
      33        
1507             # fixed text to output first, for first line of a paragraph
1508             # TBD Note that hang text is not yet checked for min_col_width or
1509             # max_word_len, and other indents could make line too wide for col!
1510 0         0 my $hang_width = $text_object->advancewidth($arg{'hang'});
1511              
1512 0         0 $text_object->translate( $xpos, $ypos );
1513 0         0 $text_object->text( $arg{'hang'} );
1514              
1515 0         0 $xpos += $hang_width;
1516 0         0 $line_width += $hang_width;
1517 0 0       0 $arg{'indent'} += $hang_width if $paragraph_number == 1;
1518             } elsif ( $first_line && exists $arg{'flindent'} &&
1519             $arg{'flindent'} > 0 ) {
1520             # amount to indent on first line of a paragraph
1521 0         0 $xpos += $arg{'flindent'};
1522 0         0 $line_width += $arg{'flindent'};
1523             } elsif ( $paragraph_number == 1 && exists $arg{'fpindent'} &&
1524             $arg{'fpindent'} > 0 ) {
1525             # amount to indent first paragraph's first line TBD ??
1526 0         0 $xpos += $arg{'fpindent'};
1527 0         0 $line_width += $arg{'fpindent'};
1528             } elsif ( exists $arg{'indent'} &&
1529             $arg{'indent'} > 0 ) {
1530             # amount to indent first line of following paragraphs
1531 0         0 $xpos += $arg{'indent'};
1532 0         0 $line_width += $arg{'indent'};
1533             }
1534              
1535             # Let's take from paragraph as many words as we can put
1536             # into $width - $indent. repeatedly test with "just one more" word
1537             # from paragraph list, until overflow.
1538             # TBD might be more efficient (as originally intended?) to build
1539             # library of word widths and add them together until "too big",
1540             # back off.
1541             # TBD don't forget to properly handle runs of more than one space.
1542 4         5 while ( @paragraph ) {
1543 14 100       19 if ( !@line ) {
1544             # first time through, @line is empty
1545             # first word in paragraph SHOULD fit!!
1546             # TBD: what if $line_width > 0??? due to indent, etc.?
1547             # add 0.01 as safety
1548 4 50       7 if ( $text_object->advancewidth( $paragraph[0] ) +
1549             $line_width <= $width+0.01 ) {
1550 4         33 push(@line, shift(@paragraph));
1551 4 100       11 next if @paragraph;
1552             } else {
1553             # this should never happen, but just in case, to
1554             # prevent an infinite loop...
1555 0         0 die("!!! Error !!! first word in paragraph for row $row_idx, col $col_idx '$paragraph[0]' doesn't fit into empty line!");
1556             }
1557             } else {
1558             # @line has text in it already
1559 10 100       26 if ( $text_object->advancewidth( join(" ", @line)." " . $paragraph[0] ) +
1560             $line_width <= $width ) {
1561 9         35 push(@line, shift(@paragraph));
1562 9 100       18 next if @paragraph;
1563             }
1564             }
1565 4         6 last;
1566             }
1567 4         11 $line_width += $text_object->advancewidth(join(' ', @line));
1568              
1569             # calculate the space width (width to use for a space)
1570 4   50     14 $align = $arg{'align'} || 'left';
1571 4 50 33     13 if ( $align eq 'fulljustify' or
      33        
1572             ($align eq 'justify' and @paragraph)) {
1573 0 0       0 @line = split(//,$line[0]) if scalar(@line) == 1;
1574 0 0       0 if (scalar(@line) > 1) {
1575 0         0 $wordspace = ($width - $line_width) / (scalar(@line) - 1);
1576             } else {
1577 0         0 $wordspace = 0; # effectively left-aligned for single word
1578             }
1579 0         0 $align = 'justify';
1580             } else {
1581             # not adding extra spacing between words, just real space
1582 4 50       8 $align = 'left' if $align eq 'justify';
1583 4         4 $wordspace = $space_width;
1584             }
1585              
1586 4         5 $line_width += $wordspace * (scalar(@line) - 1);
1587              
1588 4 50       7 if ( $align eq 'justify') {
1589 0         0 foreach my $word (@line) {
1590 0         0 $text_object->translate( $xpos, $ypos );
1591 0         0 $text_object->text( $word );
1592 0 0       0 $xpos += ($word_width{$word} + $wordspace) if (@line);
1593             }
1594 0         0 $endw = $width;
1595             } else {
1596             # calculate the left hand position of the line
1597             # if ( $align eq 'right' ) {
1598             # $xpos += $width - $line_width;
1599             # } elsif ( $align eq 'center' ) {
1600             # $xpos += ( $width - $line_width ) / 2;
1601             # }
1602              
1603             # render the line. TBD This may not work right with indents!
1604 4 50       18 if ($align eq 'right') {
    50          
1605 0         0 $text_object->translate( $xpos+$width, $ypos );
1606 0         0 $endw = $text_object->text_right(join(' ', @line), %text_options);
1607             } elsif ($align eq 'center') {
1608 0         0 $text_object->translate( $xpos + $width/2, $ypos );
1609 0         0 $endw = $text_object->text_center(join(' ', @line), %text_options);
1610             } else {
1611 4         9 $text_object->translate( $xpos, $ypos );
1612 4         24 $endw = $text_object->text(join(' ', @line), %text_options);
1613             }
1614             }
1615 4         27 $first_line = 0;
1616             } # End of while (fitting within vertical space)
1617              
1618             # any leftovers of current paragraph? will return as first new paragraph
1619 3 50       7 unshift(@paragraphs, join(' ',@paragraph)) if scalar(@paragraph);
1620              
1621 3         14 return ($endw, $ypos, join("\n", @paragraphs))
1622             } # End of text_block()
1623              
1624             1;
1625              
1626             __END__