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