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