line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# vim: ts=8 sw=8 tw=0 ai nu noet |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# (C) Daniel Kasak: dan@entropy.homelinux.org ... |
4
|
|
|
|
|
|
|
# ... with contributions from Bill Hess and Cosimo Streppone |
5
|
|
|
|
|
|
|
# ( see the changelog for details ) |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# See COPYRIGHT file for full license |
8
|
|
|
|
|
|
|
# |
9
|
|
|
|
|
|
|
# See 'man PDF::ReportWriter' for full documentation |
10
|
|
|
|
|
|
|
|
11
|
2
|
|
|
2
|
|
4650
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
101
|
|
12
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
11
|
no warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
98
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
package PDF::ReportWriter; |
16
|
|
|
|
|
|
|
|
17
|
2
|
|
|
2
|
|
4821
|
use PDF::API2; |
|
2
|
|
|
|
|
812763
|
|
|
2
|
|
|
|
|
74
|
|
18
|
2
|
|
|
2
|
|
2283
|
use Image::Size; |
|
2
|
|
|
|
|
10513
|
|
|
2
|
|
|
|
|
138
|
|
19
|
|
|
|
|
|
|
|
20
|
2
|
|
|
2
|
|
20
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
138
|
|
21
|
|
|
|
|
|
|
|
22
|
2
|
|
|
2
|
|
14
|
use constant mm => 72/25.4; # 25.4 mm in an inch, 72 points in an inch |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
137
|
|
23
|
2
|
|
|
2
|
|
11
|
use constant in => 72; # 72 points in an inch |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
120
|
|
24
|
|
|
|
|
|
|
|
25
|
2
|
|
|
2
|
|
11
|
use constant A4_x => 210 * mm; # x points in an A4 page ( 595.2755 ) |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
109
|
|
26
|
2
|
|
|
2
|
|
9
|
use constant A4_y => 297 * mm; # y points in an A4 page ( 841.8897 ) |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
103
|
|
27
|
|
|
|
|
|
|
|
28
|
2
|
|
|
2
|
|
11
|
use constant letter_x => 8.5 * in; # x points in a letter page |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
112
|
|
29
|
2
|
|
|
2
|
|
11
|
use constant letter_y => 11 * in; # y points in a letter page |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
104
|
|
30
|
|
|
|
|
|
|
|
31
|
2
|
|
|
2
|
|
12
|
use constant bsize_x => 11 * in; # x points in a B size page |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
106
|
|
32
|
2
|
|
|
2
|
|
10
|
use constant bsize_y => 17 * in; # y points in a B size page |
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
99
|
|
33
|
|
|
|
|
|
|
|
34
|
2
|
|
|
2
|
|
10
|
use constant legal_x => 11 * in; # x points in a legal page |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
99
|
|
35
|
2
|
|
|
2
|
|
11
|
use constant legal_y => 14 * in; # y points in a legal page |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
102
|
|
36
|
|
|
|
|
|
|
|
37
|
2
|
|
|
2
|
|
11
|
use constant TRUE => 1; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
131
|
|
38
|
2
|
|
|
2
|
|
10
|
use constant FALSE => 0; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
94
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
BEGIN { |
41
|
2
|
|
|
2
|
|
15896
|
$PDF::ReportWriter::VERSION = '1.5'; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub new { |
45
|
|
|
|
|
|
|
|
46
|
0
|
|
|
0
|
1
|
0
|
my ( $class, $options ) = @_; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# Create new object |
49
|
0
|
|
|
|
|
0
|
my $self = {}; |
50
|
0
|
|
|
|
|
0
|
bless $self, $class; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Initialize object state |
53
|
0
|
|
|
|
|
0
|
$self->parse_options($options); |
54
|
|
|
|
|
|
|
|
55
|
0
|
|
|
|
|
0
|
return $self; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# |
59
|
|
|
|
|
|
|
# render_report( $xml, $data_arrayref ) |
60
|
|
|
|
|
|
|
# |
61
|
|
|
|
|
|
|
# $xml can be either an xml file or any kind of object that |
62
|
|
|
|
|
|
|
# supports `load()' and `get_data()' |
63
|
|
|
|
|
|
|
# |
64
|
|
|
|
|
|
|
# Take report definition, add report data and |
65
|
|
|
|
|
|
|
# shake well. Your report is ready. |
66
|
|
|
|
|
|
|
# |
67
|
|
|
|
|
|
|
sub render_report |
68
|
|
|
|
|
|
|
{ |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Use P::R::Report to handle xml report loading |
71
|
0
|
|
|
0
|
1
|
0
|
require PDF::ReportWriter::Report; |
72
|
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
0
|
my ( $self, $xml, $data_records ) = @_; |
74
|
0
|
|
|
|
|
0
|
my $report; |
75
|
|
|
|
|
|
|
my $config; |
76
|
0
|
|
|
|
|
0
|
my $data; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# First parameter can be a report xml filename |
79
|
|
|
|
|
|
|
# or PDF::ReportWriter::Report object. Check and load the report profile |
80
|
0
|
0
|
|
|
|
0
|
if( ! $xml ) { |
81
|
0
|
|
|
|
|
0
|
die "Specify an xml report file or PDF::ReportWriter::Report object!"; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# $xml is a filename? |
85
|
0
|
0
|
|
|
|
0
|
if ( ! ref $xml ) { |
|
|
0
|
|
|
|
|
|
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# Try loading the report definition file |
88
|
0
|
0
|
|
|
|
0
|
unless( $report = PDF::ReportWriter::Report->new({ report => $xml }) ) { |
89
|
|
|
|
|
|
|
# Can't load xml report file |
90
|
0
|
|
|
|
|
0
|
die qq(Can't load xml report file $xml); |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# $xml is a PDF::ReportWriter::Report or something that can `load()'? |
94
|
|
|
|
|
|
|
} elsif( $xml->can('load') ) { |
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
0
|
$report = $xml; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Try loading the XML report profile and see if something breaks |
100
|
0
|
|
|
|
|
0
|
eval { |
101
|
0
|
|
|
|
|
0
|
$config = $report->load(); |
102
|
|
|
|
|
|
|
#use Data::Dumper; |
103
|
|
|
|
|
|
|
#print Dumper($config); |
104
|
|
|
|
|
|
|
}; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Report error to user |
107
|
0
|
0
|
|
|
|
0
|
if( $@ ) |
108
|
|
|
|
|
|
|
{ |
109
|
0
|
|
|
|
|
0
|
die qq(Can't load xml report profile from $xml object: $@); |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Ok, profile "definition" data structure is our hash |
113
|
|
|
|
|
|
|
# of main report options |
114
|
0
|
|
|
|
|
0
|
$self->parse_options( $config->{definition} ); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Profile "data" structure is our hash to be passed |
117
|
|
|
|
|
|
|
# render_data() function. |
118
|
0
|
|
|
|
|
0
|
$data = $config->{data}; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Store report object for later use (resave to xml) |
121
|
0
|
|
|
|
|
0
|
$self->{__report} = $report; |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# If we already have report data, we are done |
124
|
0
|
0
|
|
|
|
0
|
if( ! defined $data_records ) { |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Report object's `get_data()' method can be used to populate report data |
127
|
|
|
|
|
|
|
# with name of data source to use |
128
|
0
|
0
|
|
|
|
0
|
if( $report->can('get_data') ) { |
129
|
|
|
|
|
|
|
# XXX Change `detail' in `report', or `main' ?? |
130
|
0
|
|
|
|
|
0
|
$data_records = $report->get_data('detail'); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# "data" hash structure must be filled with real records |
135
|
0
|
|
|
|
|
0
|
$data->{data_array} = $data_records; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Store "data" section for later use (save to xml) |
138
|
0
|
|
|
|
|
0
|
$self->{data} = # XXX Remove? |
139
|
|
|
|
|
|
|
$self->{__report}->{data} = $data; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
# Fire! |
142
|
0
|
|
|
|
|
0
|
$self->render_data( $data) ; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# |
147
|
|
|
|
|
|
|
# Returns the current page object (PDF::API2::Page) we are working on |
148
|
|
|
|
|
|
|
# |
149
|
|
|
|
|
|
|
sub current_page |
150
|
|
|
|
|
|
|
{ |
151
|
0
|
|
|
0
|
0
|
0
|
my $self = $_[0]; |
152
|
0
|
|
|
|
|
0
|
my $page_list = $self->{pages}; |
153
|
|
|
|
|
|
|
|
154
|
0
|
0
|
0
|
|
|
0
|
if( ref $page_list eq 'ARRAY' && scalar @$page_list ) |
155
|
|
|
|
|
|
|
{ |
156
|
0
|
|
|
|
|
0
|
return $page_list->[ $#$page_list ]; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
else |
159
|
|
|
|
|
|
|
{ |
160
|
0
|
|
|
|
|
0
|
return undef; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
sub report |
165
|
|
|
|
|
|
|
{ |
166
|
0
|
|
|
0
|
0
|
0
|
my $self = $_[0]; |
167
|
0
|
|
|
|
|
0
|
return $self->{__report}; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub parse_options |
171
|
|
|
|
|
|
|
{ |
172
|
|
|
|
|
|
|
|
173
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $opt ) = @_; |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Create a new PDF document if needed |
176
|
0
|
|
0
|
|
|
0
|
$self->{pdf} ||= PDF::API2->new; |
177
|
|
|
|
|
|
|
|
178
|
0
|
0
|
|
|
|
0
|
if ( ! defined $opt ) |
179
|
|
|
|
|
|
|
{ |
180
|
0
|
|
|
|
|
0
|
return( $self ); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# Check for old margin settings and translate to new ones |
184
|
0
|
0
|
|
|
|
0
|
if ( exists $opt->{y_margin} ) { |
185
|
0
|
|
|
|
|
0
|
$opt->{upper_margin} = $opt->{y_margin}; |
186
|
0
|
|
|
|
|
0
|
$opt->{lower_margin} = $opt->{y_margin}; |
187
|
0
|
|
|
|
|
0
|
delete $opt->{y_margin}; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
0
|
0
|
|
|
|
0
|
if ( exists $opt->{x_margin} ) { |
191
|
0
|
|
|
|
|
0
|
$opt->{left_margin} = $opt->{x_margin}; |
192
|
0
|
|
|
|
|
0
|
$opt->{right_margin} = $opt->{x_margin}; |
193
|
0
|
|
|
|
|
0
|
delete $opt->{x_margin}; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Store options in the __report member that we will use |
197
|
|
|
|
|
|
|
# to export to XML format |
198
|
0
|
|
|
|
|
0
|
$self->{__report}->{definition} = $opt; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# XXX |
201
|
|
|
|
|
|
|
# Store some option keys into main object |
202
|
|
|
|
|
|
|
# Now this is necessary for all code to work correctly |
203
|
|
|
|
|
|
|
# |
204
|
0
|
|
|
|
|
0
|
for ( qw( destination upper_margin lower_margin left_margin right_margin debug template ) ) { |
205
|
0
|
|
|
|
|
0
|
$self->{$_} = $opt->{$_} |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
0
|
0
|
0
|
|
|
0
|
if ( $opt->{paper} eq "A4" ) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
209
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
0
|
$self->{page_width} = A4_x; |
211
|
0
|
|
|
|
|
0
|
$self->{page_height} = A4_y; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
} elsif ( $opt->{paper} eq "Letter" || $opt->{paper} eq "letter" ) { |
214
|
|
|
|
|
|
|
|
215
|
0
|
|
|
|
|
0
|
$self->{page_width} = letter_x; |
216
|
0
|
|
|
|
|
0
|
$self->{page_height} = letter_y; |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
} elsif ( $opt->{paper} eq "bsize" || $opt->{paper} eq "Bsize" ) { |
219
|
|
|
|
|
|
|
|
220
|
0
|
|
|
|
|
0
|
$self->{page_width} = bsize_x; |
221
|
0
|
|
|
|
|
0
|
$self->{page_height} = bsize_y; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
} elsif ( $opt->{paper} eq "Legal" || $opt->{paper} eq "legal" ) { |
224
|
|
|
|
|
|
|
|
225
|
0
|
|
|
|
|
0
|
$self->{page_width} = legal_x; |
226
|
0
|
|
|
|
|
0
|
$self->{page_height} = legal_y; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Parse user defined format `150 x 120 mm', or `29.7 x 21.0 cm', or `500X300' |
229
|
|
|
|
|
|
|
# Default unit is `mm' unless specified. Accepted units: `mm', `in' |
230
|
|
|
|
|
|
|
} elsif ( $opt->{paper} =~ /^\s*([\d\.]+)\s*[xX]\s*([\d\.]+)\s*(\w*)$/ ) { |
231
|
|
|
|
|
|
|
|
232
|
0
|
|
0
|
|
|
0
|
my $unit = lc($3) || 'mm'; |
233
|
0
|
|
|
|
|
0
|
$self->{page_width} = $1; |
234
|
0
|
|
|
|
|
0
|
$self->{page_height} = $2; |
235
|
|
|
|
|
|
|
|
236
|
0
|
0
|
|
|
|
0
|
if ( $unit eq 'mm' ) { |
|
|
0
|
|
|
|
|
|
237
|
0
|
|
|
|
|
0
|
$self->{page_width} *= &mm; |
238
|
0
|
|
|
|
|
0
|
$self->{page_height} *= &mm; |
239
|
|
|
|
|
|
|
} elsif( $unit eq 'in' ) { |
240
|
0
|
|
|
|
|
0
|
$self->{page_width} *= ∈ |
241
|
0
|
|
|
|
|
0
|
$self->{page_height} *= ∈ |
242
|
|
|
|
|
|
|
} else { |
243
|
0
|
|
|
|
|
0
|
die 'Unsupported measure unit: ' . $unit . "\n"; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
} else { |
247
|
0
|
|
|
|
|
0
|
die "Unsupported paper format: " . $opt->{paper} . "\n"; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Swap width/height in case of landscape orientation |
251
|
0
|
0
|
0
|
|
|
0
|
if( exists $opt->{orientation} && $opt->{orientation} ) { |
252
|
|
|
|
|
|
|
|
253
|
0
|
0
|
|
|
|
0
|
if( $opt->{orientation} eq 'landscape' ) { |
|
|
0
|
|
|
|
|
|
254
|
0
|
|
|
|
|
0
|
($self->{page_width}, $self->{page_height}) = |
255
|
|
|
|
|
|
|
($self->{page_height}, $self->{page_width}); |
256
|
|
|
|
|
|
|
} elsif( $opt->{orientation} ne 'portrait' ) { |
257
|
0
|
|
|
|
|
0
|
die 'Unsupported orientation: ' . $opt->{orientation} . "\n"; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# |
262
|
|
|
|
|
|
|
# Now initialize object |
263
|
|
|
|
|
|
|
# |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# Set some info stuff |
266
|
0
|
|
|
|
|
0
|
my $localtime = localtime time; |
267
|
|
|
|
|
|
|
|
268
|
0
|
|
0
|
|
|
0
|
$self->{pdf}->info( |
269
|
|
|
|
|
|
|
Author => $opt->{info}->{Author}, |
270
|
|
|
|
|
|
|
CreationDate => $localtime, |
271
|
|
|
|
|
|
|
# Should we allow a different creator? |
272
|
|
|
|
|
|
|
Creator => $opt->{info}->{Creator} || "PDF::ReportWriter $PDF::ReportWriter::VERSION", |
273
|
|
|
|
|
|
|
Keywords => $opt->{info}->{Keywords}, |
274
|
|
|
|
|
|
|
ModDate => $localtime, |
275
|
|
|
|
|
|
|
Subject => $opt->{info}->{Subject}, |
276
|
|
|
|
|
|
|
Title => $opt->{info}->{Title} |
277
|
|
|
|
|
|
|
); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Add requested fonts |
280
|
0
|
|
0
|
|
|
0
|
$opt->{font_list} ||= $opt->{font} || [ 'Helvetica' ]; |
|
|
|
0
|
|
|
|
|
281
|
|
|
|
|
|
|
|
282
|
0
|
|
|
|
|
0
|
for my $font ( @{$opt->{font_list}} ) { |
|
0
|
|
|
|
|
0
|
|
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# Roman fonts are easy |
285
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Roman} = $self->{pdf}->corefont( $font, -encoding => 'latin1'); |
286
|
|
|
|
|
|
|
# The rest are f'n ridiculous. Adobe either didn't think about this, or are just stoopid |
287
|
0
|
0
|
|
|
|
0
|
if ($font eq 'Courier') { |
288
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Bold} = $self->{pdf}->corefont( "Courier-Bold", -encoding => 'latin1'); |
289
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Italic} = $self->{pdf}->corefont( "Courier-Oblique", -encoding => 'latin1'); |
290
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{BoldItalic} = $self->{pdf}->corefont( "Courier-BoldOblique", -encoding => 'latin1'); |
291
|
|
|
|
|
|
|
} |
292
|
0
|
0
|
|
|
|
0
|
if ($font eq 'Helvetica') { |
293
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Bold} = $self->{pdf}->corefont( "Helvetica-Bold", -encoding => 'latin1'); |
294
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Italic} = $self->{pdf}->corefont( "Helvetica-Oblique", -encoding => 'latin1'); |
295
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{BoldItalic} = $self->{pdf}->corefont( "Helvetica-BoldOblique",-encoding => 'latin1'); |
296
|
|
|
|
|
|
|
} |
297
|
0
|
0
|
|
|
|
0
|
if ($font eq 'Times') { |
298
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Bold} = $self->{pdf}->corefont( "Times-Bold", -encoding => 'latin1'); |
299
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{Italic} = $self->{pdf}->corefont( "Times-Italic", -encoding => 'latin1'); |
300
|
0
|
|
|
|
|
0
|
$self->{fonts}->{$font}->{BoldItalic} = $self->{pdf}->corefont( "Times-BoldItalic", -encoding => 'latin1'); |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# Default report font size to 12 in case a default hasn't been supplied |
305
|
0
|
|
0
|
|
|
0
|
$self->{default_font_size} = $opt->{default_font_size} || 12; |
306
|
0
|
|
0
|
|
|
0
|
$self->{default_font} = $opt->{default_font} || 'Helvetica'; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# Mark date/time of document generation |
309
|
0
|
|
|
|
|
0
|
$self->{__generationtime} = $localtime; |
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
return( $self ); |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub setup_cell_definitions { |
316
|
|
|
|
|
|
|
|
317
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $cell_array, $type, $group, $group_type ) = @_; |
318
|
|
|
|
|
|
|
|
319
|
0
|
|
|
|
|
0
|
my $x = $self->{left_margin}; |
320
|
0
|
|
|
|
|
0
|
my $row = 0; |
321
|
0
|
|
|
|
|
0
|
my $cell_counter = 0; |
322
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
0
|
for my $cell ( @{$cell_array} ) { |
|
0
|
|
|
|
|
0
|
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# Support multi-line row definitions |
326
|
0
|
0
|
|
|
|
0
|
if ( $x >= $self->{page_width} - $self->{right_margin} ) { |
327
|
0
|
|
|
|
|
0
|
$row ++; |
328
|
0
|
|
|
|
|
0
|
$x = $self->{left_margin}; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
0
|
$cell->{row} = $row; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# The cell's left-hand border position |
334
|
0
|
|
|
|
|
0
|
$cell->{x_border} = $x; |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# The cell's font size - user defined by cell, or from the report default |
337
|
0
|
0
|
|
|
|
0
|
if ( ! $cell->{font_size} ) { |
338
|
0
|
|
|
|
|
0
|
$cell->{font_size} = $self->{default_font_size}; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# The cell's text whitespace ( the minimum distance between the cell border and cell text ) |
342
|
|
|
|
|
|
|
# Default to half the font size if not given |
343
|
0
|
0
|
|
|
|
0
|
if ( ! exists $cell->{text_whitespace} ) { |
344
|
0
|
|
|
|
|
0
|
$cell->{text_whitespace} = $cell->{font_size} >> 1; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Calculate cell height depending on type, etc... |
348
|
0
|
|
|
|
|
0
|
$cell->{height} = $self->calculate_cell_height( $cell ); |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# The cell's left-hand text position |
351
|
0
|
|
|
|
|
0
|
$cell->{x_text} = $x + $cell->{text_whitespace}; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# The cell's full width ( border to border ) |
354
|
0
|
|
|
|
|
0
|
$cell->{full_width} = ( $self->{page_width} - ( $self->{left_margin} + $self->{right_margin} ) ) * $cell->{percent} / 100; |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# The cell's maximum width of text |
357
|
0
|
|
|
|
|
0
|
$cell->{text_width} = $cell->{full_width} - ( $cell->{text_whitespace} * 2 ); |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# We also need to set the data-level or header/footer-level max_cell_height |
360
|
|
|
|
|
|
|
# This refers to the height of the actual cell ... |
361
|
|
|
|
|
|
|
# ... ie ie it doesn't include upper_buffer and lower_buffer whitespace |
362
|
0
|
0
|
|
|
|
0
|
if ( $type eq "data" ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
363
|
0
|
0
|
|
|
|
0
|
if ( $cell->{height} > $self->{data}->{max_cell_height} ) { |
364
|
0
|
|
|
|
|
0
|
$self->{data}->{max_cell_height} = $cell->{height}; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
# Default to the data-level background if there is none defined for this cell |
367
|
|
|
|
|
|
|
# We don't do this for page headers / footers, because I don't think this |
368
|
|
|
|
|
|
|
# is appropriate default behaviour for these ( ie usually doesn't look good ) |
369
|
0
|
0
|
|
|
|
0
|
if ( ! $cell->{background} ) { |
370
|
0
|
|
|
|
|
0
|
$cell->{background} = $self->{data}->{background}; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
# Populate the cell_mapping hash so we can easily get hold of fields via their name |
373
|
0
|
|
|
|
|
0
|
$self->{data}->{cell_mapping}->{ $cell->{name} } = $cell_counter; |
374
|
|
|
|
|
|
|
} elsif ( $type eq "field_headers" ) { |
375
|
0
|
0
|
|
|
|
0
|
if ( $cell->{height} > $self->{data}->{max_field_header_height} ) { |
376
|
0
|
|
|
|
|
0
|
$self->{data}->{max_field_header_height} = $cell->{height}; |
377
|
|
|
|
|
|
|
} |
378
|
0
|
0
|
|
|
|
0
|
if ( ! $cell->{background} ) { |
379
|
0
|
|
|
|
|
0
|
$cell->{background} = $self->{data}->{headings}->{background}; |
380
|
|
|
|
|
|
|
} |
381
|
0
|
|
|
|
|
0
|
$cell->{wrap_text} = TRUE; |
382
|
|
|
|
|
|
|
} elsif ( $type eq "page_header" ) { |
383
|
0
|
0
|
|
|
|
0
|
if ( $cell->{height} > $self->{data}->{page_header_max_cell_height} ) { |
384
|
0
|
|
|
|
|
0
|
$self->{data}->{page_header_max_cell_height} = $cell->{height}; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
} elsif ( $type eq "page_footer" ) { |
387
|
0
|
0
|
|
|
|
0
|
if ( $cell->{height} > $self->{data}->{page_footer_max_cell_height} ) { |
388
|
0
|
|
|
|
|
0
|
$self->{data}->{page_footer_max_cell_height} = $cell->{height}; |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
} elsif ( $type eq "group" ) { |
391
|
|
|
|
|
|
|
|
392
|
0
|
0
|
|
|
|
0
|
if ( $cell->{height} > $group->{$group_type . "_max_cell_height"} ) { |
393
|
0
|
|
|
|
|
0
|
$group->{$group_type . "_max_cell_height"} = $cell->{height}; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# For aggregate functions, we need the name of the group, which is used later |
397
|
|
|
|
|
|
|
# to retrieve the aggregate values ( which are stored against the group, |
398
|
|
|
|
|
|
|
# hence the need for the group name ). However when rendering a row, |
399
|
|
|
|
|
|
|
# we don't have access to the group *name*, so storing it in the 'text' |
400
|
|
|
|
|
|
|
# key is a nice way around this |
401
|
0
|
0
|
|
|
|
0
|
if ( exists $cell->{aggregate_source} ) { |
402
|
0
|
|
|
|
|
0
|
$cell->{text} = $group->{name}; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Initialise group aggregate results |
406
|
0
|
|
|
|
|
0
|
$cell->{group_results}->{$group->{name}} = 0; |
407
|
0
|
|
|
|
|
0
|
$cell->{grand_aggregate_result}->{$group->{name}} = 0; |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# Set 'bold' key for legacy behaviour anything other than data cells and images |
412
|
0
|
0
|
0
|
|
|
0
|
if ( $type ne "data" && ! $cell->{image} && ! exists $cell->{bold} ) { |
|
|
|
0
|
|
|
|
|
413
|
0
|
|
|
|
|
0
|
$cell->{bold} = TRUE; |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
0
|
0
|
|
|
|
0
|
if ( $cell->{image} ) { |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Default to a buffer of 1 to surround images, |
419
|
|
|
|
|
|
|
# otherwise they overlap cell borders |
420
|
0
|
0
|
|
|
|
0
|
if ( ! exists $cell->{image}->{buffer} ) { |
421
|
0
|
|
|
|
|
0
|
$cell->{image}->{buffer} = 1; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
# Initialise the tmp hash that we store temporary image dimensions in later |
425
|
0
|
|
|
|
|
0
|
$cell->{image}->{tmp} = {}; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# Convert old 'type' key to the new 'format' key |
430
|
|
|
|
|
|
|
# But *don't* do anything with types not listed here. Cosimo is using |
431
|
|
|
|
|
|
|
# this key for barcode stuff, and this is handled completely separately of number formatting |
432
|
|
|
|
|
|
|
|
433
|
0
|
0
|
|
|
|
0
|
if ( exists $cell->{type} ) { |
434
|
|
|
|
|
|
|
|
435
|
0
|
0
|
|
|
|
0
|
if ( $cell->{type} eq "currency" ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
0
|
carp( "\nEncountered a legacy type key with 'currency'.\n" |
438
|
|
|
|
|
|
|
. " Converting to the new 'format' key.\n" |
439
|
|
|
|
|
|
|
. " Please update your code accordingly\n" ); |
440
|
|
|
|
|
|
|
|
441
|
0
|
|
|
|
|
0
|
$cell->{format} = { |
442
|
|
|
|
|
|
|
currency => TRUE, |
443
|
|
|
|
|
|
|
decimal_places => 2, |
444
|
|
|
|
|
|
|
decimal_fill => TRUE, |
445
|
|
|
|
|
|
|
separate_thousands => TRUE |
446
|
|
|
|
|
|
|
}; |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
0
|
delete $cell->{type}; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
} elsif ( $cell->{type} eq "currency:no_fill" ) { |
451
|
|
|
|
|
|
|
|
452
|
0
|
|
|
|
|
0
|
carp( "\nEncountered a legacy type key with 'currency:nofill'.\n" |
453
|
|
|
|
|
|
|
. " Converting to the new 'format' key.\n" |
454
|
|
|
|
|
|
|
. " Please update your code accordingly\n\n" ); |
455
|
|
|
|
|
|
|
|
456
|
0
|
|
|
|
|
0
|
$cell->{format} = { |
457
|
|
|
|
|
|
|
currency => TRUE, |
458
|
|
|
|
|
|
|
decimal_places => 2, |
459
|
|
|
|
|
|
|
decimal_fill => FALSE, |
460
|
|
|
|
|
|
|
separate_thousands => TRUE |
461
|
|
|
|
|
|
|
}; |
462
|
|
|
|
|
|
|
|
463
|
0
|
|
|
|
|
0
|
delete $cell->{type}; |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
} elsif ( $cell->{type} eq "thousands_separated" ) { |
466
|
|
|
|
|
|
|
|
467
|
0
|
|
|
|
|
0
|
carp( "\nEncountered a legacy type key with 'thousands_separated'.\n" |
468
|
|
|
|
|
|
|
. " Converting to the new 'format' key.\n" |
469
|
|
|
|
|
|
|
. " Please update your code accordingly\n\n" ); |
470
|
|
|
|
|
|
|
|
471
|
0
|
|
|
|
|
0
|
$cell->{format} = { |
472
|
|
|
|
|
|
|
separate_thousands => TRUE |
473
|
|
|
|
|
|
|
}; |
474
|
|
|
|
|
|
|
|
475
|
0
|
|
|
|
|
0
|
delete $cell->{type}; |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
# Move along to the next position |
482
|
0
|
|
|
|
|
0
|
$x += $cell->{full_width}; |
483
|
|
|
|
|
|
|
|
484
|
0
|
|
|
|
|
0
|
$cell_counter ++; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Set up upper_buffer and lower_buffer values on groups |
489
|
0
|
0
|
|
|
|
0
|
if ( $type eq 'group' ) { |
490
|
0
|
0
|
|
|
|
0
|
if ( ! exists $group->{$group_type . '_upper_buffer'} ) { |
491
|
|
|
|
|
|
|
# Default to 0 - legacy behaviour |
492
|
0
|
|
|
|
|
0
|
$group->{$group_type . '_upper_buffer'} = 0; |
493
|
|
|
|
|
|
|
} |
494
|
0
|
0
|
|
|
|
0
|
if ( ! exists $group->{$group_type . '_lower_buffer'} ) { |
495
|
|
|
|
|
|
|
# Default to 0 - legacy behaviour |
496
|
0
|
|
|
|
|
0
|
$group->{$group_type . '_lower_buffer'} = 0; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# Set up data-level upper_buffer and lower_buffer values |
501
|
0
|
0
|
|
|
|
0
|
if ( $type eq 'data' ) { |
502
|
0
|
0
|
|
|
|
0
|
if ( ! exists $self->{data}->{upper_buffer} ) { |
503
|
|
|
|
|
|
|
# Default to 0, which was the previous behaviour |
504
|
0
|
|
|
|
|
0
|
$self->{data}->{upper_buffer} = 0; |
505
|
|
|
|
|
|
|
} |
506
|
0
|
0
|
|
|
|
0
|
if ( ! exists $self->{data}->{lower_buffer} ) { |
507
|
|
|
|
|
|
|
# Default to 0, which was the previous behaviour |
508
|
0
|
|
|
|
|
0
|
$self->{data}->{lower_buffer} = 0; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
# Set up field_header upper_buffer and lower_buffer values |
513
|
0
|
0
|
|
|
|
0
|
if ( $type eq 'field_headers' ) { |
514
|
0
|
0
|
|
|
|
0
|
if ( ! exists $self->{data}->{field_headers_upper_buffer} ) { |
515
|
0
|
|
|
|
|
0
|
$self->{data}->{field_headers_upper_buffer} = 0; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
sub render_data { |
522
|
|
|
|
|
|
|
|
523
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $data ) = @_; |
524
|
|
|
|
|
|
|
|
525
|
0
|
|
|
|
|
0
|
$self->{data} = $data; |
526
|
|
|
|
|
|
|
|
527
|
0
|
|
|
|
|
0
|
$self->{data}->{cell_height} = 0; |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
# Complete field definitions ... |
530
|
|
|
|
|
|
|
# ... calculate the position of each cell's borders and text positioning |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
# Create a default background object if $self->{cell_borders} is set ( ie legacy support ) |
533
|
0
|
0
|
|
|
|
0
|
if ( $self->{data}->{cell_borders} ) { |
534
|
0
|
|
|
|
|
0
|
$self->{data}->{background} = { |
535
|
|
|
|
|
|
|
border => "grey" |
536
|
|
|
|
|
|
|
}; |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
# Normal cells |
540
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $self->{data}->{fields}, "data" ); |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# Field headers |
543
|
0
|
0
|
|
|
|
0
|
if ( ! $self->{data}->{no_field_headers} ) { |
544
|
|
|
|
|
|
|
# Construct the field_headers definition if required ... |
545
|
|
|
|
|
|
|
# ... ie provide legacy behaviour if no field_headers array provided |
546
|
0
|
0
|
|
|
|
0
|
if ( ! $self->{data}->{field_headers} ) { |
547
|
0
|
|
|
|
|
0
|
foreach my $field ( @{$self->{data}->{fields}} ) { |
|
0
|
|
|
|
|
0
|
|
548
|
0
|
|
|
|
|
0
|
push @{$self->{data}->{field_headers}}, |
|
0
|
|
|
|
|
0
|
|
549
|
|
|
|
|
|
|
{ |
550
|
|
|
|
|
|
|
name => $field->{name}, |
551
|
|
|
|
|
|
|
percent => $field->{percent}, |
552
|
|
|
|
|
|
|
bold => TRUE, |
553
|
|
|
|
|
|
|
font_size => $field->{font_size}, |
554
|
|
|
|
|
|
|
text_whitespace => $field->{text_whitespace}, |
555
|
|
|
|
|
|
|
align => $field->{header_align}, |
556
|
|
|
|
|
|
|
colour => $field->{header_colour} |
557
|
|
|
|
|
|
|
}; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
# And now continue with the normal setup ... |
561
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $self->{data}->{field_headers}, "field_headers" ); |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
# Page headers |
565
|
0
|
0
|
|
|
|
0
|
if ( $self->{data}->{page}->{header} ) { |
566
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $self->{data}->{page}->{header}, "page_header" ); |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# Page footers |
570
|
0
|
0
|
0
|
|
|
0
|
if ( $self->{data}->{page}->{footer} ) { |
|
|
0
|
|
|
|
|
|
571
|
|
|
|
|
|
|
|
572
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $self->{data}->{page}->{footer}, "page_footer" ); |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
} elsif ( ! $self->{data}->{page}->{footer} && ! $self->{data}->{page}->{footerless} ) { |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
# Set a default page footer if we haven't been explicitely told not to |
577
|
0
|
|
|
|
|
0
|
$self->{data}->{cell_height} = 12; # Default text_whitespace of font size * .5 |
578
|
|
|
|
|
|
|
|
579
|
0
|
|
|
|
|
0
|
$self->{data}->{page}->{footer} = [ |
580
|
|
|
|
|
|
|
{ |
581
|
|
|
|
|
|
|
percent => 50, |
582
|
|
|
|
|
|
|
font_size => 8, |
583
|
|
|
|
|
|
|
text => "Rendered on \%TIME\%", |
584
|
|
|
|
|
|
|
align => 'left', |
585
|
|
|
|
|
|
|
bold => FALSE |
586
|
|
|
|
|
|
|
}, |
587
|
|
|
|
|
|
|
{ |
588
|
|
|
|
|
|
|
percent => 50, |
589
|
|
|
|
|
|
|
font_size => 8, |
590
|
|
|
|
|
|
|
text => "Page \%PAGE\% of \%PAGES\%", |
591
|
|
|
|
|
|
|
align => 'right', |
592
|
|
|
|
|
|
|
bold => FALSE |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
]; |
595
|
|
|
|
|
|
|
|
596
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $self->{data}->{page}->{footer}, 'page_footer' ); |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# Groups |
600
|
0
|
|
|
|
|
0
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
0
|
|
601
|
|
|
|
|
|
|
|
602
|
0
|
|
|
|
|
0
|
for my $group_type ( qw / header footer / ) { |
603
|
0
|
0
|
|
|
|
0
|
if ( $group->{$group_type} ) { |
604
|
0
|
|
|
|
|
0
|
$self->setup_cell_definitions( $group->{$group_type}, 'group', $group, $group_type ); |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
# Set all group values to a special character so we recognise that we are entering |
608
|
|
|
|
|
|
|
# a new value for each of them ... particularly the GrandTotal group |
609
|
0
|
|
|
|
|
0
|
$group->{value} = '!'; |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# Set the data_column of the GrandTotals group so the user doesn't have to specify it |
612
|
|
|
|
|
|
|
|
613
|
0
|
0
|
|
|
|
0
|
next unless $group->{name} eq 'GrandTotals'; |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# Check that there is at least one record in the data array, or this assignment triggers |
616
|
|
|
|
|
|
|
# an error about undefined ARRAY reference... |
617
|
|
|
|
|
|
|
|
618
|
0
|
|
|
|
|
0
|
my $data_ref = $self->{data}->{data_array}; |
619
|
0
|
0
|
0
|
|
|
0
|
if ( |
620
|
|
|
|
|
|
|
ref ( $data_ref ) eq 'ARRAY' |
621
|
|
|
|
|
|
|
&& ref ( $data_ref->[0] ) eq 'ARRAY' |
622
|
|
|
|
|
|
|
) { |
623
|
0
|
|
|
|
|
0
|
$group->{data_column} = scalar ( @{( $data_ref->[0] )} ); |
|
0
|
|
|
|
|
0
|
|
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# Create an array for the group header queue ( otherwise new_page() won't work so well ) |
628
|
0
|
|
|
|
|
0
|
$self->{group_header_queue} = []; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Create a new page if we have none ( ie at the start of the report ) |
631
|
0
|
0
|
|
|
|
0
|
if ( ! $self->{pages} ) { |
632
|
0
|
|
|
|
|
0
|
$self->new_page; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
# Calculate the y space needed for page footers |
636
|
0
|
|
|
|
|
0
|
my $size_calculation = $self->calculate_y_needed( |
637
|
|
|
|
|
|
|
{ |
638
|
|
|
|
|
|
|
cells => $self->{data}->{page}->{footer}, |
639
|
|
|
|
|
|
|
max_cell_height => $self->{data}->{page_footer_max_cell_height} |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
); |
642
|
|
|
|
|
|
|
|
643
|
0
|
|
|
|
|
0
|
$self->{page_footer_and_margin} = $size_calculation->{current_height} + $self->{lower_margin}; |
644
|
|
|
|
|
|
|
|
645
|
0
|
|
|
|
|
0
|
my $row_counter = 0; |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# Reset the 'need_data_header' flag - if there aren't any groups, this won't we reset |
648
|
0
|
|
|
|
|
0
|
$self->{need_data_header} = TRUE; |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
# Main loop |
651
|
0
|
|
|
|
|
0
|
for my $row ( @{$self->{data}->{data_array}} ) { |
|
0
|
|
|
|
|
0
|
|
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# Assemble the Group Header queue ... firstly assuming we *don't* require |
654
|
|
|
|
|
|
|
# a page break due to a lack of remaining paper. assemble_group_header_queue() |
655
|
|
|
|
|
|
|
# returns whether any of the new groups encounted have requested a page break |
656
|
|
|
|
|
|
|
|
657
|
0
|
|
|
|
|
0
|
my $want_new_page = $self->assemble_group_header_queue( |
658
|
|
|
|
|
|
|
$row, |
659
|
|
|
|
|
|
|
$row_counter, |
660
|
|
|
|
|
|
|
FALSE |
661
|
|
|
|
|
|
|
); |
662
|
|
|
|
|
|
|
|
663
|
0
|
0
|
|
|
|
0
|
if ( ! $want_new_page ) { |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# If none of the groups specifically requested a page break, check |
666
|
|
|
|
|
|
|
# whether everything will fit on the page |
667
|
|
|
|
|
|
|
|
668
|
0
|
|
|
|
|
0
|
my $size_calculation = $self->calculate_y_needed( |
669
|
|
|
|
|
|
|
{ |
670
|
|
|
|
|
|
|
cells => $self->{data}->{fields}, |
671
|
|
|
|
|
|
|
max_cell_height => $self->{data}->{max_cell_height}, |
672
|
|
|
|
|
|
|
row => $row |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
); |
675
|
|
|
|
|
|
|
|
676
|
0
|
0
|
|
|
|
0
|
if ( $self->{y} - ( $size_calculation->{y_needed} + $self->{page_footer_and_margin} ) < 0 ) { |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
# Our 1st set of queued headers & 1 row of data spills over the page. |
679
|
|
|
|
|
|
|
# We need to re-create the group header queue, and force $want_new_page |
680
|
|
|
|
|
|
|
# so that assemble_group_header_queue() knows this and adds all headers |
681
|
|
|
|
|
|
|
# that we need ( ie so we pick up reprinting headers that may not have been |
682
|
|
|
|
|
|
|
# added in the first pass because it wasn't known at the time that we were |
683
|
|
|
|
|
|
|
# taking a new page |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
# First though, we have to reset the group values in all currently queued headers, |
686
|
|
|
|
|
|
|
# so they get re-detected on the 2nd pass |
687
|
0
|
|
|
|
|
0
|
foreach my $queued_group ( @{$self->{group_header_queue}} ) { |
|
0
|
|
|
|
|
0
|
|
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
# Loop through our groups to find the one with the corresponding name |
690
|
|
|
|
|
|
|
# TODO We need to create a group_mapping hash so this is not required |
691
|
0
|
|
|
|
|
0
|
foreach my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
0
|
|
692
|
0
|
0
|
|
|
|
0
|
if ( $group->{name} eq $queued_group->{group}->{name} ) { |
693
|
0
|
|
|
|
|
0
|
$group->{value} = "!"; |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
|
699
|
0
|
|
|
|
|
0
|
$self->{group_header_queue} = undef; |
700
|
|
|
|
|
|
|
|
701
|
0
|
|
|
|
|
0
|
$want_new_page = $self->assemble_group_header_queue( |
702
|
|
|
|
|
|
|
$row, |
703
|
|
|
|
|
|
|
$row_counter, |
704
|
|
|
|
|
|
|
TRUE |
705
|
|
|
|
|
|
|
); |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
} |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
# We're using $row_counter here to detect whether we've actually printed |
712
|
|
|
|
|
|
|
# any data yet or not - we don't want to page break on the 1st page ... |
713
|
0
|
0
|
0
|
|
|
0
|
if ( $want_new_page && $row_counter ) { |
714
|
0
|
|
|
|
|
0
|
$self->new_page; |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
$self->render_row( |
718
|
0
|
|
|
|
|
0
|
$self->{data}->{fields}, |
719
|
|
|
|
|
|
|
$row, |
720
|
|
|
|
|
|
|
'data', |
721
|
|
|
|
|
|
|
$self->{data}->{max_cell_height}, |
722
|
|
|
|
|
|
|
$self->{data}->{upper_buffer}, |
723
|
|
|
|
|
|
|
$self->{data}->{lower_buffer} |
724
|
|
|
|
|
|
|
); |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# Reset the need_data_header flag after rendering a data row ... |
727
|
|
|
|
|
|
|
# ... this gets reset when entering a new group |
728
|
0
|
|
|
|
|
0
|
$self->{need_data_header} = FALSE; |
729
|
|
|
|
|
|
|
|
730
|
0
|
|
|
|
|
0
|
$row_counter ++; |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
# The final group footers will not have been triggered ( only happens when we get a *new* group ), so we do them now |
735
|
0
|
|
|
|
|
0
|
foreach my $group ( reverse @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
0
|
|
736
|
0
|
0
|
|
|
|
0
|
if ( $group->{footer} ) { |
737
|
0
|
|
|
|
|
0
|
$self->group_footer($group); |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
} |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
# Move down some more at the end of this pass |
742
|
0
|
|
|
|
|
0
|
$self->{y} -= $self->{data}->{max_cell_height}; |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
sub assemble_group_header_queue { |
747
|
|
|
|
|
|
|
|
748
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $row, $row_counter, $want_new_page ) = @_; |
749
|
|
|
|
|
|
|
|
750
|
0
|
|
|
|
|
0
|
foreach my $group ( reverse @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
0
|
|
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
# If we've entered a new group value, * OR * |
753
|
|
|
|
|
|
|
# - We're rendering gruop heavers because a new page has been triggered |
754
|
|
|
|
|
|
|
# ( $want_new_page is already set - by a lower-level group ) * AND * |
755
|
|
|
|
|
|
|
# - This group has the 'reprinting_header' key set |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
#if ( $want_new_page && $group->{reprinting_header} ) { |
758
|
|
|
|
|
|
|
|
759
|
0
|
0
|
0
|
|
|
0
|
if ( ( $group->{value} ne $$row[$group->{data_column}] ) || ( $want_new_page && $group->{reprinting_header} ) ) { |
|
|
|
0
|
|
|
|
|
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
# Remember to page break if we've been told to |
762
|
0
|
0
|
|
|
|
0
|
if ( $group->{page_break} ) { |
763
|
0
|
|
|
|
|
0
|
$want_new_page = TRUE; |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
# Only do a group footer if we have a ( non-zero ) value in $row_counter |
767
|
|
|
|
|
|
|
# ( ie if we've rendered at least 1 row of data so far ) |
768
|
|
|
|
|
|
|
# * AND * $want_new_page is NOT set |
769
|
|
|
|
|
|
|
# If $want_new_page IS set, then this is our 2nd run through here, and we've already |
770
|
|
|
|
|
|
|
# printed group footers |
771
|
|
|
|
|
|
|
|
772
|
0
|
0
|
0
|
|
|
0
|
if ( $row_counter && $group->{footer} && ! $want_new_page ) { |
|
|
|
0
|
|
|
|
|
773
|
0
|
|
|
|
|
0
|
$self->group_footer($group); |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
# Queue headers for rendering in the data cycle |
777
|
|
|
|
|
|
|
# ... prevents rendering a header before the last group footer is done |
778
|
0
|
0
|
|
|
|
0
|
if ( $group->{header} ) { |
779
|
0
|
|
|
|
|
0
|
push |
780
|
0
|
|
|
|
|
0
|
@{$self->{group_header_queue}}, |
781
|
|
|
|
|
|
|
{ |
782
|
|
|
|
|
|
|
group => $group, |
783
|
|
|
|
|
|
|
value => $$row[$group->{data_column}] |
784
|
|
|
|
|
|
|
}; |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
0
|
|
|
|
|
0
|
$self->{need_data_header} = TRUE; # Remember that we need to render a data header afterwoods |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
# If we're entering a new group, reset group totals |
790
|
0
|
0
|
|
|
|
0
|
if ( $group->{value} ne $$row[$group->{data_column}] ) { |
791
|
0
|
|
|
|
|
0
|
for my $field ( @{ $self->{data}->{fields} } ) { |
|
0
|
|
|
|
|
0
|
|
792
|
0
|
|
|
|
|
0
|
$field->{group_results}->{$group->{name}} = 0; |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
# Store new group value |
797
|
0
|
|
|
|
|
0
|
$group->{value} = $$row[$group->{data_column}]; |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
0
|
|
|
|
|
0
|
return $want_new_page; |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
sub fetch_group_results { |
808
|
|
|
|
|
|
|
|
809
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $options ) = @_; |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# This is a convenience function that returns the group aggregate value |
812
|
|
|
|
|
|
|
# for a given cell / group combination |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
# First do a little error checking |
815
|
0
|
0
|
|
|
|
0
|
if ( ! exists $self->{data}->{cell_mapping}->{ $options->{cell} } ) { |
816
|
0
|
|
|
|
|
0
|
carp( "\nPDF::ReportWriter::fetch_group_results called with an invalid cell: $options->{cell}\n\n" ); |
817
|
0
|
|
|
|
|
0
|
return; |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
|
820
|
0
|
0
|
|
|
|
0
|
if ( ! exists $self->{data}->{fields}[ $self->{data}->{cell_mapping}->{ $options->{cell} } ]->{group_results}->{ $options->{group} } ) { |
821
|
0
|
|
|
|
|
0
|
caro( "\nPDF::ReportWriter::fetch_group_results called with an invalid group: $options->{group} ...\n" |
822
|
|
|
|
|
|
|
. " ... check that the cell $options->{cell} has an aggregate function defined, and that the group $options->{group} exists\n" ); |
823
|
0
|
|
|
|
|
0
|
return; |
824
|
|
|
|
|
|
|
} |
825
|
|
|
|
|
|
|
|
826
|
0
|
|
|
|
|
0
|
return $self->{data}->{fields}[ $self->{data}->{cell_mapping}->{ $options->{cell} } ]->{group_results}->{ $options->{group} }; |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
} |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
# Define a new page like the PDF template (if template is specified) |
831
|
|
|
|
|
|
|
# or create a new page from scratch... |
832
|
|
|
|
|
|
|
sub page_template |
833
|
|
|
|
|
|
|
{ |
834
|
|
|
|
|
|
|
|
835
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
836
|
0
|
|
0
|
|
|
0
|
my $pdf_tmpl = shift || $self->{template}; # TODO document page_template and optional override |
837
|
0
|
|
|
|
|
0
|
my $new_page; |
838
|
0
|
|
|
|
|
0
|
my $user_warned = 0; |
839
|
|
|
|
|
|
|
|
840
|
0
|
0
|
0
|
|
|
0
|
if(defined $pdf_tmpl && $pdf_tmpl) |
841
|
|
|
|
|
|
|
{ |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
# Try to open template page |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
# TODO Cache this object to include a new page without |
846
|
|
|
|
|
|
|
# repeated opening of template file |
847
|
0
|
0
|
|
|
|
0
|
if( my $pdf_doc = PDF::API2->open($pdf_tmpl) ) |
848
|
|
|
|
|
|
|
{ |
849
|
|
|
|
|
|
|
# Template opened, import first page |
850
|
0
|
|
|
|
|
0
|
$new_page = $self->{pdf}->importpage($pdf_doc, 1); |
851
|
|
|
|
|
|
|
} |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
# Warn user in case of invalid template file |
854
|
0
|
0
|
0
|
|
|
0
|
unless($new_page || $user_warned) |
855
|
|
|
|
|
|
|
{ |
856
|
0
|
|
|
|
|
0
|
warn "Defined page template $pdf_tmpl not valid. Creating empty page."; |
857
|
0
|
|
|
|
|
0
|
$user_warned = 1; |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
# Generate an empty page if no valid page was extracted |
863
|
|
|
|
|
|
|
# from the template or there was no template... |
864
|
0
|
|
0
|
|
|
0
|
$self->{pdf} ||= PDF::API2->new(); # XXX |
865
|
0
|
|
0
|
|
|
0
|
$new_page ||= $self->{pdf}->page; |
866
|
|
|
|
|
|
|
|
867
|
0
|
|
|
|
|
0
|
return ($new_page); |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
sub new_page { |
872
|
|
|
|
|
|
|
|
873
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
# Create a new page and eventually apply pdf template |
876
|
0
|
|
|
|
|
0
|
my $page = $self->page_template; |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
# Set page dimensions |
879
|
0
|
|
|
|
|
0
|
$page->mediabox( $self->{page_width}, $self->{page_height} ); |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
# Create a new txt object for the page |
882
|
0
|
|
|
|
|
0
|
$self->{txt} = $page->text; |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
# Set y to the top of the page |
885
|
0
|
|
|
|
|
0
|
$self->{y} = $self->{page_height} - $self->{upper_margin}; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
# Remember that we need to print a data header |
888
|
0
|
|
|
|
|
0
|
$self->{need_data_header} = TRUE; |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
# Create a new gfx object for our lines |
891
|
0
|
|
|
|
|
0
|
$self->{line} = $page->gfx; |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# And a shape object for cell backgrounds and stuff |
894
|
|
|
|
|
|
|
# We *need* to call ->gfx with a *positive* value to make it render first ... |
895
|
|
|
|
|
|
|
# ... otherwise it won't be the background - it will be the foreground! |
896
|
0
|
|
|
|
|
0
|
$self->{shape} = $page->gfx(1); |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
# Append our page footer definition to an array - we store one per page, and render |
899
|
|
|
|
|
|
|
# them immediately prior to saving the PDF, so we can say "Page n of m" etc |
900
|
0
|
|
|
|
|
0
|
push @{$self->{page_footers}}, $self->{data}->{page}->{footer}; |
|
0
|
|
|
|
|
0
|
|
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
# Push new page onto array of pages |
903
|
0
|
|
|
|
|
0
|
push @{$self->{pages}}, $page; |
|
0
|
|
|
|
|
0
|
|
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
# Render page header if defined |
906
|
0
|
0
|
|
|
|
0
|
if ( $self->{data}->{page}->{header} ) { |
907
|
0
|
|
|
|
|
0
|
$self->render_row( |
908
|
|
|
|
|
|
|
$self->{data}->{page}->{header}, |
909
|
|
|
|
|
|
|
undef, |
910
|
|
|
|
|
|
|
'page_header', |
911
|
|
|
|
|
|
|
$self->{data}->{page_header_max_cell_height}, |
912
|
|
|
|
|
|
|
0, # Page headers don't need |
913
|
|
|
|
|
|
|
0 # upper / lower buffers |
914
|
|
|
|
|
|
|
# TODO Should we should add upper / buffers to page headers? |
915
|
|
|
|
|
|
|
); |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
# Renderer any group headers that have been set as 'reprinting_header' |
919
|
|
|
|
|
|
|
# ( but not if the group has the special value ! which means that we haven't started yet, |
920
|
|
|
|
|
|
|
# and also not if we've got group headers already queued ) |
921
|
0
|
|
|
|
|
0
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
0
|
|
922
|
0
|
0
|
0
|
|
|
0
|
if ( ( ! $self->{group_header_queue} ) |
|
|
|
0
|
|
|
|
|
923
|
|
|
|
|
|
|
&& ( $group->{reprinting_header} ) |
924
|
|
|
|
|
|
|
&& ( $group->{value} ne "!" ) |
925
|
|
|
|
|
|
|
) { |
926
|
0
|
|
|
|
|
0
|
$self->group_header( $group ); |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
} |
929
|
|
|
|
|
|
|
|
930
|
0
|
|
|
|
|
0
|
return( $page ); |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
} |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
sub group_header { |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
# Renders a new group header |
937
|
|
|
|
|
|
|
|
938
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $group ) = @_; |
939
|
|
|
|
|
|
|
|
940
|
0
|
0
|
|
|
|
0
|
if ( $group->{name} ne 'GrandTotals' ) { |
941
|
0
|
|
|
|
|
0
|
$self->{y} -= $group->{header_upper_buffer}; |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
$self->render_row( |
945
|
0
|
|
|
|
|
0
|
$group->{header}, |
946
|
|
|
|
|
|
|
$group->{value}, |
947
|
|
|
|
|
|
|
'group_header', |
948
|
|
|
|
|
|
|
$group->{header_max_cell_height}, |
949
|
|
|
|
|
|
|
$group->{header_upper_buffer}, |
950
|
|
|
|
|
|
|
$group->{header_lower_buffer} |
951
|
|
|
|
|
|
|
); |
952
|
|
|
|
|
|
|
|
953
|
0
|
|
|
|
|
0
|
$self->{y} -= $group->{header_lower_buffer}; |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
sub group_footer { |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
# Renders a new group footer |
960
|
|
|
|
|
|
|
|
961
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $group ) = @_; |
962
|
|
|
|
|
|
|
|
963
|
0
|
|
|
|
|
0
|
my $y_needed = $self->{page_footer_and_margin} |
964
|
|
|
|
|
|
|
+ $group->{footer_max_cell_height} |
965
|
|
|
|
|
|
|
+ $group->{footer_upper_buffer} |
966
|
|
|
|
|
|
|
+ $group->{footer_lower_buffer}; |
967
|
|
|
|
|
|
|
|
968
|
0
|
0
|
0
|
|
|
0
|
if ($y_needed <= $self->{page_height} && $self->{y} - $y_needed < 0) { |
969
|
0
|
|
|
|
|
0
|
$self->new_page; |
970
|
|
|
|
|
|
|
} |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
$self->render_row( |
973
|
0
|
|
|
|
|
0
|
$group->{footer}, |
974
|
|
|
|
|
|
|
$group->{value}, |
975
|
|
|
|
|
|
|
'group_footer', |
976
|
|
|
|
|
|
|
$group->{footer_max_cell_height}, |
977
|
|
|
|
|
|
|
$group->{footer_upper_buffer}, |
978
|
|
|
|
|
|
|
$group->{footer_lower_buffer} |
979
|
|
|
|
|
|
|
); |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
sub calculate_cell_height { |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
# Tries to calculate cell height depending on different cell types and properties. |
986
|
5
|
|
|
5
|
0
|
934
|
my ( $self, $cell ) = @_; |
987
|
|
|
|
|
|
|
|
988
|
5
|
|
|
|
|
7
|
my $height = 0; |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
# If cell is a barcode, height is given by its "zone" (height of the bars) |
991
|
5
|
50
|
|
|
|
22
|
if ( exists $cell->{barcode} ) { |
|
|
100
|
|
|
|
|
|
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# TODO: This calculation should be done adding upper mending zone, |
994
|
|
|
|
|
|
|
# lower mending zone, font size and bars height, but probably |
995
|
|
|
|
|
|
|
# we don't have them here... |
996
|
|
|
|
|
|
|
|
997
|
0
|
|
|
|
|
0
|
$height = $cell->{zone} + 25; |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
} elsif ( exists $cell->{text} ) { |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
# This is a text cell. Pay attention to multiline strings |
1002
|
4
|
|
|
|
|
7
|
my $txt_height = $cell->{font_size}; |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
# Ignore trailing CR/LF chars |
1005
|
4
|
100
|
|
|
|
19
|
if ( $cell->{text} =~ /[\r\n][^\s]/o ) { |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
# Multiply height of single line x number of lines |
1008
|
|
|
|
|
|
|
# FIXME here count of lines is fast but unaccurate |
1009
|
|
|
|
|
|
|
#$txt_height *= 1.2; |
1010
|
2
|
|
|
|
|
9
|
$txt_height *= 1 + ( $cell->{text} =~ tr/\n/\n/ ); |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
4
|
|
|
|
|
10
|
$height = $cell->{text_whitespace} + $txt_height; |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
# Every other cell |
1017
|
|
|
|
|
|
|
} else { |
1018
|
|
|
|
|
|
|
|
1019
|
1
|
|
|
|
|
3
|
$height = $cell->{text_whitespace} + $cell->{font_size}; |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
5
|
|
|
|
|
27
|
return ( $height ); |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
} |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
sub calculate_y_needed { |
1028
|
|
|
|
|
|
|
|
1029
|
0
|
|
|
0
|
0
|
|
my ( $self, $options ) = @_; |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
# This function calculates the y-space needed to render a particular row, |
1032
|
|
|
|
|
|
|
# and returns it to the caller in the form of: |
1033
|
|
|
|
|
|
|
# { |
1034
|
|
|
|
|
|
|
# current_height => $current_height, # LEGACY! |
1035
|
|
|
|
|
|
|
# y_needed => $y_needed, |
1036
|
|
|
|
|
|
|
# row_heights => \@row_heights |
1037
|
|
|
|
|
|
|
# }; |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
# Unpack options hash |
1040
|
0
|
|
|
|
|
|
my $cells = $options->{cells}; |
1041
|
0
|
|
|
|
|
|
my $max_cell_height = $options->{max_cell_height}; |
1042
|
0
|
|
|
|
|
|
my $row = $options->{row}; |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
# We've just been passed the max_cell_height |
1045
|
|
|
|
|
|
|
# This will be all we need if we are |
1046
|
|
|
|
|
|
|
# only rendering single-line text |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
# In the case of data render cycles, |
1049
|
|
|
|
|
|
|
# the max_cell_height is taken from $self->{data}->{max_cell_height}, |
1050
|
|
|
|
|
|
|
# which is in turn set by setup_cell_definitions(), |
1051
|
|
|
|
|
|
|
# which goes over each cell with calculate_cell_height() |
1052
|
|
|
|
|
|
|
|
1053
|
0
|
|
|
|
|
|
my $current_height = $max_cell_height; |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
# Search for an image in the current row |
1056
|
|
|
|
|
|
|
# If one is encountered, adjust our $y_needed according to scaling definition |
1057
|
|
|
|
|
|
|
|
1058
|
0
|
|
|
|
|
|
my $counter = 0; |
1059
|
0
|
|
|
|
|
|
my @row_heights; |
1060
|
|
|
|
|
|
|
|
1061
|
0
|
|
|
|
|
|
for my $cell ( @{$options->{cells}} ) { |
|
0
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
|
1063
|
0
|
0
|
|
|
|
|
if ( $cell->{image} ) { |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# Use this to accumulate image temporary data |
1066
|
0
|
|
|
|
|
|
my %imgdata; |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
# Support dynamic images ( image path comes from data array ) |
1069
|
|
|
|
|
|
|
# Note: $options->{row} won't necessarily be a data array ... |
1070
|
|
|
|
|
|
|
# ... it will ONLY be an array if we're rendering a row of data |
1071
|
|
|
|
|
|
|
|
1072
|
0
|
0
|
0
|
|
|
|
if ( $cell->{image}->{dynamic} && ref $options->{row} eq "ARRAY" ) { |
1073
|
0
|
|
|
|
|
|
$cell->{image}->{path} = $options->{row}->[$counter]; |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
# TODO support use of images in memory instead of from files? |
1077
|
|
|
|
|
|
|
# Is there actually a use for this? It's possible that images could come |
1078
|
|
|
|
|
|
|
# from a database, or be created on-the-fly. Wait for someone to request |
1079
|
|
|
|
|
|
|
# it, and then get them to implement it :) |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
# Only do imgsize() calculation if this is a different path from last time ... |
1082
|
0
|
0
|
0
|
|
|
|
if ( ( ! $imgdata{img_x} ) || ( $cell->{image}->{path} && $cell->{image}->{path} ne $cell->{image}->{previous_path} ) ) { |
|
|
|
0
|
|
|
|
|
1083
|
|
|
|
|
|
|
( |
1084
|
0
|
|
|
|
|
|
$imgdata{img_x}, |
1085
|
|
|
|
|
|
|
$imgdata{img_y}, |
1086
|
|
|
|
|
|
|
$imgdata{img_type} |
1087
|
|
|
|
|
|
|
) = imgsize( $cell->{image}->{path} ); |
1088
|
|
|
|
|
|
|
# Remember that we've calculated |
1089
|
0
|
|
|
|
|
|
$cell->{image}->{previous_path} = $cell->{image}->{path}; |
1090
|
|
|
|
|
|
|
} |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
# Deal with problems with image |
1093
|
0
|
0
|
|
|
|
|
if ( ! $imgdata{img_x} ) { |
1094
|
0
|
|
|
|
|
|
warn "Image $cell->{image}->{path} had zero width ... setting to 1\n"; |
1095
|
0
|
|
|
|
|
|
$imgdata{img_x} = 1; |
1096
|
|
|
|
|
|
|
} |
1097
|
|
|
|
|
|
|
|
1098
|
0
|
0
|
|
|
|
|
if ( ! $imgdata{img_y} ) { |
1099
|
0
|
|
|
|
|
|
warn "Image $cell->{image}->{path} had zero height ... setting to 1\n"; |
1100
|
0
|
|
|
|
|
|
$imgdata{img_y} = 1; |
1101
|
|
|
|
|
|
|
} |
1102
|
|
|
|
|
|
|
|
1103
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1104
|
0
|
|
|
|
|
|
print "Image $cell->{image}->{path} is $imgdata{img_x} x $imgdata{img_y}\n"; |
1105
|
|
|
|
|
|
|
} |
1106
|
|
|
|
|
|
|
|
1107
|
0
|
0
|
|
|
|
|
if ( $cell->{image}->{height} > 0 ) { |
|
|
0
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
# The user has defined an image height |
1110
|
0
|
|
|
|
|
|
$imgdata{y_scale_ratio} = ( $cell->{image}->{height} - ( $cell->{image}->{buffer} << 1 ) ) / $imgdata{img_y}; |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
} elsif ( $cell->{image}->{scale_to_fit} ) { |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
# We're scaling to fit the current cell |
1115
|
0
|
|
|
|
|
|
$imgdata{y_scale_ratio} = ( $current_height - ( $cell->{image}->{buffer} << 1 ) ) / $imgdata{img_y}; |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
} else { |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
# no scaling or hard-coded height defined |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
# TODO Check with Cosimo: what's the << operator for here? |
1122
|
|
|
|
|
|
|
#if ( ( $imgdata{img_y} + $cell->{image}->{buffer} << 1 ) > ( $self->{y} - $self->{page_footer_and_margin} ) ) { |
1123
|
0
|
0
|
|
|
|
|
if ( $imgdata{img_y} > ( $self->{y} - $self->{page_footer_and_margin} - ( $cell->{image}->{buffer} * 2) ) ) { |
1124
|
|
|
|
|
|
|
#$imgdata{y_scale_ratio} = ( $imgdata{img_y} + $cell->{image}->{buffer} << 1 ) / ( $self->{y} - $self->{page_footer_and_margin} ); |
1125
|
|
|
|
|
|
|
#$imgdata{y_scale_ratio} = ( $self->{y} - $self->{page_footer_and_margin} ) / ( $imgdata{img_y} + ( $cell->{image}->{buffer} *2 ) ); |
1126
|
0
|
|
|
|
|
|
$imgdata{y_scale_ratio} = ( $self->{y} - $self->{page_footer_and_margin} - ( $cell->{image}->{buffer} * 2 ) ) / ( $imgdata{img_y} ); |
1127
|
|
|
|
|
|
|
} else { |
1128
|
0
|
|
|
|
|
|
$imgdata{y_scale_ratio} = 1; |
1129
|
|
|
|
|
|
|
} |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
}; |
1132
|
|
|
|
|
|
|
|
1133
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1134
|
0
|
|
|
|
|
|
print "Current height ( before adjusting for this image ) is $current_height\n"; |
1135
|
0
|
|
|
|
|
|
print "Y scale ratio = $imgdata{y_scale_ratio}\n"; |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
# A this point, no matter what scaling, fixed size, or lack of |
1139
|
|
|
|
|
|
|
# other instructions, we still have to test whether the image will fit |
1140
|
|
|
|
|
|
|
# length-wise in the cell |
1141
|
|
|
|
|
|
|
|
1142
|
0
|
|
|
|
|
|
$imgdata{x_scale_ratio} = ( $cell->{full_width} - ( $cell->{image}->{buffer} * 2 ) ) / $imgdata{img_x}; |
1143
|
|
|
|
|
|
|
|
1144
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1145
|
0
|
|
|
|
|
|
print "X scale ratio = $imgdata{x_scale_ratio}\n"; |
1146
|
|
|
|
|
|
|
} |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
# Choose the smallest of x & y scale ratios to ensure we'll fit both ways |
1149
|
0
|
0
|
|
|
|
|
$imgdata{scale_ratio} = $imgdata{y_scale_ratio} < $imgdata{x_scale_ratio} |
1150
|
|
|
|
|
|
|
? $imgdata{y_scale_ratio} |
1151
|
|
|
|
|
|
|
: $imgdata{x_scale_ratio}; |
1152
|
|
|
|
|
|
|
|
1153
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1154
|
0
|
|
|
|
|
|
print "Smallest scaling ratio is $imgdata{scale_ratio}\n"; |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
# Set our new image dimensions based on this scale_ratio, |
1158
|
|
|
|
|
|
|
# but *DON'T* overwrite the original dimensions ... |
1159
|
|
|
|
|
|
|
# ... we're caching these for later re-use |
1160
|
0
|
|
|
|
|
|
$imgdata{this_img_x} = $imgdata{img_x} * $imgdata{scale_ratio}; |
1161
|
0
|
|
|
|
|
|
$imgdata{this_img_y} = $imgdata{img_y} * $imgdata{scale_ratio}; |
1162
|
0
|
|
|
|
|
|
$current_height = $imgdata{this_img_y} + ( $cell->{image}->{buffer} * 2 ); |
1163
|
|
|
|
|
|
|
|
1164
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1165
|
0
|
|
|
|
|
|
print "New dimensions:\n Image X: $imgdata{this_img_x}\n Image Y: $imgdata{this_img_y}\n"; |
1166
|
0
|
|
|
|
|
|
print " New height: $current_height\n"; |
1167
|
|
|
|
|
|
|
} |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
# Store image data for future reference |
1170
|
0
|
|
|
|
|
|
$cell->{image}->{tmp} = \%imgdata; |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
# } elsif ( ( ref $row eq "ARRAY" ) || ( exists $cell->{text} ) ) { |
1173
|
|
|
|
|
|
|
} else { |
1174
|
|
|
|
|
|
|
|
1175
|
0
|
|
|
|
|
|
my $text; |
1176
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
# If $options->{row} has been passed ( and is an array ), we're in a data-rendering cycle |
1178
|
|
|
|
|
|
|
|
1179
|
0
|
0
|
|
|
|
|
if ( ref $row eq "ARRAY" ) { |
|
|
0
|
|
|
|
|
|
1180
|
0
|
|
|
|
|
|
$text = $$row[$counter]; |
1181
|
|
|
|
|
|
|
} elsif ( $cell->{text} ) { |
1182
|
0
|
|
|
|
|
|
$text = $cell->{text}; |
1183
|
|
|
|
|
|
|
} else { |
1184
|
0
|
|
|
|
|
|
$text = $row; |
1185
|
|
|
|
|
|
|
} |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
# We need to set the font here so that wrap_text() can accurately calculate where to wrap |
1188
|
0
|
|
|
|
|
|
$self->{txt}->font( $self->get_cell_font($cell), $cell->{font_size} ); |
1189
|
|
|
|
|
|
|
|
1190
|
0
|
0
|
|
|
|
|
if ( $cell->{wrap_text} ) { |
1191
|
0
|
|
|
|
|
|
$text = $self->wrap_text( |
1192
|
|
|
|
|
|
|
{ |
1193
|
|
|
|
|
|
|
string => $text, |
1194
|
|
|
|
|
|
|
text_width => $cell->{text_width}, |
1195
|
|
|
|
|
|
|
strip_breaks => $cell->{strip_breaks} |
1196
|
|
|
|
|
|
|
} |
1197
|
|
|
|
|
|
|
); |
1198
|
|
|
|
|
|
|
} |
1199
|
|
|
|
|
|
|
|
1200
|
0
|
|
|
|
|
|
my $no_of_new_lines = $text =~ tr/\n/\n/; |
1201
|
|
|
|
|
|
|
|
1202
|
0
|
0
|
|
|
|
|
if ( $no_of_new_lines ) { |
1203
|
0
|
|
|
|
|
|
$current_height = ( 1 + $no_of_new_lines ) * ( $cell->{font_size} + $cell->{text_whitespace} ); |
1204
|
|
|
|
|
|
|
} |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
} |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
# If there is *no* row height set yet, or if it's set but is lower than the current height, |
1209
|
|
|
|
|
|
|
# set it to the current height |
1210
|
0
|
0
|
0
|
|
|
|
if ( ( ! $row_heights[ $cell->{row} ] ) || ( $current_height > $row_heights[ $cell->{row} ] ) ) { |
1211
|
0
|
|
|
|
|
|
$row_heights[ $cell->{row} ] = $current_height; |
1212
|
|
|
|
|
|
|
} |
1213
|
|
|
|
|
|
|
|
1214
|
0
|
|
|
|
|
|
$counter ++; |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
} |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
# If we have queued group headers, calculate how much Y space they need |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
# Note that at this point, $current_height is the height of the current row |
1221
|
|
|
|
|
|
|
# We now introduce $y_needed, which is $current_height, PLUS the height of headers, buffers, etc |
1222
|
|
|
|
|
|
|
|
1223
|
0
|
|
|
|
|
|
my $y_needed = $current_height + $self->{data}->{upper_buffer} + $self->{data}->{lower_buffer}; |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
# TODO this will not work if there are *unscaled* images in the headers |
1226
|
|
|
|
|
|
|
# Is it worth supporting this as well? Maybe. |
1227
|
|
|
|
|
|
|
# Maybe later ... |
1228
|
|
|
|
|
|
|
|
1229
|
0
|
0
|
|
|
|
|
if ( $self->{group_header_queue} ) { |
1230
|
0
|
|
|
|
|
|
for my $header ( @{$self->{group_header_queue}} ) { |
|
0
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
# For the headers, we take the header's max_cell_height, |
1232
|
|
|
|
|
|
|
# then add the upper & lower buffers for the group header |
1233
|
0
|
|
|
|
|
|
$y_needed += $header->{group}->{header_max_cell_height} |
1234
|
|
|
|
|
|
|
+ $header->{group}->{header_upper_buffer} |
1235
|
|
|
|
|
|
|
+ $header->{group}->{header_lower_buffer}; |
1236
|
|
|
|
|
|
|
} |
1237
|
|
|
|
|
|
|
# And also the data header if it's turned on |
1238
|
0
|
0
|
|
|
|
|
if ( ! $self->{data}->{no_field_headers} ) { |
1239
|
0
|
|
|
|
|
|
$y_needed += $max_cell_height; |
1240
|
|
|
|
|
|
|
} |
1241
|
|
|
|
|
|
|
} |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
return { |
1244
|
0
|
|
|
|
|
|
current_height => $current_height, |
1245
|
|
|
|
|
|
|
y_needed => $y_needed, |
1246
|
|
|
|
|
|
|
row_heights => \@row_heights |
1247
|
|
|
|
|
|
|
}; |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
} |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
sub render_row { |
1252
|
|
|
|
|
|
|
|
1253
|
0
|
|
|
0
|
0
|
|
my ( $self, $cells, $row, $type, $max_cell_height, $upper_buffer, $lower_buffer ) = @_; |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
# $cells - a hash of cell definitions |
1256
|
|
|
|
|
|
|
# $row - the current row to render |
1257
|
|
|
|
|
|
|
# $type - possible values are: |
1258
|
|
|
|
|
|
|
# - header - prints a row of field names |
1259
|
|
|
|
|
|
|
# - data - prints a row of data |
1260
|
|
|
|
|
|
|
# - group_header - prints a row of group header |
1261
|
|
|
|
|
|
|
# - group_footer - prints a row of group footer |
1262
|
|
|
|
|
|
|
# - page_header - prints a page header |
1263
|
|
|
|
|
|
|
# - page_footer - prints a page footer |
1264
|
|
|
|
|
|
|
# $max_cell_height - the height of the *cell* ( not including buffers ) |
1265
|
|
|
|
|
|
|
# upper_buffer - amount of whitespace to leave above this row |
1266
|
|
|
|
|
|
|
# lower_buffer - amount of whitespace to leave after this row |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
# In the case of page footers, $row will be a hash with useful stuff like |
1269
|
|
|
|
|
|
|
# page number, total pages, time, etc |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
# Calculate the y space required, including queued group footers |
1272
|
0
|
|
|
|
|
|
my $size_calculation = $self->calculate_y_needed( |
1273
|
|
|
|
|
|
|
{ |
1274
|
|
|
|
|
|
|
cells => $cells, |
1275
|
|
|
|
|
|
|
max_cell_height => $max_cell_height, |
1276
|
|
|
|
|
|
|
row => $row |
1277
|
|
|
|
|
|
|
} |
1278
|
|
|
|
|
|
|
); |
1279
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
# Page Footer / New Page / Page Header if necessary, otherwise move down by $current_height |
1281
|
|
|
|
|
|
|
# ( But don't force a new page if we're rendering a page footer ) |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
# Check that total y space needed does not exceed page size. |
1284
|
|
|
|
|
|
|
# In that case we cannot keep adding more pages, which causes |
1285
|
|
|
|
|
|
|
# horrible out of memory errors |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
# TODO Should this be taken into account in calculate_y_needed? |
1288
|
0
|
|
|
|
|
|
$size_calculation->{y_needed} += $self->{page_footer_and_margin}; |
1289
|
|
|
|
|
|
|
|
1290
|
0
|
0
|
0
|
|
|
|
if ( $type ne 'page_footer' |
|
|
|
0
|
|
|
|
|
1291
|
|
|
|
|
|
|
&& $size_calculation->{y_needed} <= $self->{page_height} |
1292
|
|
|
|
|
|
|
&& $self->{y} - $size_calculation->{y_needed} < 0 |
1293
|
|
|
|
|
|
|
) |
1294
|
|
|
|
|
|
|
{ |
1295
|
0
|
|
|
|
|
|
$self->new_page; |
1296
|
|
|
|
|
|
|
} |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
# Trigger any group headers that we have queued, but ONLY if we're in a data cycle |
1299
|
0
|
0
|
|
|
|
|
if ( $type eq "data" ) { |
1300
|
0
|
|
|
|
|
|
while ( my $queued_headers = pop @{$self->{group_header_queue}} ) { |
|
0
|
|
|
|
|
|
|
1301
|
0
|
|
|
|
|
|
$self->group_header( $queued_headers->{group}, $queued_headers->{value} ); |
1302
|
|
|
|
|
|
|
} |
1303
|
|
|
|
|
|
|
} |
1304
|
|
|
|
|
|
|
|
1305
|
0
|
0
|
0
|
|
|
|
if ( $type eq "data" && $self->{need_data_header} && ! $self->{data}->{no_field_headers} ) { |
|
|
|
0
|
|
|
|
|
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
# If we are in field headers section, leave room as specified by options |
1308
|
0
|
|
|
|
|
|
$self->{y} -= $self->{data}->{field_headers_upper_buffer}; |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
# Now render field headers row |
1311
|
0
|
|
|
|
|
|
$self->render_row( |
1312
|
|
|
|
|
|
|
$self->{data}->{field_headers}, |
1313
|
|
|
|
|
|
|
0, |
1314
|
|
|
|
|
|
|
'header', |
1315
|
|
|
|
|
|
|
$self->{data}->{max_field_header_height}, |
1316
|
|
|
|
|
|
|
$self->{data}->{field_header_upper_buffer}, |
1317
|
|
|
|
|
|
|
$self->{data}->{field_header_lower_buffer} |
1318
|
|
|
|
|
|
|
); |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
} |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
# Move down for upper_buffer, and then for the current row height |
1323
|
|
|
|
|
|
|
# $self->{y} -= $upper_buffer + $current_height; |
1324
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
# Move down for upper_buffer, and then for the FIRST row height |
1326
|
0
|
|
|
|
|
|
$self->{y} -= $upper_buffer; |
1327
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
# |
1329
|
|
|
|
|
|
|
# Render row |
1330
|
|
|
|
|
|
|
# |
1331
|
|
|
|
|
|
|
|
1332
|
|
|
|
|
|
|
# Prepare options to be passed to *all* cell rendering methods |
1333
|
0
|
|
|
|
|
|
my $options = { |
1334
|
|
|
|
|
|
|
current_row => $row, |
1335
|
|
|
|
|
|
|
row_type => $type, # Row type (data, header, group, footer) |
1336
|
|
|
|
|
|
|
cell_counter => 0, |
1337
|
|
|
|
|
|
|
cell_y_border => $self->{y}, |
1338
|
|
|
|
|
|
|
# cell_full_height => $current_height, |
1339
|
0
|
|
|
|
|
|
page => $self->{pages}->[ scalar( @{$self->{pages}} ) - 1 ], |
1340
|
0
|
|
|
|
|
|
page_no => scalar( @{$self->{pages}} ) - 1 |
1341
|
|
|
|
|
|
|
}; |
1342
|
|
|
|
|
|
|
|
1343
|
0
|
|
|
|
|
|
my $this_row = -1; # Forces us to move down immediately |
1344
|
|
|
|
|
|
|
|
1345
|
0
|
|
|
|
|
|
for my $cell ( @{$cells} ) { |
|
0
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
# If we're entering a new line ( ie multi-line rows ), |
1348
|
|
|
|
|
|
|
# then shift our Y position and set the new cell_full_height |
1349
|
|
|
|
|
|
|
|
1350
|
0
|
0
|
|
|
|
|
if ( $this_row != $cell->{row} ) { |
1351
|
0
|
|
|
|
|
|
$self->{y} -= $size_calculation->{row_heights}[ $cell->{row} ]; |
1352
|
0
|
|
|
|
|
|
$options->{cell_full_height} = $size_calculation->{row_heights}[ $cell->{row} ]; |
1353
|
0
|
|
|
|
|
|
$this_row = $cell->{row}; |
1354
|
|
|
|
|
|
|
} |
1355
|
|
|
|
|
|
|
|
1356
|
0
|
|
|
|
|
|
$options->{cell} = $cell; |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
# TODO Apparent we're not looking in 'text' key for hard-coded text any more. Add back ... |
1359
|
0
|
0
|
|
|
|
|
if ( ref( $options->{current_row} ) eq 'ARRAY' ) { |
1360
|
0
|
|
|
|
|
|
$options->{current_value} = $options->{current_row}->[ $options->{cell_counter} ]; |
1361
|
|
|
|
|
|
|
} else { |
1362
|
0
|
|
|
|
|
|
$options->{current_value} = $options->{current_row}; |
1363
|
|
|
|
|
|
|
} |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
#} else { |
1366
|
|
|
|
|
|
|
#} else { |
1367
|
|
|
|
|
|
|
# warn 'Found notref value '.$options->{current_row}; |
1368
|
|
|
|
|
|
|
# $options->{current_value} = $options->{current_row}->[ $options->{cell_counter} ]; |
1369
|
|
|
|
|
|
|
# $options->{current_value} = $options->{current_row}; |
1370
|
|
|
|
|
|
|
#} |
1371
|
|
|
|
|
|
|
|
1372
|
0
|
|
|
|
|
|
$self->render_cell( $cell, $options ); |
1373
|
0
|
|
|
|
|
|
$options->{cell_counter}++; |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
} |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
# Move down for the lower_buffer |
1378
|
0
|
|
|
|
|
|
$self->{y} -= $lower_buffer; |
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
} |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
sub render_cell { |
1383
|
|
|
|
|
|
|
|
1384
|
0
|
|
|
0
|
0
|
|
my ( $self, $cell, $options ) = @_; |
1385
|
|
|
|
|
|
|
|
1386
|
0
|
|
|
|
|
|
my $type = $options->{row_type}; |
1387
|
0
|
|
|
|
|
|
my $row = $options->{current_row}; |
1388
|
0
|
|
|
|
|
|
my $current_height = $options->{cell_full_height}; |
1389
|
0
|
|
|
|
|
|
my $cell_counter = $options->{cell_counter}; |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
# Render cell background ( an ellipse, box, or cell borders ) |
1392
|
0
|
0
|
|
|
|
|
if ( exists $cell->{background} ) { |
1393
|
0
|
|
|
|
|
|
$self->render_cell_background( $cell, $options ); |
1394
|
|
|
|
|
|
|
} |
1395
|
|
|
|
|
|
|
|
1396
|
|
|
|
|
|
|
# Run custom render functions and see if they return anything |
1397
|
0
|
0
|
|
|
|
|
if ( exists $cell->{custom_render_func} ) { |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
# XXX Here to unify all the universal forces, the first parameter |
1400
|
|
|
|
|
|
|
# should be the cell "object", then all the options, even if options |
1401
|
|
|
|
|
|
|
# already contains a "cell" object |
1402
|
0
|
|
|
|
|
|
my $func_return = $cell->{custom_render_func}( $options ); |
1403
|
|
|
|
|
|
|
|
1404
|
0
|
0
|
|
|
|
|
if ( ref $func_return eq "HASH" ) { |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
# We've received a return hash with instructions on what to do |
1407
|
0
|
0
|
0
|
|
|
|
if ( exists $func_return->{render_text} ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
# We've been passed some text to render. Shove it into the current value and continue |
1410
|
0
|
|
|
|
|
|
$options->{current_value} = $func_return->{render_text}; |
1411
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
} elsif ( exists $func_return->{render_image} ) { |
1413
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
# We've been passed an image hash. Copy each key in the hash back into the cell and continue |
1415
|
0
|
|
|
|
|
|
foreach my $key ( keys %{$func_return->{render_image}} ) { |
|
0
|
|
|
|
|
|
|
1416
|
0
|
|
|
|
|
|
$cell->{image}->{$key} = $$func_return->{render_image}->{$key}; |
1417
|
|
|
|
|
|
|
} |
1418
|
|
|
|
|
|
|
|
1419
|
|
|
|
|
|
|
} elsif ( exists $func_return->{rendering_done} && $func_return->{rendering_done} ) { |
1420
|
|
|
|
|
|
|
|
1421
|
0
|
|
|
|
|
|
return; |
1422
|
|
|
|
|
|
|
|
1423
|
|
|
|
|
|
|
} else { |
1424
|
|
|
|
|
|
|
|
1425
|
0
|
|
|
|
|
|
warn "A custom render function returned an unrecognised hash!\n"; |
1426
|
0
|
|
|
|
|
|
return; |
1427
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
|
1430
|
|
|
|
|
|
|
} else { |
1431
|
|
|
|
|
|
|
|
1432
|
0
|
|
|
|
|
|
warn "A custom render function was executed, but it didn't provide a return hash!\n"; |
1433
|
0
|
|
|
|
|
|
return; |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
} |
1436
|
|
|
|
|
|
|
} |
1437
|
|
|
|
|
|
|
|
1438
|
0
|
0
|
|
|
|
|
if ( $cell->{image} ) { |
|
|
0
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
|
1440
|
0
|
|
|
|
|
|
$self->render_cell_image( $cell, $options ); |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
} elsif ( $cell->{barcode} ) { |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
# Barcode cell |
1445
|
|
|
|
|
|
|
|
1446
|
0
|
|
|
|
|
|
$self->render_cell_barcode( $cell, $options ); |
1447
|
|
|
|
|
|
|
|
1448
|
|
|
|
|
|
|
} else { |
1449
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
# Generic text cell rendering |
1451
|
|
|
|
|
|
|
|
1452
|
0
|
|
|
|
|
|
$self->render_cell_text( $cell, $options ); |
1453
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
# Now perform aggregate functions if defined |
1455
|
|
|
|
|
|
|
|
1456
|
0
|
0
|
0
|
|
|
|
if ( $type eq 'data' && $cell->{aggregate_function} ) { |
1457
|
|
|
|
|
|
|
|
1458
|
0
|
|
0
|
|
|
|
my $cell_value = $options->{current_value} || 0; |
1459
|
0
|
|
0
|
|
|
|
my $group_res = $cell->{group_results} ||= {}; |
1460
|
0
|
|
|
|
|
|
my $aggr_func = $cell->{aggregate_function}; |
1461
|
|
|
|
|
|
|
|
1462
|
0
|
0
|
|
|
|
|
if ( $aggr_func ) { |
1463
|
|
|
|
|
|
|
|
1464
|
0
|
0
|
|
|
|
|
if ( $aggr_func eq 'sum' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1465
|
|
|
|
|
|
|
|
1466
|
0
|
|
|
|
|
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
|
|
1467
|
0
|
|
|
|
|
|
$group_res->{$group->{name}} += $cell_value; |
1468
|
|
|
|
|
|
|
} |
1469
|
|
|
|
|
|
|
|
1470
|
0
|
|
|
|
|
|
$cell->{grand_aggregate_result} += $cell_value; |
1471
|
|
|
|
|
|
|
|
1472
|
|
|
|
|
|
|
} elsif ( $aggr_func eq 'count' ) { |
1473
|
|
|
|
|
|
|
|
1474
|
0
|
|
|
|
|
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
|
|
1475
|
0
|
|
|
|
|
|
$group_res->{$group->{name}} ++; |
1476
|
|
|
|
|
|
|
} |
1477
|
|
|
|
|
|
|
|
1478
|
0
|
|
|
|
|
|
$cell->{grand_aggregate_result} ++; |
1479
|
|
|
|
|
|
|
|
1480
|
|
|
|
|
|
|
} elsif ( $aggr_func eq 'max' ) { |
1481
|
|
|
|
|
|
|
|
1482
|
0
|
|
|
|
|
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
|
|
1483
|
0
|
0
|
|
|
|
|
if( $cell_value > $group_res->{$group->{name}} ) { |
1484
|
0
|
|
|
|
|
|
$cell->{grand_aggregate_result} = |
1485
|
|
|
|
|
|
|
$group_res->{$group->{name}} = $cell_value; |
1486
|
|
|
|
|
|
|
} |
1487
|
|
|
|
|
|
|
} |
1488
|
|
|
|
|
|
|
|
1489
|
|
|
|
|
|
|
} elsif ( $aggr_func eq 'min' ) { |
1490
|
|
|
|
|
|
|
|
1491
|
0
|
|
|
|
|
|
for my $group ( @{$self->{data}->{groups}} ) { |
|
0
|
|
|
|
|
|
|
1492
|
0
|
0
|
|
|
|
|
if( $cell_value < $group_res->{$group->{name}} ) { |
1493
|
0
|
|
|
|
|
|
$cell->{grand_aggregate_result} = |
1494
|
|
|
|
|
|
|
$group_res->{$group->{name}} = $cell_value; |
1495
|
|
|
|
|
|
|
} |
1496
|
|
|
|
|
|
|
} |
1497
|
|
|
|
|
|
|
|
1498
|
|
|
|
|
|
|
} |
1499
|
|
|
|
|
|
|
|
1500
|
|
|
|
|
|
|
# TODO add an "avg" aggregate function? Should be simple. |
1501
|
|
|
|
|
|
|
|
1502
|
|
|
|
|
|
|
} |
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
} |
1505
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
} |
1507
|
|
|
|
|
|
|
|
1508
|
|
|
|
|
|
|
} |
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
sub render_cell_background { |
1511
|
|
|
|
|
|
|
|
1512
|
0
|
|
|
0
|
0
|
|
my ( $self, $cell, $opt ) = @_; |
1513
|
|
|
|
|
|
|
|
1514
|
0
|
|
|
|
|
|
my $background; |
1515
|
|
|
|
|
|
|
|
1516
|
0
|
0
|
|
|
|
|
if ( $cell->{background_func} ) { |
1517
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1518
|
0
|
|
|
|
|
|
print "\nRunning background_func() \n"; |
1519
|
|
|
|
|
|
|
} |
1520
|
|
|
|
|
|
|
|
1521
|
0
|
|
|
|
|
|
$background = $cell->{background_func}($opt->{current_value}, $opt->{current_row}, $opt); |
1522
|
|
|
|
|
|
|
} |
1523
|
|
|
|
|
|
|
else { |
1524
|
0
|
|
|
|
|
|
$background = $cell->{background}; |
1525
|
|
|
|
|
|
|
} |
1526
|
|
|
|
|
|
|
|
1527
|
0
|
0
|
|
|
|
|
unless ( defined $background ) { |
1528
|
0
|
|
|
|
|
|
return; |
1529
|
|
|
|
|
|
|
} |
1530
|
|
|
|
|
|
|
|
1531
|
0
|
|
|
|
|
|
my $current_height = $opt->{cell_full_height}; |
1532
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
|
1534
|
0
|
0
|
|
|
|
|
if ( $background->{shape} ) { |
1535
|
|
|
|
|
|
|
|
1536
|
0
|
0
|
|
|
|
|
if ( $background->{shape} eq "ellipse" ) { |
|
|
0
|
|
|
|
|
|
1537
|
|
|
|
|
|
|
|
1538
|
0
|
|
|
|
|
|
$self->{shape}->fillcolor( $background->{colour} ); |
1539
|
|
|
|
|
|
|
|
1540
|
0
|
|
|
|
|
|
$self->{shape}->ellipse( |
1541
|
|
|
|
|
|
|
$cell->{x_border} + ( $cell->{full_width} >> 1 ), # x centre |
1542
|
|
|
|
|
|
|
$self->{y} + ( $current_height >> 1 ), # y centre |
1543
|
|
|
|
|
|
|
$cell->{full_width} >> 1, # length ( / 2 ... for some reason ) |
1544
|
|
|
|
|
|
|
$current_height >> 1 # height ( / 2 ... for some reason ) |
1545
|
|
|
|
|
|
|
); |
1546
|
|
|
|
|
|
|
|
1547
|
0
|
|
|
|
|
|
$self->{shape}->fill; |
1548
|
|
|
|
|
|
|
|
1549
|
|
|
|
|
|
|
} elsif ( $background->{shape} eq "box" ) { |
1550
|
|
|
|
|
|
|
|
1551
|
0
|
|
|
|
|
|
$self->{shape}->fillcolor( $background->{colour} ); |
1552
|
|
|
|
|
|
|
|
1553
|
0
|
|
|
|
|
|
$self->{shape}->rect( |
1554
|
|
|
|
|
|
|
$cell->{x_border}, # left border |
1555
|
|
|
|
|
|
|
$self->{y}, # bottom border |
1556
|
|
|
|
|
|
|
$cell->{full_width}, # length |
1557
|
|
|
|
|
|
|
$current_height # height |
1558
|
|
|
|
|
|
|
); |
1559
|
|
|
|
|
|
|
|
1560
|
0
|
|
|
|
|
|
$self->{shape}->fill; |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
} |
1563
|
|
|
|
|
|
|
|
1564
|
|
|
|
|
|
|
} |
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
# |
1567
|
|
|
|
|
|
|
# Now render cell background borders |
1568
|
|
|
|
|
|
|
# |
1569
|
0
|
0
|
|
|
|
|
if ( $background->{border} ) { |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
# Cell Borders |
1572
|
0
|
|
|
|
|
|
$self->{line}->strokecolor( $background->{border} ); |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
# TODO Move the regex setuff into setup_cell_definitions() |
1575
|
|
|
|
|
|
|
# so we don't have to regex per cell, which is |
1576
|
|
|
|
|
|
|
# apparently quite expensive |
1577
|
|
|
|
|
|
|
|
1578
|
|
|
|
|
|
|
# If the 'borders' key does not exist then draw all borders |
1579
|
|
|
|
|
|
|
# to support code written before this was added. |
1580
|
|
|
|
|
|
|
# A value of 'all' can also be used. |
1581
|
0
|
0
|
0
|
|
|
|
if ( ( ! exists $background->{borders} ) || ( uc $background->{borders} eq 'ALL' ) ) |
1582
|
|
|
|
|
|
|
{ |
1583
|
0
|
|
|
|
|
|
$background->{borders} = "tblr"; |
1584
|
|
|
|
|
|
|
} |
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
# The 'borders' key looks for the following chars in the string |
1587
|
|
|
|
|
|
|
# t or T - Top Border Line |
1588
|
|
|
|
|
|
|
# b or B - Bottom Border Line |
1589
|
|
|
|
|
|
|
# l or L - Left Border Line |
1590
|
|
|
|
|
|
|
# r or R - Right Border Line |
1591
|
|
|
|
|
|
|
|
1592
|
0
|
|
|
|
|
|
my $cell_bb = $background->{borders}; |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
# Bottom Horz Line |
1595
|
0
|
0
|
|
|
|
|
if ( $cell_bb =~ /[bB]/ ) { |
1596
|
0
|
|
|
|
|
|
$self->{line}->move( $cell->{x_border}, $self->{y} ); |
1597
|
0
|
|
|
|
|
|
$self->{line}->line( $cell->{x_border} + $cell->{full_width}, $self->{y} ); |
1598
|
0
|
|
|
|
|
|
$self->{line}->stroke; |
1599
|
|
|
|
|
|
|
} |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
# Right Vert Line |
1602
|
0
|
0
|
|
|
|
|
if ( $cell_bb =~ /[rR]/ ) { |
1603
|
0
|
|
|
|
|
|
$self->{line}->move( $cell->{x_border} + $cell->{full_width}, $self->{y} ); |
1604
|
0
|
|
|
|
|
|
$self->{line}->line( $cell->{x_border} + $cell->{full_width}, $self->{y} + $current_height ); |
1605
|
0
|
|
|
|
|
|
$self->{line}->stroke; |
1606
|
|
|
|
|
|
|
} |
1607
|
|
|
|
|
|
|
|
1608
|
|
|
|
|
|
|
# Top Horz Line |
1609
|
0
|
0
|
|
|
|
|
if ( $cell_bb =~ /[tT]/ ) { |
1610
|
0
|
|
|
|
|
|
$self->{line}->move( $cell->{x_border} + $cell->{full_width}, $self->{y} + $current_height ); |
1611
|
0
|
|
|
|
|
|
$self->{line}->line( $cell->{x_border}, $self->{y} + $current_height ); |
1612
|
0
|
|
|
|
|
|
$self->{line}->stroke; |
1613
|
|
|
|
|
|
|
} |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
# Left Vert Line |
1616
|
0
|
0
|
|
|
|
|
if ( $cell_bb =~ /[lL]/ ) { |
1617
|
0
|
|
|
|
|
|
$self->{line}->move( $cell->{x_border}, $self->{y} + $current_height ); |
1618
|
0
|
|
|
|
|
|
$self->{line}->line( $cell->{x_border}, $self->{y} ); |
1619
|
0
|
|
|
|
|
|
$self->{line}->stroke; |
1620
|
|
|
|
|
|
|
} |
1621
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
} |
1623
|
|
|
|
|
|
|
|
1624
|
|
|
|
|
|
|
} |
1625
|
|
|
|
|
|
|
|
1626
|
|
|
|
|
|
|
sub render_cell_barcode { |
1627
|
|
|
|
|
|
|
|
1628
|
0
|
|
|
0
|
0
|
|
my ( $self, $cell, $opt ) = @_; |
1629
|
|
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
# PDF::API2 barcode options |
1631
|
|
|
|
|
|
|
# |
1632
|
|
|
|
|
|
|
# x, y => center of barcode position |
1633
|
|
|
|
|
|
|
# type => 'code128', '2of5int', '3of9', 'ean13', 'code39' |
1634
|
|
|
|
|
|
|
# code => what is written into barcode |
1635
|
|
|
|
|
|
|
# extn => barcode extension, where applicable |
1636
|
|
|
|
|
|
|
# umzn => upper mending zone (?) |
1637
|
|
|
|
|
|
|
# lmzn => lower mending zone (?) |
1638
|
|
|
|
|
|
|
# quzn => quiet zone (space between frame and barcode) |
1639
|
|
|
|
|
|
|
# spcr => what to put between each char in the text |
1640
|
|
|
|
|
|
|
# ofwt => overflow width |
1641
|
|
|
|
|
|
|
# fnsz => font size for the text |
1642
|
|
|
|
|
|
|
# text => optional text under the barcode |
1643
|
|
|
|
|
|
|
# zone => height of the bars |
1644
|
|
|
|
|
|
|
# scale=> 0 .. 1 |
1645
|
|
|
|
|
|
|
|
1646
|
0
|
|
|
|
|
|
my $pdf = $self->{pdf}; |
1647
|
0
|
|
|
|
|
|
my $bcode = $self->get_cell_text($opt->{current_row}, $cell, $cell->{barcode}); |
1648
|
0
|
|
|
|
|
|
my $btype = 'xo_code128'; |
1649
|
|
|
|
|
|
|
|
1650
|
|
|
|
|
|
|
# For EAN-13 barcodes, calculate check digit |
1651
|
0
|
0
|
|
|
|
|
if ( $cell->{type} eq 'ean13' ) |
1652
|
|
|
|
|
|
|
{ |
1653
|
0
|
0
|
|
|
|
|
return unless eval { require GD::Barcode::EAN13 }; |
|
0
|
|
|
|
|
|
|
1654
|
0
|
|
|
|
|
|
$bcode .= '000000000000'; |
1655
|
0
|
|
|
|
|
|
$bcode = substr( $bcode, 0, 12 ); |
1656
|
0
|
|
|
|
|
|
$bcode .= GD::Barcode::EAN13::calcEAN13CD($bcode); |
1657
|
0
|
|
|
|
|
|
$btype = 'xo_ean13'; |
1658
|
|
|
|
|
|
|
} |
1659
|
|
|
|
|
|
|
|
1660
|
|
|
|
|
|
|
# Define font type |
1661
|
0
|
0
|
0
|
|
|
|
my %bcode_opt = ( |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1662
|
|
|
|
|
|
|
-font=>$self->get_cell_font($cell), |
1663
|
|
|
|
|
|
|
-fnsz=>$cell->{font_size} || $self->{default_font_size}, |
1664
|
|
|
|
|
|
|
-code=>$bcode, |
1665
|
|
|
|
|
|
|
-text=>$bcode, |
1666
|
|
|
|
|
|
|
-quzn=>exists $cell->{quiet_zone} ? $cell->{quiet_zone} : 2, |
1667
|
|
|
|
|
|
|
-umzn=>exists $cell->{upper_mending_zone} ? $cell->{upper_mending_zone} : 4, |
1668
|
|
|
|
|
|
|
-zone=>exists $cell->{zone} ? $cell->{zone} : 25, |
1669
|
|
|
|
|
|
|
-lmzn=>exists $cell->{lower_mending_zone} ? $cell->{lower_mending_zone} : 12, |
1670
|
|
|
|
|
|
|
-spcr=>' ', |
1671
|
|
|
|
|
|
|
-ofwt=>0.1, |
1672
|
|
|
|
|
|
|
); |
1673
|
|
|
|
|
|
|
|
1674
|
0
|
0
|
|
|
|
|
if( $cell->{type} eq 'code128' ) |
1675
|
|
|
|
|
|
|
{ |
1676
|
0
|
|
|
|
|
|
$bcode_opt{-ean} = 0; |
1677
|
|
|
|
|
|
|
# TODO Don't know what type to use here. |
1678
|
|
|
|
|
|
|
# `a' does not seem to handle lowercase chars. |
1679
|
|
|
|
|
|
|
# `c' is a mess. |
1680
|
|
|
|
|
|
|
# `b' seems the better... |
1681
|
0
|
|
|
|
|
|
$bcode_opt{-type} = 'b'; |
1682
|
|
|
|
|
|
|
} |
1683
|
|
|
|
|
|
|
|
1684
|
0
|
0
|
|
|
|
|
if( $cell->{type} eq 'code39' ) |
1685
|
|
|
|
|
|
|
{ |
1686
|
0
|
|
|
|
|
|
print STDERR "code 39 code\n"; |
1687
|
0
|
|
|
|
|
|
$bcode_opt{-ean} = 0; |
1688
|
|
|
|
|
|
|
# TODO Don't know what type to use here. |
1689
|
|
|
|
|
|
|
# `a' does not seem to handle lowercase chars. |
1690
|
|
|
|
|
|
|
# `c' is a mess. |
1691
|
|
|
|
|
|
|
# `b' seems the better... |
1692
|
0
|
|
|
|
|
|
$btype = 'xo_3of9'; |
1693
|
|
|
|
|
|
|
} |
1694
|
|
|
|
|
|
|
|
1695
|
0
|
|
|
|
|
|
my $bar = $pdf->$btype(%bcode_opt); |
1696
|
0
|
0
|
|
|
|
|
my $scale = exists $cell->{scale} ? $cell->{scale} : 1; |
1697
|
0
|
0
|
|
|
|
|
my $x_pos = exists $cell->{x} ? $cell->{x} : $cell->{x_border}; |
1698
|
0
|
0
|
|
|
|
|
my $y_pos = exists $cell->{y} ? $cell->{y} : $self->{y}; |
1699
|
|
|
|
|
|
|
|
1700
|
|
|
|
|
|
|
# Manage alignment (left, right or center) |
1701
|
0
|
|
0
|
|
|
|
my $align = substr lc $cell->{align} || 'l', 0, 1; |
1702
|
0
|
|
|
|
|
|
my $bar_width = $bar->width * $scale; |
1703
|
0
|
0
|
|
|
|
|
if( $align eq 'r' ) { |
|
|
0
|
|
|
|
|
|
1704
|
0
|
|
|
|
|
|
$x_pos -= $bar_width; |
1705
|
|
|
|
|
|
|
} elsif( $align eq 'c' ) { |
1706
|
0
|
|
|
|
|
|
$x_pos -= $bar_width >> 1; |
1707
|
|
|
|
|
|
|
} |
1708
|
|
|
|
|
|
|
|
1709
|
|
|
|
|
|
|
# Position barcode with correct x,y and scale |
1710
|
0
|
|
|
|
|
|
my $gfx = $opt->{page}->gfx; |
1711
|
0
|
|
|
|
|
|
$gfx->formimage($bar, $x_pos, $y_pos, $scale); |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
} |
1714
|
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
sub render_cell_image { |
1716
|
|
|
|
|
|
|
|
1717
|
0
|
|
|
0
|
0
|
|
my( $self, $cell, $opt ) = @_; |
1718
|
|
|
|
|
|
|
|
1719
|
0
|
|
|
|
|
|
my $current_height = $opt->{cell_full_height}; |
1720
|
0
|
|
|
|
|
|
my $gfx = $opt->{page}->gfx; |
1721
|
0
|
|
|
|
|
|
my $image; |
1722
|
0
|
|
|
|
|
|
my $imgdata = $cell->{image}->{tmp}; |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
# TODO Add support for GD::Image images? |
1725
|
|
|
|
|
|
|
# PDF::API2 supports using them directly. |
1726
|
|
|
|
|
|
|
# We need another key - shouldn't re-use $cell->{image}->{path} |
1727
|
|
|
|
|
|
|
# We also shouldn't run imgsize() on it, so we have to figure out |
1728
|
|
|
|
|
|
|
# another way of getting the image size. |
1729
|
|
|
|
|
|
|
# I haven't use GD before, but I've noted stuff here for people |
1730
|
|
|
|
|
|
|
# who want GD::Image support ... |
1731
|
|
|
|
|
|
|
|
1732
|
|
|
|
|
|
|
# Try to know if installed version of PDF::API2 support the |
1733
|
|
|
|
|
|
|
# image we are throwing in the PDF document, to avoid bombs |
1734
|
|
|
|
|
|
|
# when calling image_* pdf methods. |
1735
|
0
|
|
|
|
|
|
my %img_meth = ( |
1736
|
|
|
|
|
|
|
PNG=>'image_png', |
1737
|
|
|
|
|
|
|
JPG=>'image_jpeg', |
1738
|
|
|
|
|
|
|
TIF=>'image_tiff', |
1739
|
|
|
|
|
|
|
GIF=>'image_gif', |
1740
|
|
|
|
|
|
|
PNM=>'image_pnm', |
1741
|
|
|
|
|
|
|
); |
1742
|
|
|
|
|
|
|
|
1743
|
0
|
|
|
|
|
|
eval { |
1744
|
|
|
|
|
|
|
|
1745
|
0
|
0
|
|
|
|
|
my $img_call = exists $img_meth{ $imgdata->{img_type} } |
1746
|
|
|
|
|
|
|
? $img_meth{ $imgdata->{img_type} } |
1747
|
|
|
|
|
|
|
: undef; |
1748
|
|
|
|
|
|
|
|
1749
|
0
|
0
|
|
|
|
|
if( ! defined $img_call ) |
1750
|
|
|
|
|
|
|
{ |
1751
|
0
|
|
|
|
|
|
warn "\n * * * * * * * * * * * * * WARNING * * * * * * * * * * * * *\n"; |
1752
|
0
|
|
|
|
|
|
warn " Unknown image type: $imgdata->{img_type}\n"; |
1753
|
0
|
|
|
|
|
|
warn " NOT rendering this image.\n"; |
1754
|
0
|
|
|
|
|
|
warn " Please add support for PDF::ReportWriter and send patches :)\n\n"; |
1755
|
0
|
|
|
|
|
|
warn "\n * * * * * * * * * * * * * WARNING * * * * * * * * * * * * *\n\n"; |
1756
|
|
|
|
|
|
|
|
1757
|
|
|
|
|
|
|
# Return now or errors are going to happen when putting an invalid image |
1758
|
|
|
|
|
|
|
# object on PDF page gfx context |
1759
|
0
|
|
|
|
|
|
die "Unrecognized image type"; |
1760
|
|
|
|
|
|
|
} |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
# Check for PDF::API2 capabilities |
1763
|
0
|
0
|
|
|
|
|
if( ! $self->{pdf}->can($img_call) ) |
1764
|
|
|
|
|
|
|
{ |
1765
|
0
|
|
|
|
|
|
my $ver = PDF::API2->VERSION(); |
1766
|
0
|
|
|
|
|
|
die "Your version of PDF::API2 module ($ver) doesn't support $$imgdata{img_type} images or image file is broken."; |
1767
|
|
|
|
|
|
|
} |
1768
|
|
|
|
|
|
|
else |
1769
|
|
|
|
|
|
|
{ |
1770
|
|
|
|
|
|
|
# Finally try to include image in PDF file |
1771
|
2
|
|
|
2
|
|
23
|
no strict 'refs'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5549
|
|
1772
|
0
|
|
|
|
|
|
$image = $self->{pdf}->$img_call($cell->{image}->{path}); |
1773
|
|
|
|
|
|
|
} |
1774
|
|
|
|
|
|
|
}; |
1775
|
|
|
|
|
|
|
|
1776
|
|
|
|
|
|
|
# Check if some image processing error happened |
1777
|
0
|
0
|
|
|
|
|
if( $@ ) |
1778
|
|
|
|
|
|
|
{ |
1779
|
0
|
|
|
|
|
|
warn 'Error in image ' . $cell->{image}->{path} . ' processing: '.$@; |
1780
|
0
|
|
|
|
|
|
return(); |
1781
|
|
|
|
|
|
|
} |
1782
|
|
|
|
|
|
|
|
1783
|
|
|
|
|
|
|
# Relative or absolute positioning is handled here... |
1784
|
0
|
0
|
|
|
|
|
my $img_x_pos = exists $cell->{x} ? $cell->{x} : $cell->{x_border}; |
1785
|
0
|
0
|
|
|
|
|
my $img_y_pos = exists $cell->{y} ? $cell->{y} : $self->{y}; |
1786
|
|
|
|
|
|
|
|
1787
|
|
|
|
|
|
|
# Alignment |
1788
|
0
|
0
|
0
|
|
|
|
if ( $cell->{align} && ( $cell->{align} eq 'centre' || $cell->{align} eq 'center' ) ) { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
1789
|
0
|
|
|
|
|
|
$img_x_pos += ( ( $cell->{full_width} - $imgdata->{this_img_x} ) / 2 ); |
1790
|
0
|
|
|
|
|
|
$img_y_pos += ( ( $current_height - $imgdata->{this_img_y} ) / 2 ); |
1791
|
|
|
|
|
|
|
} elsif ( $cell->{align} && $cell->{align} eq 'right') { |
1792
|
0
|
|
|
|
|
|
$img_x_pos += ( $cell->{full_width} - $imgdata->{this_img_x} ) - $cell->{image}->{buffer}; |
1793
|
0
|
|
|
|
|
|
$img_y_pos += ( ( $current_height - $imgdata->{this_img_y} ) / 2 ); |
1794
|
|
|
|
|
|
|
} else { |
1795
|
0
|
|
|
|
|
|
$img_x_pos += $cell->{image}->{buffer}; |
1796
|
0
|
|
|
|
|
|
$img_y_pos += ( ( $current_height - $imgdata->{this_img_y} ) / 2 ); |
1797
|
|
|
|
|
|
|
}; |
1798
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
#warn 'image: '.$cell->{image}->{path}.' scale_ratio:'. $imgdata->{scale_ratio}; |
1800
|
|
|
|
|
|
|
|
1801
|
|
|
|
|
|
|
# Place image onto PDF document's graphics context |
1802
|
0
|
|
|
|
|
|
$gfx->image( |
1803
|
|
|
|
|
|
|
$image, # The image |
1804
|
|
|
|
|
|
|
$img_x_pos, # X |
1805
|
|
|
|
|
|
|
$img_y_pos, # Y |
1806
|
|
|
|
|
|
|
$imgdata->{scale_ratio} # scale |
1807
|
|
|
|
|
|
|
); |
1808
|
|
|
|
|
|
|
|
1809
|
|
|
|
|
|
|
} |
1810
|
|
|
|
|
|
|
|
1811
|
|
|
|
|
|
|
sub get_cell_font |
1812
|
|
|
|
|
|
|
{ |
1813
|
0
|
|
|
0
|
0
|
|
my ( $self, $cell ) = @_; |
1814
|
0
|
0
|
0
|
|
|
|
my $font_type = |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
1815
|
|
|
|
|
|
|
( exists $cell->{bold} && $cell->{bold} ) |
1816
|
|
|
|
|
|
|
? ( exists $cell->{italic} && $cell->{italic} ) ? 'BoldItalic' : 'Bold' |
1817
|
|
|
|
|
|
|
: ( exists $cell->{italic} && $cell->{italic} ) ? 'Italic' : 'Roman'; |
1818
|
0
|
|
0
|
|
|
|
my $font_name = $cell->{font} || $self->{default_font}; |
1819
|
0
|
|
|
|
|
|
return $self->{fonts}->{$font_name}->{$font_type}; |
1820
|
|
|
|
|
|
|
} |
1821
|
|
|
|
|
|
|
|
1822
|
|
|
|
|
|
|
sub render_cell_text { |
1823
|
|
|
|
|
|
|
|
1824
|
0
|
|
|
0
|
0
|
|
my ( $self, $cell, $opt ) = @_; |
1825
|
|
|
|
|
|
|
|
1826
|
0
|
|
|
|
|
|
my $row = $opt->{current_row}; |
1827
|
0
|
|
|
|
|
|
my $type = $opt->{row_type}; |
1828
|
|
|
|
|
|
|
|
1829
|
|
|
|
|
|
|
# Figure out what we're putting into the current cell and set the font and size |
1830
|
|
|
|
|
|
|
# We currently default to Bold if we're doing a header |
1831
|
|
|
|
|
|
|
# We also check for an specific font for this field, or fall back on the report default |
1832
|
|
|
|
|
|
|
|
1833
|
0
|
|
|
|
|
|
my $string; |
1834
|
|
|
|
|
|
|
|
1835
|
0
|
|
|
|
|
|
$self->{txt}->font( $self->get_cell_font($cell), $cell->{font_size} ); |
1836
|
|
|
|
|
|
|
|
1837
|
0
|
0
|
|
|
|
|
if ($type eq 'header') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
|
1839
|
0
|
|
|
|
|
|
$string = $cell->{name}; |
1840
|
|
|
|
|
|
|
|
1841
|
|
|
|
|
|
|
} elsif ( $type eq 'data' ) { |
1842
|
|
|
|
|
|
|
|
1843
|
|
|
|
|
|
|
#$string = $row->[$opt->{cell_counter}]; |
1844
|
0
|
|
|
|
|
|
$string = $opt->{current_value}; |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
} elsif ( $type eq 'group_header' ) { |
1847
|
|
|
|
|
|
|
|
1848
|
|
|
|
|
|
|
# Replaces the `?' char and manages text delimited cells |
1849
|
0
|
|
|
|
|
|
$string = $self->get_cell_text( $row, $cell, $cell->{text} ); |
1850
|
|
|
|
|
|
|
|
1851
|
|
|
|
|
|
|
} elsif ( $type eq 'group_footer' ) { |
1852
|
|
|
|
|
|
|
|
1853
|
0
|
0
|
|
|
|
|
if ( exists $cell->{aggregate_source} ) { |
1854
|
0
|
|
|
|
|
|
my $aggr_field = $self->{data}->{fields}->[ $cell->{aggregate_source} ]; |
1855
|
0
|
0
|
|
|
|
|
if ($cell->{text} eq 'GrandTotals') { |
1856
|
0
|
|
|
|
|
|
$string = $aggr_field->{grand_aggregate_result}; |
1857
|
|
|
|
|
|
|
} else { |
1858
|
0
|
|
|
|
|
|
$string = $aggr_field->{group_results}->{$cell->{text}}; |
1859
|
|
|
|
|
|
|
} |
1860
|
|
|
|
|
|
|
} else { |
1861
|
0
|
|
|
|
|
|
$string = $cell->{text}; |
1862
|
|
|
|
|
|
|
} |
1863
|
|
|
|
|
|
|
|
1864
|
0
|
|
|
|
|
|
$string =~ s/\?/$row/g; # In the case of a group footer, the $row variable is the group value |
1865
|
|
|
|
|
|
|
#$string = $self->get_cell_text($row, $cell, $string); |
1866
|
|
|
|
|
|
|
|
1867
|
|
|
|
|
|
|
} elsif ( $type =~ m/^page/ ) { |
1868
|
|
|
|
|
|
|
|
1869
|
|
|
|
|
|
|
# page_header or page_footer |
1870
|
0
|
|
|
|
|
|
$string = $self->get_cell_text( $row, $cell, $cell->{text} ); |
1871
|
|
|
|
|
|
|
} |
1872
|
|
|
|
|
|
|
|
1873
|
0
|
0
|
|
|
|
|
if ( $cell->{colour_func} ) { |
1874
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
1875
|
0
|
|
|
|
|
|
print "\nRunning colour_func() on data: " . $string . "\n"; |
1876
|
|
|
|
|
|
|
} |
1877
|
0
|
|
0
|
|
|
|
$self->{txt}->fillcolor( $cell->{colour_func}( $string, $row, $opt ) || "black" ); |
1878
|
|
|
|
|
|
|
} else { |
1879
|
0
|
|
0
|
|
|
|
$self->{txt}->fillcolor( $cell->{colour} || "black" ); |
1880
|
|
|
|
|
|
|
} |
1881
|
|
|
|
|
|
|
|
1882
|
|
|
|
|
|
|
# Formatting |
1883
|
0
|
0
|
0
|
|
|
|
if ( $type ne 'header' && $cell->{format} ) { |
|
|
0
|
0
|
|
|
|
|
1884
|
|
|
|
|
|
|
|
1885
|
|
|
|
|
|
|
# The new ( v1.4 ) formatter hash |
1886
|
0
|
|
|
|
|
|
$string = $self->format_number( |
1887
|
|
|
|
|
|
|
$cell->{format}, |
1888
|
|
|
|
|
|
|
$string |
1889
|
|
|
|
|
|
|
); |
1890
|
|
|
|
|
|
|
|
1891
|
|
|
|
|
|
|
} elsif ( $cell->{type} && $cell->{type} =~ /^custom:(.+)$/ ) { |
1892
|
|
|
|
|
|
|
|
1893
|
|
|
|
|
|
|
# Custom formatter, in the legacy 'type' key |
1894
|
|
|
|
|
|
|
# Should this be renamed to 'format' too? |
1895
|
|
|
|
|
|
|
|
1896
|
|
|
|
|
|
|
# TODO Better develop custom cell type? |
1897
|
|
|
|
|
|
|
# TODO How do we specify the custom formatter object? |
1898
|
|
|
|
|
|
|
|
1899
|
0
|
|
|
|
|
|
eval "require $1"; |
1900
|
0
|
0
|
|
|
|
|
if( $@ ) |
1901
|
|
|
|
|
|
|
{ |
1902
|
0
|
|
|
|
|
|
warn "Cell custom formatter class $1 was not found or had errors: $@"; |
1903
|
|
|
|
|
|
|
} |
1904
|
|
|
|
|
|
|
|
1905
|
0
|
|
|
|
|
|
my $formatter_obj = $1->new(); |
1906
|
0
|
|
|
|
|
|
$string = $formatter_obj->format({ cell => $cell, options => $opt, string => $string }); |
1907
|
|
|
|
|
|
|
|
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
} |
1910
|
|
|
|
|
|
|
|
1911
|
|
|
|
|
|
|
# Line height = font size + text whitespace |
1912
|
|
|
|
|
|
|
# TODO Find a better way to calculate this (external property?) |
1913
|
|
|
|
|
|
|
# I ( Dan ) am pretty sure this calculation is OK now |
1914
|
0
|
|
|
|
|
|
my $line_height = $cell->{font_size} + $cell->{text_whitespace}; |
1915
|
|
|
|
|
|
|
|
1916
|
|
|
|
|
|
|
# Wrap text |
1917
|
0
|
0
|
|
|
|
|
if ( $cell->{wrap_text} ) { |
1918
|
0
|
|
|
|
|
|
$string = $self->wrap_text( |
1919
|
|
|
|
|
|
|
{ |
1920
|
|
|
|
|
|
|
string => $string, |
1921
|
|
|
|
|
|
|
text_width => $cell->{text_width}, |
1922
|
|
|
|
|
|
|
strip_breaks => $cell->{strip_breaks} |
1923
|
|
|
|
|
|
|
} |
1924
|
|
|
|
|
|
|
); |
1925
|
|
|
|
|
|
|
} |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
# Alignment and position |
1928
|
0
|
0
|
0
|
|
|
|
my $y_pos = exists $cell->{y} ? $cell->{y} : |
1929
|
|
|
|
|
|
|
$self->{y} + ( $cell->{text_whitespace} || 0 ) # The space needed for the 1st row |
1930
|
|
|
|
|
|
|
+ ( $string =~ tr/\n/\n/ * $line_height ); # The number of new-line characters |
1931
|
|
|
|
|
|
|
|
1932
|
0
|
0
|
0
|
|
|
|
my $align = exists $cell->{align} ? substr($cell->{align} || 'left', 0, 1) : 'l'; |
1933
|
|
|
|
|
|
|
|
1934
|
|
|
|
|
|
|
# If cell is absolutely positioned (y), we should avoid automatic page break. |
1935
|
|
|
|
|
|
|
# This is intuitive to do, I think... |
1936
|
0
|
|
|
|
|
|
my $cell_abs_pos = exists $cell->{y}; |
1937
|
|
|
|
|
|
|
|
1938
|
|
|
|
|
|
|
# Handle multiline text |
1939
|
|
|
|
|
|
|
|
1940
|
|
|
|
|
|
|
# Whatever the format (Dos/Unix/Mac/Amiga), this should correctly split rows |
1941
|
|
|
|
|
|
|
# NOTE: This breaks rendering of blank lines |
1942
|
|
|
|
|
|
|
# TODO Check with Cosimo why we're stripping blank rows |
1943
|
|
|
|
|
|
|
#my @text_rows = split /[\r\n]+\s*/ => $string; |
1944
|
|
|
|
|
|
|
|
1945
|
0
|
|
|
|
|
|
my @text_rows = split /\n/, $string; |
1946
|
|
|
|
|
|
|
|
1947
|
0
|
|
|
|
|
|
for $string ( @text_rows ) { |
1948
|
|
|
|
|
|
|
|
1949
|
|
|
|
|
|
|
# Skip empty lines ... but NOT strings that eq "0" |
1950
|
|
|
|
|
|
|
# We still want to be able to render the character 0 |
1951
|
|
|
|
|
|
|
# TODO Why are we doing this? Don't. It breaks rendering blank lines |
1952
|
|
|
|
|
|
|
|
1953
|
|
|
|
|
|
|
# next unless ( $string || $string eq "0" ); |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
# Skip strings with only whitespace |
1956
|
|
|
|
|
|
|
# TODO Why is this here. It breaks rendering for strings that start with a space character |
1957
|
|
|
|
|
|
|
# next if $string =~ /^\s+/; |
1958
|
|
|
|
|
|
|
|
1959
|
|
|
|
|
|
|
# Make sure the current string fits inside the current cell |
1960
|
|
|
|
|
|
|
# Beware: if text_width < 0, there is something wrong with `percent' attribute. |
1961
|
|
|
|
|
|
|
# Maybe it hasn't been set... |
1962
|
|
|
|
|
|
|
|
1963
|
0
|
0
|
|
|
|
|
if ( $cell->{text_width} > 0 ) { |
1964
|
0
|
|
0
|
|
|
|
while ( $string && $self->{txt}->advancewidth( $string ) > $cell->{text_width}) { |
1965
|
0
|
|
|
|
|
|
chop($string); |
1966
|
|
|
|
|
|
|
} |
1967
|
|
|
|
|
|
|
} |
1968
|
|
|
|
|
|
|
|
1969
|
|
|
|
|
|
|
#if( $self->{debug} ) |
1970
|
|
|
|
|
|
|
#{ |
1971
|
|
|
|
|
|
|
# print 'Text `', $string, '\' at (', $x_pos, ',' , $y_pos, ') align: '.$cell->{align}, "\n"; |
1972
|
|
|
|
|
|
|
#} |
1973
|
|
|
|
|
|
|
|
1974
|
|
|
|
|
|
|
# We have to do X alignment inside the multiline text loop here ... |
1975
|
0
|
0
|
|
|
|
|
my $x_pos = exists $cell->{x} ? $cell->{x} : $cell->{x_text}; |
1976
|
|
|
|
|
|
|
|
1977
|
0
|
0
|
0
|
|
|
|
if ( $align eq 'l' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1978
|
|
|
|
|
|
|
|
1979
|
|
|
|
|
|
|
# Default alignment if left-aligned |
1980
|
0
|
|
|
|
|
|
$self->{txt}->translate( $x_pos, $y_pos ); |
1981
|
0
|
|
|
|
|
|
$self->{txt}->text( $string ); |
1982
|
|
|
|
|
|
|
|
1983
|
|
|
|
|
|
|
} elsif ( $align eq 'c' || $type eq 'header' ) { |
1984
|
|
|
|
|
|
|
|
1985
|
|
|
|
|
|
|
# Calculate the width of the string, and move to the right so there's an |
1986
|
|
|
|
|
|
|
# even gap at both sides, and render left-aligned from there |
1987
|
|
|
|
|
|
|
|
1988
|
0
|
|
|
|
|
|
my $string_width = $self->{txt}->advancewidth( $string ); |
1989
|
|
|
|
|
|
|
|
1990
|
0
|
0
|
|
|
|
|
my $x_offset = $cell_abs_pos |
1991
|
|
|
|
|
|
|
? - ($string_width >> 1) |
1992
|
|
|
|
|
|
|
: ( $cell->{text_width} - $string_width ) >> 1; |
1993
|
|
|
|
|
|
|
|
1994
|
0
|
|
|
|
|
|
$x_pos += $x_offset; |
1995
|
0
|
|
|
|
|
|
$self->{txt}->translate( $x_pos, $y_pos ); |
1996
|
0
|
|
|
|
|
|
$self->{txt}->text( $string ); |
1997
|
|
|
|
|
|
|
|
1998
|
|
|
|
|
|
|
} elsif ( $align eq 'r' ) { |
1999
|
|
|
|
|
|
|
|
2000
|
0
|
0
|
|
|
|
|
if( $cell_abs_pos ) { |
2001
|
0
|
|
|
|
|
|
$x_pos -= $self->{txt}->advancewidth( $string ) >> 1; |
2002
|
|
|
|
|
|
|
} else { |
2003
|
0
|
|
|
|
|
|
$x_pos += $cell->{text_width}; |
2004
|
|
|
|
|
|
|
} |
2005
|
|
|
|
|
|
|
|
2006
|
0
|
|
|
|
|
|
$self->{txt}->translate( $x_pos, $y_pos ); |
2007
|
0
|
|
|
|
|
|
$self->{txt}->text_right($string); |
2008
|
|
|
|
|
|
|
|
2009
|
|
|
|
|
|
|
} elsif ( $align eq 'j' ) { |
2010
|
|
|
|
|
|
|
|
2011
|
|
|
|
|
|
|
# Justify text |
2012
|
|
|
|
|
|
|
# This is largely taken from a brilliant example at: http://incompetech.com/gallimaufry/perl_api2_justify.html |
2013
|
|
|
|
|
|
|
|
2014
|
|
|
|
|
|
|
# Set up the control |
2015
|
0
|
|
|
|
|
|
$self->{txt}->charspace( 0 ); |
2016
|
|
|
|
|
|
|
|
2017
|
|
|
|
|
|
|
# Calculate the width at this default spacing |
2018
|
0
|
|
|
|
|
|
my $standard_width = $self->{txt}->advancewidth( $string ); |
2019
|
|
|
|
|
|
|
|
2020
|
|
|
|
|
|
|
# Now the experiment |
2021
|
0
|
|
|
|
|
|
$self->{txt}->charspace( 1 ); |
2022
|
|
|
|
|
|
|
|
2023
|
0
|
|
|
|
|
|
my $experiment_width = $self->{txt}->advancewidth( $string ); |
2024
|
|
|
|
|
|
|
|
2025
|
|
|
|
|
|
|
# SINCE 0 -> $nominal AND 1 -> $experiment ... WTF was he on about here? |
2026
|
0
|
0
|
|
|
|
|
if ( $standard_width ) { |
2027
|
|
|
|
|
|
|
|
2028
|
0
|
|
|
|
|
|
my $diff = $experiment_width - $standard_width; |
2029
|
0
|
|
|
|
|
|
my $min = $cell->{text_width} - $standard_width; |
2030
|
0
|
|
|
|
|
|
my $target = $min / $diff; |
2031
|
|
|
|
|
|
|
|
2032
|
|
|
|
|
|
|
# TODO Provide a 'maxcharspace' option? How about a normal charspace option? |
2033
|
|
|
|
|
|
|
# TODO Is there a more elegent way to do this? |
2034
|
|
|
|
|
|
|
|
2035
|
0
|
0
|
|
|
|
|
$target = 0 if ( $target > 1 ); # charspacing > 1 looks kinda dodgy, so don't bother with justifying in this case |
2036
|
|
|
|
|
|
|
|
2037
|
|
|
|
|
|
|
# Set the target charspace |
2038
|
0
|
|
|
|
|
|
$self->{txt}->charspace( $target ); |
2039
|
|
|
|
|
|
|
|
2040
|
|
|
|
|
|
|
# Render |
2041
|
0
|
|
|
|
|
|
$self->{txt}->translate( $x_pos, $y_pos ); |
2042
|
0
|
|
|
|
|
|
$self->{txt}->text( $string ); |
2043
|
|
|
|
|
|
|
|
2044
|
|
|
|
|
|
|
# Default back to 0 charspace |
2045
|
0
|
|
|
|
|
|
$self->{txt}->charspace( 0 ); |
2046
|
|
|
|
|
|
|
|
2047
|
|
|
|
|
|
|
} |
2048
|
|
|
|
|
|
|
|
2049
|
|
|
|
|
|
|
} |
2050
|
|
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
# XXX Empirical result? Is there a text line_height information? |
2052
|
0
|
|
|
|
|
|
$y_pos -= $line_height; |
2053
|
|
|
|
|
|
|
|
2054
|
|
|
|
|
|
|
# Run empty on page space? Make a page break |
2055
|
|
|
|
|
|
|
# Dan's note: THIS SHOULD *NEVER* HAPPEN. |
2056
|
|
|
|
|
|
|
# If it does, something is wrong with our y-space calculation |
2057
|
0
|
0
|
0
|
|
|
|
if( $cell_abs_pos && $y_pos < $line_height ) { |
2058
|
0
|
|
|
|
|
|
warn "* * * render_cell_text() is requesting a new page * * *\n"; |
2059
|
0
|
|
|
|
|
|
warn "* * * please check y-space calculate_y_needed() * * *\n"; |
2060
|
0
|
|
|
|
|
|
warn "* * * this MUST be a bug ... * * *\n"; |
2061
|
0
|
|
|
|
|
|
$self->new_page(); |
2062
|
|
|
|
|
|
|
} |
2063
|
|
|
|
|
|
|
|
2064
|
|
|
|
|
|
|
} |
2065
|
|
|
|
|
|
|
|
2066
|
|
|
|
|
|
|
} |
2067
|
|
|
|
|
|
|
|
2068
|
|
|
|
|
|
|
sub wrap_text { |
2069
|
|
|
|
|
|
|
|
2070
|
|
|
|
|
|
|
# TODO FIXME This is incredibly slow. |
2071
|
|
|
|
|
|
|
# Someone, please fix me .... |
2072
|
|
|
|
|
|
|
|
2073
|
0
|
|
|
0
|
1
|
|
my ( $self, $options ) = @_; |
2074
|
|
|
|
|
|
|
|
2075
|
0
|
|
|
|
|
|
my $string = $options->{string}; |
2076
|
0
|
|
|
|
|
|
my $text_width = $options->{text_width}; |
2077
|
|
|
|
|
|
|
|
2078
|
0
|
0
|
|
|
|
|
if ( $text_width == 0 ) { |
2079
|
0
|
|
|
|
|
|
return $string; |
2080
|
|
|
|
|
|
|
} |
2081
|
|
|
|
|
|
|
|
2082
|
|
|
|
|
|
|
# Replace \r\n with \n |
2083
|
0
|
|
|
|
|
|
$string =~ s/\r\n/\n/g; |
2084
|
|
|
|
|
|
|
|
2085
|
|
|
|
|
|
|
# Remove line breaks? |
2086
|
0
|
0
|
|
|
|
|
if ( $options->{strip_breaks} ) { |
2087
|
0
|
|
|
|
|
|
$string =~ s/\n//g; |
2088
|
|
|
|
|
|
|
} |
2089
|
|
|
|
|
|
|
|
2090
|
0
|
|
|
|
|
|
my @wrapped_text; |
2091
|
0
|
|
|
|
|
|
my @paragraphs = split /\n/, $string; |
2092
|
|
|
|
|
|
|
|
2093
|
|
|
|
|
|
|
# We want to maintain any existing line breaks, |
2094
|
|
|
|
|
|
|
# and also add new line breaks if the text won't fit on 1 line |
2095
|
|
|
|
|
|
|
|
2096
|
0
|
|
|
|
|
|
foreach my $paragraph ( @paragraphs ) { |
2097
|
|
|
|
|
|
|
|
2098
|
|
|
|
|
|
|
# We need to do this to preserve blank lines ( it slips through the loop below ) |
2099
|
0
|
0
|
|
|
|
|
if ( $paragraph eq '' ) { |
2100
|
0
|
|
|
|
|
|
push @wrapped_text, $paragraph; |
2101
|
|
|
|
|
|
|
} |
2102
|
|
|
|
|
|
|
|
2103
|
0
|
|
|
|
|
|
while ( $paragraph ) { |
2104
|
|
|
|
|
|
|
|
2105
|
0
|
|
|
|
|
|
my $position = 0; |
2106
|
0
|
|
|
|
|
|
my $last_space = 0; |
2107
|
|
|
|
|
|
|
|
2108
|
0
|
|
0
|
|
|
|
while ( |
2109
|
|
|
|
|
|
|
( $self->{txt}->advancewidth( substr( $paragraph, 0, $position ) ) < $text_width ) |
2110
|
|
|
|
|
|
|
&& ( $position < length( $paragraph ) ) |
2111
|
|
|
|
|
|
|
) { |
2112
|
0
|
0
|
|
|
|
|
if ( substr( $paragraph, $position, 1 ) eq " " ) { |
2113
|
0
|
|
|
|
|
|
$last_space = $position; |
2114
|
|
|
|
|
|
|
} |
2115
|
0
|
|
|
|
|
|
$position ++; |
2116
|
|
|
|
|
|
|
} |
2117
|
|
|
|
|
|
|
|
2118
|
0
|
|
|
|
|
|
my $length; |
2119
|
|
|
|
|
|
|
|
2120
|
0
|
0
|
|
|
|
|
if ( $position == length( $paragraph ) ) { |
2121
|
|
|
|
|
|
|
|
2122
|
|
|
|
|
|
|
# This bit doesn't need wrapping. Take it all |
2123
|
0
|
|
|
|
|
|
$length = $position; |
2124
|
|
|
|
|
|
|
|
2125
|
|
|
|
|
|
|
} else { |
2126
|
|
|
|
|
|
|
|
2127
|
|
|
|
|
|
|
# We didn't get to the end of the string, so this bit *does* need wrapping |
2128
|
|
|
|
|
|
|
# Go back to the last space |
2129
|
|
|
|
|
|
|
|
2130
|
0
|
|
|
|
|
|
$length = $last_space; |
2131
|
|
|
|
|
|
|
|
2132
|
|
|
|
|
|
|
} |
2133
|
|
|
|
|
|
|
|
2134
|
0
|
0
|
|
|
|
|
if ( $self->{debug} ) { |
2135
|
0
|
|
|
|
|
|
print "PDF::ReportWriter::wrap_text returning line: " . substr( $paragraph, 0, $length ) . "\n\n"; |
2136
|
|
|
|
|
|
|
} |
2137
|
|
|
|
|
|
|
|
2138
|
0
|
|
|
|
|
|
push @wrapped_text, substr( $paragraph, 0, $length ); |
2139
|
|
|
|
|
|
|
|
2140
|
0
|
|
|
|
|
|
$paragraph = substr( $paragraph, $length + 1, length( $paragraph ) - $length ); |
2141
|
|
|
|
|
|
|
|
2142
|
|
|
|
|
|
|
} |
2143
|
|
|
|
|
|
|
|
2144
|
|
|
|
|
|
|
} |
2145
|
|
|
|
|
|
|
|
2146
|
0
|
|
|
|
|
|
return join "\n", @wrapped_text; |
2147
|
|
|
|
|
|
|
|
2148
|
|
|
|
|
|
|
} |
2149
|
|
|
|
|
|
|
|
2150
|
|
|
|
|
|
|
sub format_number { |
2151
|
|
|
|
|
|
|
|
2152
|
0
|
|
|
0
|
0
|
|
my ( $self, $options, $value ) = @_; |
2153
|
|
|
|
|
|
|
|
2154
|
|
|
|
|
|
|
# $options can contain the following: |
2155
|
|
|
|
|
|
|
# - currency BOOLEAN |
2156
|
|
|
|
|
|
|
# - decimal_places INT ... or |
2157
|
|
|
|
|
|
|
# - decimals INT |
2158
|
|
|
|
|
|
|
# - decimal_fill BOOLEAN |
2159
|
|
|
|
|
|
|
# - separate_thousands BOOLEAN |
2160
|
|
|
|
|
|
|
# - null_if_zero BOOLEAN |
2161
|
|
|
|
|
|
|
|
2162
|
0
|
|
|
|
|
|
my $calc = $value; |
2163
|
|
|
|
|
|
|
|
2164
|
0
|
|
|
|
|
|
my $final; |
2165
|
|
|
|
|
|
|
|
2166
|
|
|
|
|
|
|
# Support for null_if_zero |
2167
|
0
|
0
|
0
|
|
|
|
if ( exists $options->{null_if_zero} && $options->{null_if_zero} && $value == 0 ) { |
|
|
|
0
|
|
|
|
|
2168
|
0
|
|
|
|
|
|
return undef; |
2169
|
|
|
|
|
|
|
} |
2170
|
|
|
|
|
|
|
|
2171
|
0
|
0
|
|
|
|
|
my $decimals = exists $options->{decimal_places} ? $options->{decimal_places} : $options->{decimals}; |
2172
|
|
|
|
|
|
|
|
2173
|
|
|
|
|
|
|
# Allow for our number of decimal places |
2174
|
0
|
0
|
|
|
|
|
if ( $decimals ) { |
2175
|
0
|
|
|
|
|
|
$calc *= 10 ** $decimals; |
2176
|
|
|
|
|
|
|
} |
2177
|
|
|
|
|
|
|
|
2178
|
|
|
|
|
|
|
# Round |
2179
|
0
|
|
|
|
|
|
$calc = int( $calc + .5 * ( $calc <=> 0 ) ); |
2180
|
|
|
|
|
|
|
|
2181
|
|
|
|
|
|
|
# Get decimals back |
2182
|
0
|
0
|
|
|
|
|
if ( $decimals ) { |
2183
|
0
|
|
|
|
|
|
$calc /= 10 ** $decimals; |
2184
|
|
|
|
|
|
|
} |
2185
|
|
|
|
|
|
|
|
2186
|
|
|
|
|
|
|
# Split whole and decimal parts |
2187
|
0
|
|
|
|
|
|
my ( $whole, $decimal ) = split /\./, $calc; |
2188
|
|
|
|
|
|
|
|
2189
|
|
|
|
|
|
|
# Pad decimals |
2190
|
0
|
0
|
|
|
|
|
if ( $options->{decimal_fill} ) { |
2191
|
0
|
0
|
|
|
|
|
if ( defined $decimal ) { |
2192
|
0
|
|
|
|
|
|
$decimal = $decimal . "0" x ( $decimals - length( $decimal ) ); |
2193
|
|
|
|
|
|
|
} else { |
2194
|
0
|
|
|
|
|
|
$decimal = "0" x $decimals; |
2195
|
|
|
|
|
|
|
} |
2196
|
|
|
|
|
|
|
} |
2197
|
|
|
|
|
|
|
|
2198
|
|
|
|
|
|
|
# Separate thousands |
2199
|
0
|
0
|
|
|
|
|
if ( $options->{separate_thousands} ) { |
2200
|
|
|
|
|
|
|
# This BS comes from 'perldoc -q numbers' |
2201
|
0
|
|
|
|
|
|
$whole =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; |
2202
|
|
|
|
|
|
|
} |
2203
|
|
|
|
|
|
|
|
2204
|
|
|
|
|
|
|
# Currency? |
2205
|
0
|
0
|
|
|
|
|
if ( $options->{currency} ) { |
2206
|
0
|
|
|
|
|
|
$final = '$'; |
2207
|
|
|
|
|
|
|
} |
2208
|
|
|
|
|
|
|
|
2209
|
|
|
|
|
|
|
# Don't put a decimal point if there are no decimals |
2210
|
0
|
0
|
|
|
|
|
if ( defined $decimal ) { |
2211
|
0
|
|
|
|
|
|
$final .= $whole . "." . $decimal; |
2212
|
|
|
|
|
|
|
} else { |
2213
|
0
|
|
|
|
|
|
$final .= $whole; |
2214
|
|
|
|
|
|
|
} |
2215
|
|
|
|
|
|
|
|
2216
|
0
|
|
|
|
|
|
return $final; |
2217
|
|
|
|
|
|
|
|
2218
|
|
|
|
|
|
|
} |
2219
|
|
|
|
|
|
|
|
2220
|
|
|
|
|
|
|
sub render_footers |
2221
|
|
|
|
|
|
|
{ |
2222
|
0
|
|
|
0
|
0
|
|
my $self = $_[0]; |
2223
|
|
|
|
|
|
|
|
2224
|
|
|
|
|
|
|
# If no pages defined, there are no footers to render |
2225
|
0
|
0
|
0
|
|
|
|
if( ! exists $self->{pages} || ! ref $self->{pages} ) |
2226
|
|
|
|
|
|
|
{ |
2227
|
0
|
|
|
|
|
|
return; |
2228
|
|
|
|
|
|
|
} |
2229
|
|
|
|
|
|
|
|
2230
|
0
|
|
|
|
|
|
my $total_pages = scalar@{$self->{pages}}; |
|
0
|
|
|
|
|
|
|
2231
|
|
|
|
|
|
|
|
2232
|
|
|
|
|
|
|
# We first loop through all the pages and add footers to them |
2233
|
0
|
|
|
|
|
|
for my $this_page_no ( 0 .. $total_pages - 1 ) { |
2234
|
|
|
|
|
|
|
|
2235
|
0
|
|
|
|
|
|
$self->{txt} = $self->{pages}[$this_page_no]->text; |
2236
|
0
|
|
|
|
|
|
$self->{line} = $self->{pages}[$this_page_no]->gfx; |
2237
|
0
|
|
|
|
|
|
$self->{shape} = $self->{pages}[$this_page_no]->gfx(1); |
2238
|
|
|
|
|
|
|
|
2239
|
0
|
|
|
|
|
|
my $localtime = localtime time; |
2240
|
|
|
|
|
|
|
|
2241
|
|
|
|
|
|
|
# Get the current_height of the footer - we have to move this much *above* the lower_margin, |
2242
|
|
|
|
|
|
|
# as our render_row() will move this much down before rendering |
2243
|
0
|
|
|
|
|
|
my $size_calculation = $self->calculate_y_needed( |
2244
|
|
|
|
|
|
|
{ |
2245
|
|
|
|
|
|
|
cells => $self->{page_footers}[$this_page_no], |
2246
|
|
|
|
|
|
|
max_cell_height => $self->{page_footer_max_cell_height} |
2247
|
|
|
|
|
|
|
} |
2248
|
|
|
|
|
|
|
); |
2249
|
|
|
|
|
|
|
|
2250
|
0
|
|
|
|
|
|
$self->{y} = $self->{lower_margin} + $size_calculation->{current_height}; |
2251
|
|
|
|
|
|
|
|
2252
|
0
|
|
|
|
|
|
$self->render_row( |
2253
|
|
|
|
|
|
|
$self->{page_footers}[$this_page_no], |
2254
|
|
|
|
|
|
|
{ |
2255
|
|
|
|
|
|
|
current_page => $this_page_no + 1, |
2256
|
|
|
|
|
|
|
total_pages => $total_pages, |
2257
|
|
|
|
|
|
|
current_time => $localtime |
2258
|
|
|
|
|
|
|
}, |
2259
|
|
|
|
|
|
|
'page_footer', |
2260
|
|
|
|
|
|
|
$self->{page_footer_max_cell_height}, |
2261
|
|
|
|
|
|
|
0, |
2262
|
|
|
|
|
|
|
0 |
2263
|
|
|
|
|
|
|
); |
2264
|
|
|
|
|
|
|
|
2265
|
|
|
|
|
|
|
} |
2266
|
|
|
|
|
|
|
|
2267
|
|
|
|
|
|
|
} |
2268
|
|
|
|
|
|
|
|
2269
|
|
|
|
|
|
|
sub stringify |
2270
|
|
|
|
|
|
|
{ |
2271
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2272
|
0
|
|
|
|
|
|
my $pdf_stream; |
2273
|
|
|
|
|
|
|
|
2274
|
0
|
|
|
|
|
|
$self->render_footers(); |
2275
|
|
|
|
|
|
|
|
2276
|
0
|
|
|
|
|
|
$pdf_stream = $self->{pdf}->stringify; |
2277
|
0
|
|
|
|
|
|
$self->{pdf}->end; |
2278
|
|
|
|
|
|
|
|
2279
|
0
|
|
|
|
|
|
return($pdf_stream); |
2280
|
|
|
|
|
|
|
} |
2281
|
|
|
|
|
|
|
|
2282
|
|
|
|
|
|
|
sub save { |
2283
|
|
|
|
|
|
|
|
2284
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2285
|
0
|
|
|
|
|
|
my $ok = 0; |
2286
|
|
|
|
|
|
|
|
2287
|
0
|
|
|
|
|
|
$self->render_footers(); |
2288
|
|
|
|
|
|
|
|
2289
|
0
|
|
|
|
|
|
$ok = $self->{pdf}->saveas($self->{destination}); |
2290
|
0
|
|
|
|
|
|
$self->{pdf}->end(); |
2291
|
|
|
|
|
|
|
|
2292
|
|
|
|
|
|
|
# TODO Check result of PDF::API2 saveas() and end() methods? |
2293
|
0
|
|
|
|
|
|
return(1); |
2294
|
|
|
|
|
|
|
|
2295
|
|
|
|
|
|
|
} |
2296
|
|
|
|
|
|
|
|
2297
|
|
|
|
|
|
|
sub saveas { |
2298
|
|
|
|
|
|
|
|
2299
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2300
|
0
|
|
|
|
|
|
my $file = shift; |
2301
|
0
|
|
|
|
|
|
$self->{destination} = $file; |
2302
|
0
|
|
|
|
|
|
$self->save(); |
2303
|
|
|
|
|
|
|
|
2304
|
|
|
|
|
|
|
} |
2305
|
|
|
|
|
|
|
|
2306
|
|
|
|
|
|
|
# |
2307
|
|
|
|
|
|
|
# Spool a report to CUPS print queue for direct printing |
2308
|
|
|
|
|
|
|
# |
2309
|
|
|
|
|
|
|
# $self->print({ |
2310
|
|
|
|
|
|
|
# tempdir => '/tmp', |
2311
|
|
|
|
|
|
|
# command => '/usr/bin/lpr.cups', |
2312
|
|
|
|
|
|
|
# printer => 'myprinter', |
2313
|
|
|
|
|
|
|
# }); |
2314
|
|
|
|
|
|
|
# |
2315
|
|
|
|
|
|
|
sub print { |
2316
|
|
|
|
|
|
|
|
2317
|
2
|
|
|
2
|
|
4361
|
use File::Temp (); |
|
2
|
|
|
|
|
31773
|
|
|
2
|
|
|
|
|
9056
|
|
2318
|
|
|
|
|
|
|
|
2319
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2320
|
0
|
|
|
|
|
|
my $opt = shift; |
2321
|
0
|
|
|
|
|
|
my @cups_locations = qw(/usr/bin/lpr.cups /usr/bin/lpr-cups /usr/bin/lpr); |
2322
|
|
|
|
|
|
|
|
2323
|
|
|
|
|
|
|
# Apply option defaults |
2324
|
0
|
0
|
|
|
|
|
my $unlink_spool = exists $opt->{unlink} ? $opt->{unlink} : 1; |
2325
|
0
|
|
0
|
|
|
|
$opt->{tempdir} ||= '/tmp'; |
2326
|
|
|
|
|
|
|
|
2327
|
|
|
|
|
|
|
# Try to find a suitable cups command |
2328
|
0
|
0
|
|
|
|
|
if( ! $opt->{command} ) |
2329
|
|
|
|
|
|
|
{ |
2330
|
0
|
|
|
|
|
|
my $cmd; |
2331
|
0
|
|
0
|
|
|
|
do { |
2332
|
0
|
0
|
|
|
|
|
last unless @cups_locations; |
2333
|
0
|
|
|
|
|
|
$cmd = shift @cups_locations; |
2334
|
|
|
|
|
|
|
} until ( -e $cmd && -x $cmd ); |
2335
|
|
|
|
|
|
|
|
2336
|
0
|
0
|
|
|
|
|
if( ! $cmd ) |
2337
|
|
|
|
|
|
|
{ |
2338
|
0
|
|
|
|
|
|
warn 'Can\'t find a lpr/cups shell command to run!'; |
2339
|
0
|
|
|
|
|
|
return undef; |
2340
|
|
|
|
|
|
|
} |
2341
|
|
|
|
|
|
|
|
2342
|
|
|
|
|
|
|
# Ok, found a cups/lpr command |
2343
|
0
|
|
|
|
|
|
$opt->{command} = $cmd; |
2344
|
|
|
|
|
|
|
} |
2345
|
|
|
|
|
|
|
|
2346
|
0
|
|
|
|
|
|
my $cups_cmd = $opt->{command}; |
2347
|
0
|
|
|
|
|
|
my $ok = my $err = 0; |
2348
|
0
|
|
|
|
|
|
my $printer; |
2349
|
|
|
|
|
|
|
|
2350
|
|
|
|
|
|
|
# Add printer queue name if supplied |
2351
|
0
|
0
|
|
|
|
|
if( $printer = $opt->{printer} ) |
2352
|
|
|
|
|
|
|
{ |
2353
|
0
|
|
|
|
|
|
$cups_cmd .= " -P $printer"; |
2354
|
|
|
|
|
|
|
} |
2355
|
|
|
|
|
|
|
|
2356
|
|
|
|
|
|
|
# Generate a temporary file to store pdf content |
2357
|
0
|
|
|
|
|
|
my($temp_file, $temp_name) = File::Temp::tempfile('reportXXXXXXX', DIR=>$opt->{tempdir}, SUFFIX=>'.pdf'); |
2358
|
|
|
|
|
|
|
|
2359
|
|
|
|
|
|
|
# Print all pdf stream to file |
2360
|
0
|
0
|
|
|
|
|
if( $temp_file ) |
2361
|
|
|
|
|
|
|
{ |
2362
|
0
|
|
|
|
|
|
binmode $temp_file; |
2363
|
0
|
|
|
|
|
|
$ok = print $temp_file $self->stringify(); |
2364
|
0
|
|
0
|
|
|
|
$ok &&= close $temp_file; |
2365
|
|
|
|
|
|
|
|
2366
|
|
|
|
|
|
|
# Now spool this temp file |
2367
|
0
|
0
|
|
|
|
|
if( $ok ) |
2368
|
|
|
|
|
|
|
{ |
2369
|
0
|
|
|
|
|
|
$cups_cmd .= ' ' . $temp_name; |
2370
|
|
|
|
|
|
|
|
2371
|
|
|
|
|
|
|
# Run spool command and get exit status |
2372
|
0
|
|
0
|
|
|
|
my $exit = system($cups_cmd) && 0xFF; |
2373
|
0
|
|
|
|
|
|
$ok = ($exit == 0); |
2374
|
|
|
|
|
|
|
|
2375
|
0
|
0
|
|
|
|
|
if( ! $ok ) |
2376
|
|
|
|
|
|
|
{ |
2377
|
|
|
|
|
|
|
# ERROR 1: FAILED spooling of report with CUPS |
2378
|
0
|
|
|
|
|
|
$err = 1; |
2379
|
|
|
|
|
|
|
} |
2380
|
|
|
|
|
|
|
|
2381
|
|
|
|
|
|
|
# OK: Report spooled correctly to CUPS printer |
2382
|
|
|
|
|
|
|
|
2383
|
|
|
|
|
|
|
} |
2384
|
|
|
|
|
|
|
else |
2385
|
|
|
|
|
|
|
{ |
2386
|
|
|
|
|
|
|
# ERROR 2: FAILED creation of report spool file |
2387
|
0
|
|
|
|
|
|
$err = 2; |
2388
|
|
|
|
|
|
|
} |
2389
|
|
|
|
|
|
|
|
2390
|
0
|
0
|
|
|
|
|
unlink $temp_name if $unlink_spool; |
2391
|
|
|
|
|
|
|
} |
2392
|
|
|
|
|
|
|
else |
2393
|
|
|
|
|
|
|
{ |
2394
|
|
|
|
|
|
|
# ERROR 3: FAILED opening of a temporary spool file |
2395
|
0
|
|
|
|
|
|
$err = 3; |
2396
|
|
|
|
|
|
|
} |
2397
|
|
|
|
|
|
|
|
2398
|
0
|
|
|
|
|
|
return($err); |
2399
|
|
|
|
|
|
|
} |
2400
|
|
|
|
|
|
|
|
2401
|
|
|
|
|
|
|
# |
2402
|
|
|
|
|
|
|
# Replaces `?' with current value and handles cells with delimiter and index |
2403
|
|
|
|
|
|
|
# Returns the final string value |
2404
|
|
|
|
|
|
|
# |
2405
|
|
|
|
|
|
|
|
2406
|
|
|
|
|
|
|
{ |
2407
|
|
|
|
|
|
|
# Datasource strings regular expression |
2408
|
|
|
|
|
|
|
# Example: `%customers[2,5]%' |
2409
|
|
|
|
|
|
|
my $ds_regex = qr/%(\w+)\[(\d+),(\d+)\]%/o; |
2410
|
|
|
|
|
|
|
|
2411
|
|
|
|
|
|
|
sub get_cell_text { |
2412
|
|
|
|
|
|
|
|
2413
|
0
|
|
|
0
|
0
|
|
my ( $self, $row, $cell, $text ) = @_; |
2414
|
|
|
|
|
|
|
|
2415
|
0
|
|
0
|
|
|
|
my $string = $text || $cell->{text}; |
2416
|
|
|
|
|
|
|
|
2417
|
|
|
|
|
|
|
# If string begins and ends with `%', this is a reference to an external datasource. |
2418
|
|
|
|
|
|
|
# Example: `%mydata[m,n]%' means lookup the tag with name `mydata', |
2419
|
|
|
|
|
|
|
# try to load the records and return the n-th column of the m-th record. |
2420
|
|
|
|
|
|
|
# Also multiple data strings are allowed in a text cell, as in |
2421
|
|
|
|
|
|
|
# `Dear %customers[0,1]% %customers[0,2]%' |
2422
|
|
|
|
|
|
|
|
2423
|
0
|
|
|
|
|
|
while ( $string =~ $ds_regex ) { |
2424
|
|
|
|
|
|
|
|
2425
|
|
|
|
|
|
|
# Lookup from external datasource |
2426
|
0
|
|
|
|
|
|
my $ds_name = $1; |
2427
|
0
|
|
|
|
|
|
my $n_rec = $2; |
2428
|
0
|
|
|
|
|
|
my $n_col = $3; |
2429
|
0
|
|
|
|
|
|
my $ds_value= ''; |
2430
|
|
|
|
|
|
|
|
2431
|
|
|
|
|
|
|
# TODO Here we must cache the results of `get_data' by |
2432
|
|
|
|
|
|
|
# data source name or we could reload many times |
2433
|
|
|
|
|
|
|
# the same data... |
2434
|
0
|
0
|
|
|
|
|
if( my $data = $self->report->get_data( $ds_name ) ) { |
2435
|
0
|
|
|
|
|
|
$ds_value = $data->[$n_rec]->[$n_col]; |
2436
|
|
|
|
|
|
|
} |
2437
|
|
|
|
|
|
|
|
2438
|
0
|
|
|
|
|
|
$string =~ s/$ds_regex/$ds_value/; |
2439
|
|
|
|
|
|
|
|
2440
|
|
|
|
|
|
|
} |
2441
|
|
|
|
|
|
|
|
2442
|
|
|
|
|
|
|
# In case row is a scalar, we are into group cell, |
2443
|
|
|
|
|
|
|
# not data cell rendering. |
2444
|
0
|
0
|
|
|
|
|
if ( ref $row eq 'HASH' ) { |
2445
|
0
|
|
|
|
|
|
$string =~ s/\%PAGE\%/$row->{current_page}/; |
2446
|
0
|
|
|
|
|
|
$string =~ s/\%PAGES\%/$row->{total_pages}/; |
2447
|
|
|
|
|
|
|
} else { |
2448
|
|
|
|
|
|
|
# In case of group headers/footers, $row is a single scalar |
2449
|
0
|
0
|
|
|
|
|
if ( $cell->{delimiter} ) { |
2450
|
|
|
|
|
|
|
# This assumes the delim is a non-alpha char like |,~,!, etc... |
2451
|
0
|
|
|
|
|
|
my $delim = "\\" . $cell->{delimiter}; |
2452
|
0
|
|
|
|
|
|
my $row2 = ( split /$delim/, $row )[ $cell->{index} ]; |
2453
|
0
|
|
|
|
|
|
$string =~ s/\?/$row2/g; |
2454
|
|
|
|
|
|
|
} else { |
2455
|
0
|
|
|
|
|
|
$string =~ s/\?/$row/g; |
2456
|
|
|
|
|
|
|
} |
2457
|
|
|
|
|
|
|
} |
2458
|
|
|
|
|
|
|
|
2459
|
|
|
|
|
|
|
# __generationtime member is set at object initialization (parse_options) |
2460
|
0
|
|
|
|
|
|
$string =~ s/\%TIME\%/$$self{__generationtime}/; |
2461
|
|
|
|
|
|
|
|
2462
|
0
|
|
|
|
|
|
return ( $string ); |
2463
|
|
|
|
|
|
|
|
2464
|
|
|
|
|
|
|
} |
2465
|
|
|
|
|
|
|
|
2466
|
|
|
|
|
|
|
} |
2467
|
|
|
|
|
|
|
|
2468
|
|
|
|
|
|
|
1; |
2469
|
|
|
|
|
|
|
|
2470
|
|
|
|
|
|
|
=head1 NAME |
2471
|
|
|
|
|
|
|
|
2472
|
|
|
|
|
|
|
PDF::ReportWriter |
2473
|
|
|
|
|
|
|
|
2474
|
|
|
|
|
|
|
=head1 DESCRIPTION |
2475
|
|
|
|
|
|
|
|
2476
|
|
|
|
|
|
|
PDF::ReportWriter is designed to create high-quality business reports, for archiving or printing. |
2477
|
|
|
|
|
|
|
|
2478
|
|
|
|
|
|
|
=head1 USAGE |
2479
|
|
|
|
|
|
|
|
2480
|
|
|
|
|
|
|
The example below is purely as a reference inside this documentation to give you an idea of what goes |
2481
|
|
|
|
|
|
|
where. It is not intended as a working example - for a working example, see the demo application package, |
2482
|
|
|
|
|
|
|
distributed separately at http://entropy.homelinux.org/axis_not_evil |
2483
|
|
|
|
|
|
|
|
2484
|
|
|
|
|
|
|
First we set up the top-level report definition and create a new PDF::ReportWriter object ... |
2485
|
|
|
|
|
|
|
|
2486
|
|
|
|
|
|
|
$report = { |
2487
|
|
|
|
|
|
|
|
2488
|
|
|
|
|
|
|
destination => "/home/dan/my_fantastic_report.pdf", |
2489
|
|
|
|
|
|
|
paper => "A4", |
2490
|
|
|
|
|
|
|
orientation => "portrait", |
2491
|
|
|
|
|
|
|
template => '/home/dan/my_page_template.pdf', |
2492
|
|
|
|
|
|
|
font_list => [ "Times" ], |
2493
|
|
|
|
|
|
|
default_font => "Times", |
2494
|
|
|
|
|
|
|
default_font_size => "10", |
2495
|
|
|
|
|
|
|
x_margin => 10 * mm, |
2496
|
|
|
|
|
|
|
y_margin => 10 * mm, |
2497
|
|
|
|
|
|
|
info => { |
2498
|
|
|
|
|
|
|
Author => "Daniel Kasak", |
2499
|
|
|
|
|
|
|
Keywords => "Fantastic, Amazing, Superb", |
2500
|
|
|
|
|
|
|
Subject => "Stuff", |
2501
|
|
|
|
|
|
|
Title => "My Fantastic Report" |
2502
|
|
|
|
|
|
|
} |
2503
|
|
|
|
|
|
|
|
2504
|
|
|
|
|
|
|
}; |
2505
|
|
|
|
|
|
|
|
2506
|
|
|
|
|
|
|
my $pdf = PDF::ReportWriter->new( $report ); |
2507
|
|
|
|
|
|
|
|
2508
|
|
|
|
|
|
|
Next we define our page setup, with a page header ( we can also put a 'footer' object in here as well ) |
2509
|
|
|
|
|
|
|
|
2510
|
|
|
|
|
|
|
my $page = { |
2511
|
|
|
|
|
|
|
|
2512
|
|
|
|
|
|
|
header => [ |
2513
|
|
|
|
|
|
|
{ |
2514
|
|
|
|
|
|
|
percent => 60, |
2515
|
|
|
|
|
|
|
font_size => 15, |
2516
|
|
|
|
|
|
|
align => "left", |
2517
|
|
|
|
|
|
|
text => "My Fantastic Report" |
2518
|
|
|
|
|
|
|
}, |
2519
|
|
|
|
|
|
|
{ |
2520
|
|
|
|
|
|
|
percent => 40, |
2521
|
|
|
|
|
|
|
align => "right", |
2522
|
|
|
|
|
|
|
image => { |
2523
|
|
|
|
|
|
|
path => "/home/dan/fantastic_stuff.png", |
2524
|
|
|
|
|
|
|
scale_to_fit => TRUE |
2525
|
|
|
|
|
|
|
} |
2526
|
|
|
|
|
|
|
} |
2527
|
|
|
|
|
|
|
] |
2528
|
|
|
|
|
|
|
|
2529
|
|
|
|
|
|
|
}; |
2530
|
|
|
|
|
|
|
|
2531
|
|
|
|
|
|
|
Define our fields - which will make up most of the report |
2532
|
|
|
|
|
|
|
|
2533
|
|
|
|
|
|
|
my $fields = [ |
2534
|
|
|
|
|
|
|
|
2535
|
|
|
|
|
|
|
{ |
2536
|
|
|
|
|
|
|
name => "Date", # 'Date' will appear in field headers |
2537
|
|
|
|
|
|
|
percent => 35, # The percentage of X-space the cell will occupy |
2538
|
|
|
|
|
|
|
align => "centre", # Content will be centred |
2539
|
|
|
|
|
|
|
colour => "blue", # Text will be blue |
2540
|
|
|
|
|
|
|
font_size => 12, # Override the default_font_size with '12' for this cell |
2541
|
|
|
|
|
|
|
header_colour => "white" # Field headers will be rendered in white |
2542
|
|
|
|
|
|
|
}, |
2543
|
|
|
|
|
|
|
{ |
2544
|
|
|
|
|
|
|
name => "Item", |
2545
|
|
|
|
|
|
|
percent => 35, |
2546
|
|
|
|
|
|
|
align => "centre", |
2547
|
|
|
|
|
|
|
header_colour => "white", |
2548
|
|
|
|
|
|
|
}, |
2549
|
|
|
|
|
|
|
{ |
2550
|
|
|
|
|
|
|
name => "Appraisal", |
2551
|
|
|
|
|
|
|
percent => 30, |
2552
|
|
|
|
|
|
|
align => "centre", |
2553
|
|
|
|
|
|
|
colour_func => sub { red_if_fantastic(@_); }, # red_if_fantastic() will be called to calculate colour for this cell |
2554
|
|
|
|
|
|
|
aggregate_function => "count" # Items will be counted, and the results stored against this cell |
2555
|
|
|
|
|
|
|
} |
2556
|
|
|
|
|
|
|
|
2557
|
|
|
|
|
|
|
]; |
2558
|
|
|
|
|
|
|
|
2559
|
|
|
|
|
|
|
I've defined a custom colour_func for the 'Appraisal' field, so here's the sub: |
2560
|
|
|
|
|
|
|
|
2561
|
|
|
|
|
|
|
sub red_if_fantastic { |
2562
|
|
|
|
|
|
|
|
2563
|
|
|
|
|
|
|
my $data = shift; |
2564
|
|
|
|
|
|
|
if ( $data eq "Fantastic" ) { |
2565
|
|
|
|
|
|
|
return "red"; |
2566
|
|
|
|
|
|
|
} else { |
2567
|
|
|
|
|
|
|
return "black"; |
2568
|
|
|
|
|
|
|
} |
2569
|
|
|
|
|
|
|
|
2570
|
|
|
|
|
|
|
} |
2571
|
|
|
|
|
|
|
|
2572
|
|
|
|
|
|
|
Define some groups ( or in this case, a single group ) |
2573
|
|
|
|
|
|
|
|
2574
|
|
|
|
|
|
|
my $groups = [ |
2575
|
|
|
|
|
|
|
|
2576
|
|
|
|
|
|
|
{ |
2577
|
|
|
|
|
|
|
name => "DateGroup", # Not particularly important - apart from the special group "GrandTotals" |
2578
|
|
|
|
|
|
|
data_column => 0, # Which column to group on ( 'Date' in this case ) |
2579
|
|
|
|
|
|
|
header => [ |
2580
|
|
|
|
|
|
|
{ |
2581
|
|
|
|
|
|
|
percent => 100, |
2582
|
|
|
|
|
|
|
align => "right", |
2583
|
|
|
|
|
|
|
colour => "white", |
2584
|
|
|
|
|
|
|
background => { # Draw a background for this cell ... |
2585
|
|
|
|
|
|
|
{ |
2586
|
|
|
|
|
|
|
shape => "ellipse", # ... a filled ellipse ... |
2587
|
|
|
|
|
|
|
colour => "blue" # ... and make it blue |
2588
|
|
|
|
|
|
|
} |
2589
|
|
|
|
|
|
|
} |
2590
|
|
|
|
|
|
|
text => "Entries for ?" # ? will be replaced by the current group value ( ie the date ) |
2591
|
|
|
|
|
|
|
} |
2592
|
|
|
|
|
|
|
footer => [ |
2593
|
|
|
|
|
|
|
{ |
2594
|
|
|
|
|
|
|
percent => 70, |
2595
|
|
|
|
|
|
|
align => "right", |
2596
|
|
|
|
|
|
|
text => "Total entries for ?" |
2597
|
|
|
|
|
|
|
}, |
2598
|
|
|
|
|
|
|
{ |
2599
|
|
|
|
|
|
|
percent => 30, |
2600
|
|
|
|
|
|
|
align => "centre", |
2601
|
|
|
|
|
|
|
aggregate_source => 2 # Take figure from field 2 ( which has the aggregate_function on it ) |
2602
|
|
|
|
|
|
|
} |
2603
|
|
|
|
|
|
|
} |
2604
|
|
|
|
|
|
|
|
2605
|
|
|
|
|
|
|
]; |
2606
|
|
|
|
|
|
|
|
2607
|
|
|
|
|
|
|
We need a data array ... |
2608
|
|
|
|
|
|
|
|
2609
|
|
|
|
|
|
|
my $data_array = $dbh->selectall_arrayref( |
2610
|
|
|
|
|
|
|
"select Date, Item, Appraisal from Entries order by Date" |
2611
|
|
|
|
|
|
|
); |
2612
|
|
|
|
|
|
|
|
2613
|
|
|
|
|
|
|
Note that you MUST order the data array, as above, if you want to use grouping. |
2614
|
|
|
|
|
|
|
PDF::ReportWriter doesn't do any ordering of data for you. |
2615
|
|
|
|
|
|
|
|
2616
|
|
|
|
|
|
|
Now we put everything together ... |
2617
|
|
|
|
|
|
|
|
2618
|
|
|
|
|
|
|
my $data = { |
2619
|
|
|
|
|
|
|
|
2620
|
|
|
|
|
|
|
background => { # Set up a default background for all cells ... |
2621
|
|
|
|
|
|
|
border => "grey" # ... a grey border |
2622
|
|
|
|
|
|
|
}, |
2623
|
|
|
|
|
|
|
fields => $fields, |
2624
|
|
|
|
|
|
|
groups => $groups, |
2625
|
|
|
|
|
|
|
page => $page, |
2626
|
|
|
|
|
|
|
data_array => $data_array, |
2627
|
|
|
|
|
|
|
headings => { # This is where we set up field header properties ( not a perfect idea, I know ) |
2628
|
|
|
|
|
|
|
background => { |
2629
|
|
|
|
|
|
|
shape => "box", |
2630
|
|
|
|
|
|
|
colour => "darkgrey" |
2631
|
|
|
|
|
|
|
} |
2632
|
|
|
|
|
|
|
} |
2633
|
|
|
|
|
|
|
|
2634
|
|
|
|
|
|
|
}; |
2635
|
|
|
|
|
|
|
|
2636
|
|
|
|
|
|
|
... and finally pass this into PDF::ReportWriter |
2637
|
|
|
|
|
|
|
|
2638
|
|
|
|
|
|
|
$pdf->render_data( $data ); |
2639
|
|
|
|
|
|
|
|
2640
|
|
|
|
|
|
|
At this point, we can do something like assemble a *completely* new $data object, |
2641
|
|
|
|
|
|
|
and then run $pdf->render_data( $data ) again, or else we can just finish things off here: |
2642
|
|
|
|
|
|
|
|
2643
|
|
|
|
|
|
|
$pdf->save; |
2644
|
|
|
|
|
|
|
|
2645
|
|
|
|
|
|
|
|
2646
|
|
|
|
|
|
|
=head1 CELL DEFINITIONS |
2647
|
|
|
|
|
|
|
|
2648
|
|
|
|
|
|
|
PDF::ReportWriter renders all content the same way - in cells. Each cell is defined by a hash. |
2649
|
|
|
|
|
|
|
A report definition is basically a collection of cells, arranged at various levels in the report. |
2650
|
|
|
|
|
|
|
|
2651
|
|
|
|
|
|
|
Each 'level' to be rendered is defined by an array of cells. |
2652
|
|
|
|
|
|
|
ie an array of cells for the data, an array of cells for the group header, and an array of cells for page footers. |
2653
|
|
|
|
|
|
|
|
2654
|
|
|
|
|
|
|
Cell spacing is relative. You define a percentage for each cell, and the actual length of the cell is |
2655
|
|
|
|
|
|
|
calculated based on the page dimensions ( in the top-level report definition ). |
2656
|
|
|
|
|
|
|
|
2657
|
|
|
|
|
|
|
A cell can have the following attributes |
2658
|
|
|
|
|
|
|
|
2659
|
|
|
|
|
|
|
=head2 name |
2660
|
|
|
|
|
|
|
|
2661
|
|
|
|
|
|
|
=over 4 |
2662
|
|
|
|
|
|
|
|
2663
|
|
|
|
|
|
|
The 'name' is used when rendering data headers, which happens whenever a new group or page is started. |
2664
|
|
|
|
|
|
|
It's not used for anything else - data must be arranged in the same order as the cells to 'line up' in |
2665
|
|
|
|
|
|
|
the right place. |
2666
|
|
|
|
|
|
|
|
2667
|
|
|
|
|
|
|
You can disable rendering of field headers by setting no_field_headers in your data definition ( ie the |
2668
|
|
|
|
|
|
|
hash that you pass to the render() method ). |
2669
|
|
|
|
|
|
|
|
2670
|
|
|
|
|
|
|
=back |
2671
|
|
|
|
|
|
|
|
2672
|
|
|
|
|
|
|
=head2 percent |
2673
|
|
|
|
|
|
|
|
2674
|
|
|
|
|
|
|
=over 4 |
2675
|
|
|
|
|
|
|
|
2676
|
|
|
|
|
|
|
The width of the cell, as a percentage of the total available width. |
2677
|
|
|
|
|
|
|
The actual width will depend on the paper definition ( size and orientation ) |
2678
|
|
|
|
|
|
|
and the x_margin in your report_definition. |
2679
|
|
|
|
|
|
|
|
2680
|
|
|
|
|
|
|
In most cases, a collection of cells should add up to 100%. For multi-line 'rows', |
2681
|
|
|
|
|
|
|
you can continue defining cells beyond 100% width, and these will spill over onto the next line. |
2682
|
|
|
|
|
|
|
See the section on MULTI-LINE ROWS, below. |
2683
|
|
|
|
|
|
|
|
2684
|
|
|
|
|
|
|
=back |
2685
|
|
|
|
|
|
|
|
2686
|
|
|
|
|
|
|
=head2 x |
2687
|
|
|
|
|
|
|
|
2688
|
|
|
|
|
|
|
=over 4 |
2689
|
|
|
|
|
|
|
|
2690
|
|
|
|
|
|
|
The x position of the cell, expressed in points, where 1 mm = 72/25.4 points. |
2691
|
|
|
|
|
|
|
|
2692
|
|
|
|
|
|
|
=back |
2693
|
|
|
|
|
|
|
|
2694
|
|
|
|
|
|
|
=head2 y |
2695
|
|
|
|
|
|
|
|
2696
|
|
|
|
|
|
|
=over 4 |
2697
|
|
|
|
|
|
|
|
2698
|
|
|
|
|
|
|
The y position of the cell, expressed in points, where 1 mm = 72/25.4 points. |
2699
|
|
|
|
|
|
|
|
2700
|
|
|
|
|
|
|
=back |
2701
|
|
|
|
|
|
|
|
2702
|
|
|
|
|
|
|
=head2 font |
2703
|
|
|
|
|
|
|
|
2704
|
|
|
|
|
|
|
=over 4 |
2705
|
|
|
|
|
|
|
|
2706
|
|
|
|
|
|
|
The font to use. In most cases, you would set up a report-wide default_font. |
2707
|
|
|
|
|
|
|
Only use this setting to override the default. |
2708
|
|
|
|
|
|
|
|
2709
|
|
|
|
|
|
|
=back |
2710
|
|
|
|
|
|
|
|
2711
|
|
|
|
|
|
|
=head2 font_size |
2712
|
|
|
|
|
|
|
|
2713
|
|
|
|
|
|
|
=over 4 |
2714
|
|
|
|
|
|
|
|
2715
|
|
|
|
|
|
|
The font size. Nothing special here... |
2716
|
|
|
|
|
|
|
|
2717
|
|
|
|
|
|
|
=back |
2718
|
|
|
|
|
|
|
|
2719
|
|
|
|
|
|
|
=head2 bold |
2720
|
|
|
|
|
|
|
|
2721
|
|
|
|
|
|
|
=over 4 |
2722
|
|
|
|
|
|
|
|
2723
|
|
|
|
|
|
|
A boolean flag to indicate whether you want the text rendered in bold or not. |
2724
|
|
|
|
|
|
|
|
2725
|
|
|
|
|
|
|
=back |
2726
|
|
|
|
|
|
|
|
2727
|
|
|
|
|
|
|
=head2 colour |
2728
|
|
|
|
|
|
|
|
2729
|
|
|
|
|
|
|
=over 4 |
2730
|
|
|
|
|
|
|
|
2731
|
|
|
|
|
|
|
No surprises here either. |
2732
|
|
|
|
|
|
|
|
2733
|
|
|
|
|
|
|
=back |
2734
|
|
|
|
|
|
|
|
2735
|
|
|
|
|
|
|
=head2 header_colour |
2736
|
|
|
|
|
|
|
|
2737
|
|
|
|
|
|
|
=over 4 |
2738
|
|
|
|
|
|
|
|
2739
|
|
|
|
|
|
|
The colour to use for rendering data headers ( ie field names ). |
2740
|
|
|
|
|
|
|
|
2741
|
|
|
|
|
|
|
=back |
2742
|
|
|
|
|
|
|
|
2743
|
|
|
|
|
|
|
=head2 header_align |
2744
|
|
|
|
|
|
|
|
2745
|
|
|
|
|
|
|
=over 4 |
2746
|
|
|
|
|
|
|
|
2747
|
|
|
|
|
|
|
The alignment of the data headers ( ie field names ). |
2748
|
|
|
|
|
|
|
Possible values are "left", "right" and "centre" ( or now "center", also ). |
2749
|
|
|
|
|
|
|
|
2750
|
|
|
|
|
|
|
=back |
2751
|
|
|
|
|
|
|
|
2752
|
|
|
|
|
|
|
=head2 text |
2753
|
|
|
|
|
|
|
|
2754
|
|
|
|
|
|
|
=over 4 |
2755
|
|
|
|
|
|
|
|
2756
|
|
|
|
|
|
|
The text to display in the cell ( ie if the cell is not rendering data, but static text ). |
2757
|
|
|
|
|
|
|
|
2758
|
|
|
|
|
|
|
=back |
2759
|
|
|
|
|
|
|
|
2760
|
|
|
|
|
|
|
=head2 wrap_text |
2761
|
|
|
|
|
|
|
|
2762
|
|
|
|
|
|
|
=over 4 |
2763
|
|
|
|
|
|
|
|
2764
|
|
|
|
|
|
|
Turns on wrapping of text that exceeds the width of the cell. |
2765
|
|
|
|
|
|
|
|
2766
|
|
|
|
|
|
|
=back |
2767
|
|
|
|
|
|
|
|
2768
|
|
|
|
|
|
|
=head2 strip_breaks |
2769
|
|
|
|
|
|
|
|
2770
|
|
|
|
|
|
|
=over 4 |
2771
|
|
|
|
|
|
|
|
2772
|
|
|
|
|
|
|
Strips line breaks out of text. |
2773
|
|
|
|
|
|
|
|
2774
|
|
|
|
|
|
|
=back |
2775
|
|
|
|
|
|
|
|
2776
|
|
|
|
|
|
|
=head2 image |
2777
|
|
|
|
|
|
|
|
2778
|
|
|
|
|
|
|
=over 4 |
2779
|
|
|
|
|
|
|
|
2780
|
|
|
|
|
|
|
A hash with details of the image to render. See below for details. |
2781
|
|
|
|
|
|
|
If you try to use an image type that is not supported by your installed |
2782
|
|
|
|
|
|
|
version of PDF::API2, your image is skipped, and a warning is printed out. |
2783
|
|
|
|
|
|
|
|
2784
|
|
|
|
|
|
|
=back |
2785
|
|
|
|
|
|
|
|
2786
|
|
|
|
|
|
|
=head2 colour_func |
2787
|
|
|
|
|
|
|
|
2788
|
|
|
|
|
|
|
=over 4 |
2789
|
|
|
|
|
|
|
|
2790
|
|
|
|
|
|
|
A user-defined sub that returns a colour. Your colour_func will be passed: |
2791
|
|
|
|
|
|
|
|
2792
|
|
|
|
|
|
|
=head3 value |
2793
|
|
|
|
|
|
|
|
2794
|
|
|
|
|
|
|
=over 4 |
2795
|
|
|
|
|
|
|
|
2796
|
|
|
|
|
|
|
The current cell value |
2797
|
|
|
|
|
|
|
|
2798
|
|
|
|
|
|
|
=back |
2799
|
|
|
|
|
|
|
|
2800
|
|
|
|
|
|
|
=head3 row |
2801
|
|
|
|
|
|
|
|
2802
|
|
|
|
|
|
|
=over 4 |
2803
|
|
|
|
|
|
|
|
2804
|
|
|
|
|
|
|
an array reference containing the current row |
2805
|
|
|
|
|
|
|
|
2806
|
|
|
|
|
|
|
=back |
2807
|
|
|
|
|
|
|
|
2808
|
|
|
|
|
|
|
=head3 options |
2809
|
|
|
|
|
|
|
|
2810
|
|
|
|
|
|
|
=over 4 |
2811
|
|
|
|
|
|
|
|
2812
|
|
|
|
|
|
|
a hash containing the current rendering options: |
2813
|
|
|
|
|
|
|
|
2814
|
|
|
|
|
|
|
{ |
2815
|
|
|
|
|
|
|
current_row - the current row of data |
2816
|
|
|
|
|
|
|
row_type - the current row type (data, group_header, ...) |
2817
|
|
|
|
|
|
|
current_value - the current value of this cell |
2818
|
|
|
|
|
|
|
cell - the cell definition ( get x position and width from this ) |
2819
|
|
|
|
|
|
|
cell_counter - position of the current cell in the row ( 0 .. n - 1 ) |
2820
|
|
|
|
|
|
|
cell_y_border - the bottom of the cell |
2821
|
|
|
|
|
|
|
cell_full_height - the height of the cell |
2822
|
|
|
|
|
|
|
page - the current page ( a PDF::API2 page ) |
2823
|
|
|
|
|
|
|
page_no - the current page number |
2824
|
|
|
|
|
|
|
} |
2825
|
|
|
|
|
|
|
|
2826
|
|
|
|
|
|
|
=back |
2827
|
|
|
|
|
|
|
|
2828
|
|
|
|
|
|
|
Note that prior to version 1.4, we only passed the value. |
2829
|
|
|
|
|
|
|
|
2830
|
|
|
|
|
|
|
=back |
2831
|
|
|
|
|
|
|
|
2832
|
|
|
|
|
|
|
=head2 background_func |
2833
|
|
|
|
|
|
|
|
2834
|
|
|
|
|
|
|
=over 4 |
2835
|
|
|
|
|
|
|
|
2836
|
|
|
|
|
|
|
A user-defined sub that returns a colour for the cell background. Your background_func will be passed: |
2837
|
|
|
|
|
|
|
|
2838
|
|
|
|
|
|
|
=head3 value |
2839
|
|
|
|
|
|
|
|
2840
|
|
|
|
|
|
|
=over 4 |
2841
|
|
|
|
|
|
|
|
2842
|
|
|
|
|
|
|
The current cell value |
2843
|
|
|
|
|
|
|
|
2844
|
|
|
|
|
|
|
=back |
2845
|
|
|
|
|
|
|
|
2846
|
|
|
|
|
|
|
=head3 row |
2847
|
|
|
|
|
|
|
|
2848
|
|
|
|
|
|
|
=over 4 |
2849
|
|
|
|
|
|
|
|
2850
|
|
|
|
|
|
|
an array reference containing the current row |
2851
|
|
|
|
|
|
|
|
2852
|
|
|
|
|
|
|
=back |
2853
|
|
|
|
|
|
|
|
2854
|
|
|
|
|
|
|
=head3 options |
2855
|
|
|
|
|
|
|
|
2856
|
|
|
|
|
|
|
=over 4 |
2857
|
|
|
|
|
|
|
|
2858
|
|
|
|
|
|
|
a hash containing the current rendering options: |
2859
|
|
|
|
|
|
|
|
2860
|
|
|
|
|
|
|
{ |
2861
|
|
|
|
|
|
|
current_row - the current row of data |
2862
|
|
|
|
|
|
|
row_type - the current row type (data, group_header, ...) |
2863
|
|
|
|
|
|
|
current_value - the current value of this cell |
2864
|
|
|
|
|
|
|
cell - the cell definition ( get x position and width from this ) |
2865
|
|
|
|
|
|
|
cell_counter - position of the current cell in the row ( 0 .. n - 1 ) |
2866
|
|
|
|
|
|
|
cell_y_border - the bottom of the cell |
2867
|
|
|
|
|
|
|
cell_full_height - the height of the cell |
2868
|
|
|
|
|
|
|
page - the current page ( a PDF::API2 page ) |
2869
|
|
|
|
|
|
|
page_no - the current page number |
2870
|
|
|
|
|
|
|
} |
2871
|
|
|
|
|
|
|
|
2872
|
|
|
|
|
|
|
=back |
2873
|
|
|
|
|
|
|
|
2874
|
|
|
|
|
|
|
=head2 custom_render_func |
2875
|
|
|
|
|
|
|
|
2876
|
|
|
|
|
|
|
=over 4 |
2877
|
|
|
|
|
|
|
|
2878
|
|
|
|
|
|
|
A user-define sub to replace the built-in text / image rendering functions |
2879
|
|
|
|
|
|
|
The sub will receive a hash of options: |
2880
|
|
|
|
|
|
|
|
2881
|
|
|
|
|
|
|
{ |
2882
|
|
|
|
|
|
|
current_row - the current row of data |
2883
|
|
|
|
|
|
|
row_type - the current row type (data, group_header, ...) |
2884
|
|
|
|
|
|
|
current_value - the current value of this cell |
2885
|
|
|
|
|
|
|
cell - the cell definition ( get x position and width from this ) |
2886
|
|
|
|
|
|
|
cell_counter - position of the current cell in the row ( 0 .. n - 1 ) |
2887
|
|
|
|
|
|
|
cell_y_border - the bottom of the cell |
2888
|
|
|
|
|
|
|
cell_full_height - the height of the cell |
2889
|
|
|
|
|
|
|
page - the current page ( a PDF::API2 page ) |
2890
|
|
|
|
|
|
|
} |
2891
|
|
|
|
|
|
|
|
2892
|
|
|
|
|
|
|
=back |
2893
|
|
|
|
|
|
|
|
2894
|
|
|
|
|
|
|
=head2 align |
2895
|
|
|
|
|
|
|
|
2896
|
|
|
|
|
|
|
=over 4 |
2897
|
|
|
|
|
|
|
|
2898
|
|
|
|
|
|
|
Possible values are "left", "right", "centre" ( or now "center", also ), and "justified" |
2899
|
|
|
|
|
|
|
|
2900
|
|
|
|
|
|
|
=back |
2901
|
|
|
|
|
|
|
|
2902
|
|
|
|
|
|
|
=head2 aggregate_function |
2903
|
|
|
|
|
|
|
|
2904
|
|
|
|
|
|
|
=over 4 |
2905
|
|
|
|
|
|
|
|
2906
|
|
|
|
|
|
|
Possible values are "sum" and "count". Setting this attribute will make PDF::ReportWriter carry |
2907
|
|
|
|
|
|
|
out the selected function and store the results ( attached to the cell ) for later use in group footers. |
2908
|
|
|
|
|
|
|
|
2909
|
|
|
|
|
|
|
=back |
2910
|
|
|
|
|
|
|
|
2911
|
|
|
|
|
|
|
=head2 type ( LEGACY ) |
2912
|
|
|
|
|
|
|
|
2913
|
|
|
|
|
|
|
=over 4 |
2914
|
|
|
|
|
|
|
|
2915
|
|
|
|
|
|
|
Please see the 'format' key, below, for improved numeric / currency formatting. |
2916
|
|
|
|
|
|
|
|
2917
|
|
|
|
|
|
|
This key turns on formatting of data. |
2918
|
|
|
|
|
|
|
The possible values currently are 'currency', 'currency:no_fill' and 'thousands_separated'. |
2919
|
|
|
|
|
|
|
|
2920
|
|
|
|
|
|
|
There is also another special value that allows custom formatting of text cells: C. |
2921
|
|
|
|
|
|
|
If you define the cell type as, for example, C, the cell text that |
2922
|
|
|
|
|
|
|
will be output is the return value of the following (pseudo) code: |
2923
|
|
|
|
|
|
|
|
2924
|
|
|
|
|
|
|
my $formatter_object = my::formatter::class->new(); |
2925
|
|
|
|
|
|
|
$formatter_object->format({ |
2926
|
|
|
|
|
|
|
cell => { ... }, # Cell object "properties" |
2927
|
|
|
|
|
|
|
options => { ... }, # Cell options |
2928
|
|
|
|
|
|
|
string => 'Original cell text', # Cell actual content to be formatted |
2929
|
|
|
|
|
|
|
}); |
2930
|
|
|
|
|
|
|
|
2931
|
|
|
|
|
|
|
An example of formatter class is the following: |
2932
|
|
|
|
|
|
|
|
2933
|
|
|
|
|
|
|
package formatter::greeter; |
2934
|
|
|
|
|
|
|
use strict; |
2935
|
|
|
|
|
|
|
|
2936
|
|
|
|
|
|
|
sub new { |
2937
|
|
|
|
|
|
|
bless \my $self |
2938
|
|
|
|
|
|
|
} |
2939
|
|
|
|
|
|
|
sub format { |
2940
|
|
|
|
|
|
|
my $self = $_[0]; |
2941
|
|
|
|
|
|
|
my $args = $_[1]; |
2942
|
|
|
|
|
|
|
|
2943
|
|
|
|
|
|
|
return 'Hello, ' . $args->{string}; |
2944
|
|
|
|
|
|
|
} |
2945
|
|
|
|
|
|
|
|
2946
|
|
|
|
|
|
|
This class will greet anything it is specified in its cell. |
2947
|
|
|
|
|
|
|
Useful, eh?! :-) |
2948
|
|
|
|
|
|
|
|
2949
|
|
|
|
|
|
|
=back |
2950
|
|
|
|
|
|
|
|
2951
|
|
|
|
|
|
|
=head2 format |
2952
|
|
|
|
|
|
|
|
2953
|
|
|
|
|
|
|
=over 4 |
2954
|
|
|
|
|
|
|
|
2955
|
|
|
|
|
|
|
This key is a hash that controls numeric and currency formatting. Possible keys are: |
2956
|
|
|
|
|
|
|
|
2957
|
|
|
|
|
|
|
{ |
2958
|
|
|
|
|
|
|
currency - a BOOLEAN that causes all value to have a dollar sign prepeneded to them |
2959
|
|
|
|
|
|
|
decimal_places - an INT that indicates how many decimal places to round values to |
2960
|
|
|
|
|
|
|
decimal_fill - a BOOLEAN that causes all decimal values to be filled to decimal_places places |
2961
|
|
|
|
|
|
|
separate_thousands - a BOOLEAN that turns on thousands separating ( ie with commas ) |
2962
|
|
|
|
|
|
|
null_if_zero - a BOOLEAN that causes zero amounts to render nothing ( NULL ) |
2963
|
|
|
|
|
|
|
} |
2964
|
|
|
|
|
|
|
|
2965
|
|
|
|
|
|
|
=back |
2966
|
|
|
|
|
|
|
|
2967
|
|
|
|
|
|
|
=head2 background |
2968
|
|
|
|
|
|
|
|
2969
|
|
|
|
|
|
|
=over 4 |
2970
|
|
|
|
|
|
|
|
2971
|
|
|
|
|
|
|
A hash containing details on how to render the background of the cell. See below. |
2972
|
|
|
|
|
|
|
|
2973
|
|
|
|
|
|
|
=back |
2974
|
|
|
|
|
|
|
|
2975
|
|
|
|
|
|
|
=head1 IMAGES |
2976
|
|
|
|
|
|
|
|
2977
|
|
|
|
|
|
|
You can define images in any cell ( data, or group header / footer ). |
2978
|
|
|
|
|
|
|
The default behaviour is to render the image at its original size. |
2979
|
|
|
|
|
|
|
If the image won't fit horizontally, it is scaled down until it will. |
2980
|
|
|
|
|
|
|
Images can be aligned in the same way as other fields, with the 'align' key. |
2981
|
|
|
|
|
|
|
|
2982
|
|
|
|
|
|
|
The images hash has the following keys: |
2983
|
|
|
|
|
|
|
|
2984
|
|
|
|
|
|
|
=head2 path |
2985
|
|
|
|
|
|
|
|
2986
|
|
|
|
|
|
|
=over 4 |
2987
|
|
|
|
|
|
|
|
2988
|
|
|
|
|
|
|
The full path to the image to render ( currently only supports png and jpg ). |
2989
|
|
|
|
|
|
|
You should either set the path, or set the 'dynamic' flag, below. |
2990
|
|
|
|
|
|
|
|
2991
|
|
|
|
|
|
|
=back |
2992
|
|
|
|
|
|
|
|
2993
|
|
|
|
|
|
|
=head2 dynamic |
2994
|
|
|
|
|
|
|
|
2995
|
|
|
|
|
|
|
=over 4 |
2996
|
|
|
|
|
|
|
|
2997
|
|
|
|
|
|
|
A boolean flag to indicate that the full path to the image to use will be in the data array. |
2998
|
|
|
|
|
|
|
You should either set a hard-coded image path ( above ), or set this flag on. |
2999
|
|
|
|
|
|
|
|
3000
|
|
|
|
|
|
|
=back |
3001
|
|
|
|
|
|
|
|
3002
|
|
|
|
|
|
|
=head2 scale_to_fit |
3003
|
|
|
|
|
|
|
|
3004
|
|
|
|
|
|
|
=over 4 |
3005
|
|
|
|
|
|
|
|
3006
|
|
|
|
|
|
|
A boolean value, indicating whether the image should be scaled to fit the current cell or not. |
3007
|
|
|
|
|
|
|
Whether this is set or not, scaling will still occur if the image is too wide for the cell. |
3008
|
|
|
|
|
|
|
|
3009
|
|
|
|
|
|
|
=back |
3010
|
|
|
|
|
|
|
|
3011
|
|
|
|
|
|
|
=head2 height |
3012
|
|
|
|
|
|
|
|
3013
|
|
|
|
|
|
|
=over 4 |
3014
|
|
|
|
|
|
|
|
3015
|
|
|
|
|
|
|
You can hard-code a height value if you like. The image will be scaled to the given height value, |
3016
|
|
|
|
|
|
|
to the extent that it still fits length-wise in the cell. |
3017
|
|
|
|
|
|
|
|
3018
|
|
|
|
|
|
|
=back |
3019
|
|
|
|
|
|
|
|
3020
|
|
|
|
|
|
|
=head2 buffer |
3021
|
|
|
|
|
|
|
|
3022
|
|
|
|
|
|
|
=over 4 |
3023
|
|
|
|
|
|
|
|
3024
|
|
|
|
|
|
|
A *minimum* white-space buffer ( in points ) to wrap the image in. This defaults to 1, which |
3025
|
|
|
|
|
|
|
ensures that the image doesn't render over part of the cell borders ( which looks bad ). |
3026
|
|
|
|
|
|
|
|
3027
|
|
|
|
|
|
|
=back |
3028
|
|
|
|
|
|
|
|
3029
|
|
|
|
|
|
|
=head1 BACKGROUNDS |
3030
|
|
|
|
|
|
|
|
3031
|
|
|
|
|
|
|
You can define a background for any cell, including normal fields, group header & footers, etc. |
3032
|
|
|
|
|
|
|
For data headers ONLY, you must ( currently ) set them up per data set, instead of per field. In this case, |
3033
|
|
|
|
|
|
|
you add the background key to the 'headings' hash in the main data hash. |
3034
|
|
|
|
|
|
|
|
3035
|
|
|
|
|
|
|
The background hash has the following keys: |
3036
|
|
|
|
|
|
|
|
3037
|
|
|
|
|
|
|
=head2 shape |
3038
|
|
|
|
|
|
|
|
3039
|
|
|
|
|
|
|
=over 4 |
3040
|
|
|
|
|
|
|
|
3041
|
|
|
|
|
|
|
Current options are 'box' or 'ellipse'. 'ellipse' is good for group headers. |
3042
|
|
|
|
|
|
|
'box' is good for data headers or 'normal' cell backgrounds. If you use an 'ellipse', |
3043
|
|
|
|
|
|
|
it tends to look better if the text is centred. More shapes are needed. |
3044
|
|
|
|
|
|
|
A 'round_box', with nice rounded edges, would be great. Send patches. |
3045
|
|
|
|
|
|
|
|
3046
|
|
|
|
|
|
|
=back |
3047
|
|
|
|
|
|
|
|
3048
|
|
|
|
|
|
|
=head2 colour |
3049
|
|
|
|
|
|
|
|
3050
|
|
|
|
|
|
|
=over 4 |
3051
|
|
|
|
|
|
|
|
3052
|
|
|
|
|
|
|
The colour to use to fill the background's shape. Keep in mind with data headers ( the automatic |
3053
|
|
|
|
|
|
|
headers that appear at the top of each data set ), that you set the *foreground* colour via the |
3054
|
|
|
|
|
|
|
field's 'header_colour' key, as there are ( currently ) no explicit definitions for data headers. |
3055
|
|
|
|
|
|
|
|
3056
|
|
|
|
|
|
|
=back |
3057
|
|
|
|
|
|
|
|
3058
|
|
|
|
|
|
|
=head2 border |
3059
|
|
|
|
|
|
|
|
3060
|
|
|
|
|
|
|
=over 4 |
3061
|
|
|
|
|
|
|
|
3062
|
|
|
|
|
|
|
The colour ( if any ) to use to render the cell's border. If this is set, the border will be a rectangle, |
3063
|
|
|
|
|
|
|
around the very outside of the cell. You can have a shaped background and a border rendererd in the |
3064
|
|
|
|
|
|
|
same cell. |
3065
|
|
|
|
|
|
|
|
3066
|
|
|
|
|
|
|
=over 4 |
3067
|
|
|
|
|
|
|
|
3068
|
|
|
|
|
|
|
=head2 borders |
3069
|
|
|
|
|
|
|
|
3070
|
|
|
|
|
|
|
If you have set the border key ( above ), you can also define which borders to render by setting |
3071
|
|
|
|
|
|
|
the borders key with the 1st letter(s) of the border to render, from the possible list of: |
3072
|
|
|
|
|
|
|
|
3073
|
|
|
|
|
|
|
l ( left border ) |
3074
|
|
|
|
|
|
|
r ( right border ) |
3075
|
|
|
|
|
|
|
t ( top border ) |
3076
|
|
|
|
|
|
|
b ( bottom border ) |
3077
|
|
|
|
|
|
|
all ( all borders ) - this is also the default if no 'borders' key is encountered |
3078
|
|
|
|
|
|
|
|
3079
|
|
|
|
|
|
|
eg you would set borders = "tlr" to have all borders except the bottom ( b ) border |
3080
|
|
|
|
|
|
|
|
3081
|
|
|
|
|
|
|
Upper-case letters will also work. |
3082
|
|
|
|
|
|
|
|
3083
|
|
|
|
|
|
|
=back |
3084
|
|
|
|
|
|
|
|
3085
|
|
|
|
|
|
|
=back |
3086
|
|
|
|
|
|
|
|
3087
|
|
|
|
|
|
|
=head1 BARCODES |
3088
|
|
|
|
|
|
|
|
3089
|
|
|
|
|
|
|
You can define barcodes in any cell ( data, or group header / footer ). |
3090
|
|
|
|
|
|
|
The default barcode type is B. The available types are B and |
3091
|
|
|
|
|
|
|
B. |
3092
|
|
|
|
|
|
|
|
3093
|
|
|
|
|
|
|
The barcode hash has the following keys: |
3094
|
|
|
|
|
|
|
|
3095
|
|
|
|
|
|
|
=over 4 |
3096
|
|
|
|
|
|
|
|
3097
|
|
|
|
|
|
|
=item type |
3098
|
|
|
|
|
|
|
|
3099
|
|
|
|
|
|
|
Type of the barcode, either B or B. Support for other barcode types |
3100
|
|
|
|
|
|
|
should be fairly simple, but currently is not there. No default. |
3101
|
|
|
|
|
|
|
|
3102
|
|
|
|
|
|
|
=item x, y |
3103
|
|
|
|
|
|
|
|
3104
|
|
|
|
|
|
|
As in text cells. |
3105
|
|
|
|
|
|
|
|
3106
|
|
|
|
|
|
|
=item scale |
3107
|
|
|
|
|
|
|
|
3108
|
|
|
|
|
|
|
Defines a zoom scale for barcode, where 1.0 means scale 1:1. |
3109
|
|
|
|
|
|
|
|
3110
|
|
|
|
|
|
|
=item align |
3111
|
|
|
|
|
|
|
|
3112
|
|
|
|
|
|
|
Defines the alignment of the barcode object. Should be C (or C), |
3113
|
|
|
|
|
|
|
C (or C), or C (or C). This should work as expected either |
3114
|
|
|
|
|
|
|
if you specify absolute x,y coordinates or not. |
3115
|
|
|
|
|
|
|
|
3116
|
|
|
|
|
|
|
=item font_size |
3117
|
|
|
|
|
|
|
|
3118
|
|
|
|
|
|
|
Defines the font size of the clear text that appears below the bars. |
3119
|
|
|
|
|
|
|
If not present, takes report C property. |
3120
|
|
|
|
|
|
|
|
3121
|
|
|
|
|
|
|
=item font |
3122
|
|
|
|
|
|
|
|
3123
|
|
|
|
|
|
|
Defines the font face of the clear text that appears below the bars. |
3124
|
|
|
|
|
|
|
If not present, takes report C property. |
3125
|
|
|
|
|
|
|
|
3126
|
|
|
|
|
|
|
=item zone |
3127
|
|
|
|
|
|
|
|
3128
|
|
|
|
|
|
|
Regulates the height of the barcode lines. |
3129
|
|
|
|
|
|
|
|
3130
|
|
|
|
|
|
|
=item upper_mending_zone, lower_mending_zone |
3131
|
|
|
|
|
|
|
|
3132
|
|
|
|
|
|
|
Space below and above barcode bars? I tried experimenting a bit, but |
3133
|
|
|
|
|
|
|
didn't properly understand what C does. |
3134
|
|
|
|
|
|
|
C is the height of the barcode extensions toward the |
3135
|
|
|
|
|
|
|
lower end, where clear text is printed. |
3136
|
|
|
|
|
|
|
I don't know how to explain these better... |
3137
|
|
|
|
|
|
|
|
3138
|
|
|
|
|
|
|
=item quiet_zone |
3139
|
|
|
|
|
|
|
|
3140
|
|
|
|
|
|
|
Empty space around the barcode bars? Try to experiment yourself. |
3141
|
|
|
|
|
|
|
|
3142
|
|
|
|
|
|
|
=back |
3143
|
|
|
|
|
|
|
|
3144
|
|
|
|
|
|
|
=head1 GROUP DEFINITIONS |
3145
|
|
|
|
|
|
|
|
3146
|
|
|
|
|
|
|
Grouping is achieved by defining a column in the data array to use as a group value. When a new group |
3147
|
|
|
|
|
|
|
value is encountered, a group footer ( if defined ) is rendered, and a new group header ( if defined ) |
3148
|
|
|
|
|
|
|
is rendered. At present, the simple group aggregate functions 'count' and 'sum' are supported - see the |
3149
|
|
|
|
|
|
|
cell definition section for details on how to chose a column to perform aggregate functions on, and below |
3150
|
|
|
|
|
|
|
for how to retrieve the aggregate value in a footer. You can perform one aggregate function on each column |
3151
|
|
|
|
|
|
|
in your data array. |
3152
|
|
|
|
|
|
|
|
3153
|
|
|
|
|
|
|
As of version 0.9, support has been added for splitting data from a single field ( ie the group value |
3154
|
|
|
|
|
|
|
from the data_column above ) into multiple cells. To do this, simply pack your data into the column |
3155
|
|
|
|
|
|
|
identified by data_column, and separate the fields with a delimiter. Then in your group definition, |
3156
|
|
|
|
|
|
|
set up the cells with the special keys 'delimiter' and 'index' ( see below ) to identify how to |
3157
|
|
|
|
|
|
|
delimit the data, and which column to use for the cell once the data is split. Many thanks to |
3158
|
|
|
|
|
|
|
Bill Hess for this patch :) |
3159
|
|
|
|
|
|
|
|
3160
|
|
|
|
|
|
|
Groups have the following attributes: |
3161
|
|
|
|
|
|
|
|
3162
|
|
|
|
|
|
|
=head2 name |
3163
|
|
|
|
|
|
|
|
3164
|
|
|
|
|
|
|
=over 4 |
3165
|
|
|
|
|
|
|
|
3166
|
|
|
|
|
|
|
The name is used to identify which value to use in rendering aggregate functions ( see aggregate_source, below ). |
3167
|
|
|
|
|
|
|
Also, a special name, "GrandTotals" will cause PDF::ReportWriter to fetch *Grand* totals instead of group totals. |
3168
|
|
|
|
|
|
|
|
3169
|
|
|
|
|
|
|
=back |
3170
|
|
|
|
|
|
|
|
3171
|
|
|
|
|
|
|
=head2 page_break |
3172
|
|
|
|
|
|
|
|
3173
|
|
|
|
|
|
|
=over 4 |
3174
|
|
|
|
|
|
|
|
3175
|
|
|
|
|
|
|
Set this to TRUE if you want to cause a page break when entering a new group value. |
3176
|
|
|
|
|
|
|
|
3177
|
|
|
|
|
|
|
=back |
3178
|
|
|
|
|
|
|
|
3179
|
|
|
|
|
|
|
=head2 data_column |
3180
|
|
|
|
|
|
|
|
3181
|
|
|
|
|
|
|
=over 4 |
3182
|
|
|
|
|
|
|
|
3183
|
|
|
|
|
|
|
The data_column refers to the column ( starting at 0 ) of the data_array that you want to group on. |
3184
|
|
|
|
|
|
|
|
3185
|
|
|
|
|
|
|
=back |
3186
|
|
|
|
|
|
|
|
3187
|
|
|
|
|
|
|
=head2 reprinting_header |
3188
|
|
|
|
|
|
|
|
3189
|
|
|
|
|
|
|
=over 4 |
3190
|
|
|
|
|
|
|
|
3191
|
|
|
|
|
|
|
If this is set, the group header will be reprinted on each new page |
3192
|
|
|
|
|
|
|
|
3193
|
|
|
|
|
|
|
=back |
3194
|
|
|
|
|
|
|
|
3195
|
|
|
|
|
|
|
=head2 header_upper_buffer / header_lower_buffer / footer_upper_buffer / footer_lower_buffer |
3196
|
|
|
|
|
|
|
|
3197
|
|
|
|
|
|
|
=over 4 |
3198
|
|
|
|
|
|
|
|
3199
|
|
|
|
|
|
|
These 4 keys set the respective buffers ( ie whitespace ) that separates the group |
3200
|
|
|
|
|
|
|
headers / footers from things above ( upper ) and below ( lower ) them. If you don't specify any |
3201
|
|
|
|
|
|
|
buffers, default values will be set to emulate legacy behaviour. |
3202
|
|
|
|
|
|
|
|
3203
|
|
|
|
|
|
|
=back |
3204
|
|
|
|
|
|
|
|
3205
|
|
|
|
|
|
|
=head2 header / footer |
3206
|
|
|
|
|
|
|
|
3207
|
|
|
|
|
|
|
=over 4 |
3208
|
|
|
|
|
|
|
|
3209
|
|
|
|
|
|
|
Group headers and footers are defined in a similar way to field definitions ( and rendered by the same code ). |
3210
|
|
|
|
|
|
|
The difference is that the cell definition is contained in the 'header' and 'footer' hashes, ie the header and |
3211
|
|
|
|
|
|
|
footer hashes resemble a field hash. Consequently, most attributes that work for field cells also work for |
3212
|
|
|
|
|
|
|
group cells. Additional attributes in the header and footer hashes are: |
3213
|
|
|
|
|
|
|
|
3214
|
|
|
|
|
|
|
=back |
3215
|
|
|
|
|
|
|
|
3216
|
|
|
|
|
|
|
=head2 aggregate_source ( footers only ) |
3217
|
|
|
|
|
|
|
|
3218
|
|
|
|
|
|
|
=over 4 |
3219
|
|
|
|
|
|
|
|
3220
|
|
|
|
|
|
|
This is used to indicate which column to retrieve the results of an aggregate_function from |
3221
|
|
|
|
|
|
|
( see cell definition section ). |
3222
|
|
|
|
|
|
|
|
3223
|
|
|
|
|
|
|
=back |
3224
|
|
|
|
|
|
|
|
3225
|
|
|
|
|
|
|
=head2 delimiter ( headers only ) |
3226
|
|
|
|
|
|
|
|
3227
|
|
|
|
|
|
|
=over 4 |
3228
|
|
|
|
|
|
|
|
3229
|
|
|
|
|
|
|
This optional key is used in conjunction with the 'index' key ( below ) and defines the |
3230
|
|
|
|
|
|
|
delimiter character used to separate 'fields' in a single column of data. |
3231
|
|
|
|
|
|
|
|
3232
|
|
|
|
|
|
|
=back |
3233
|
|
|
|
|
|
|
|
3234
|
|
|
|
|
|
|
=head2 index ( headers only ) |
3235
|
|
|
|
|
|
|
|
3236
|
|
|
|
|
|
|
=over 4 |
3237
|
|
|
|
|
|
|
|
3238
|
|
|
|
|
|
|
This option key is used inconjunction with the 'delimiter' key ( above ), and defines the |
3239
|
|
|
|
|
|
|
'column' inside the delimited data column to use for the current cell. |
3240
|
|
|
|
|
|
|
|
3241
|
|
|
|
|
|
|
=back |
3242
|
|
|
|
|
|
|
|
3243
|
|
|
|
|
|
|
=head1 REPORT DEFINITION |
3244
|
|
|
|
|
|
|
|
3245
|
|
|
|
|
|
|
Possible attributes for the report defintion are: |
3246
|
|
|
|
|
|
|
|
3247
|
|
|
|
|
|
|
=head2 destination |
3248
|
|
|
|
|
|
|
|
3249
|
|
|
|
|
|
|
=over 4 |
3250
|
|
|
|
|
|
|
|
3251
|
|
|
|
|
|
|
The path to the destination ( the pdf that you want to create ). |
3252
|
|
|
|
|
|
|
|
3253
|
|
|
|
|
|
|
=back |
3254
|
|
|
|
|
|
|
|
3255
|
|
|
|
|
|
|
=head2 paper |
3256
|
|
|
|
|
|
|
|
3257
|
|
|
|
|
|
|
=over 4 |
3258
|
|
|
|
|
|
|
|
3259
|
|
|
|
|
|
|
Supported types are: |
3260
|
|
|
|
|
|
|
|
3261
|
|
|
|
|
|
|
=over 4 |
3262
|
|
|
|
|
|
|
|
3263
|
|
|
|
|
|
|
- A4 |
3264
|
|
|
|
|
|
|
- Letter |
3265
|
|
|
|
|
|
|
- bsize |
3266
|
|
|
|
|
|
|
- legal |
3267
|
|
|
|
|
|
|
|
3268
|
|
|
|
|
|
|
=back |
3269
|
|
|
|
|
|
|
|
3270
|
|
|
|
|
|
|
=back |
3271
|
|
|
|
|
|
|
|
3272
|
|
|
|
|
|
|
=head2 orientation |
3273
|
|
|
|
|
|
|
|
3274
|
|
|
|
|
|
|
=over 4 |
3275
|
|
|
|
|
|
|
|
3276
|
|
|
|
|
|
|
portrait or landscape |
3277
|
|
|
|
|
|
|
|
3278
|
|
|
|
|
|
|
=back |
3279
|
|
|
|
|
|
|
|
3280
|
|
|
|
|
|
|
=head2 template |
3281
|
|
|
|
|
|
|
|
3282
|
|
|
|
|
|
|
=over 4 |
3283
|
|
|
|
|
|
|
|
3284
|
|
|
|
|
|
|
Path to a single page PDF file to be used as template for new pages of the report. |
3285
|
|
|
|
|
|
|
If PDF is multipage, only first page will be extracted and used. |
3286
|
|
|
|
|
|
|
All content in PDF template will be included in every page of the final report. |
3287
|
|
|
|
|
|
|
Be sure to avoid overlapping PDF template content and report content. |
3288
|
|
|
|
|
|
|
|
3289
|
|
|
|
|
|
|
=back |
3290
|
|
|
|
|
|
|
|
3291
|
|
|
|
|
|
|
=head2 font_list |
3292
|
|
|
|
|
|
|
|
3293
|
|
|
|
|
|
|
=over 4 |
3294
|
|
|
|
|
|
|
|
3295
|
|
|
|
|
|
|
An array of font names ( from the corefonts supported by PDF::API2 ) to set up. |
3296
|
|
|
|
|
|
|
When you include a font 'family', a range of fonts ( roman, italic, bold, etc ) are created. |
3297
|
|
|
|
|
|
|
|
3298
|
|
|
|
|
|
|
=back |
3299
|
|
|
|
|
|
|
|
3300
|
|
|
|
|
|
|
=head2 default_font |
3301
|
|
|
|
|
|
|
|
3302
|
|
|
|
|
|
|
=over 4 |
3303
|
|
|
|
|
|
|
|
3304
|
|
|
|
|
|
|
The name of the font type ( from the above list ) to use as a default ( ie if one isn't set up for a cell ). |
3305
|
|
|
|
|
|
|
|
3306
|
|
|
|
|
|
|
=back |
3307
|
|
|
|
|
|
|
|
3308
|
|
|
|
|
|
|
=head2 default_font_size |
3309
|
|
|
|
|
|
|
|
3310
|
|
|
|
|
|
|
=over 4 |
3311
|
|
|
|
|
|
|
|
3312
|
|
|
|
|
|
|
The default font size to use if one isn't set up for a cell. |
3313
|
|
|
|
|
|
|
This is no longer required and defaults to 12 if one is not given. |
3314
|
|
|
|
|
|
|
|
3315
|
|
|
|
|
|
|
=back |
3316
|
|
|
|
|
|
|
|
3317
|
|
|
|
|
|
|
=head2 x_margin |
3318
|
|
|
|
|
|
|
|
3319
|
|
|
|
|
|
|
=over 4 |
3320
|
|
|
|
|
|
|
|
3321
|
|
|
|
|
|
|
The amount of space ( left and right ) to leave as a margin for the report. |
3322
|
|
|
|
|
|
|
|
3323
|
|
|
|
|
|
|
=back |
3324
|
|
|
|
|
|
|
|
3325
|
|
|
|
|
|
|
=head2 y_margin |
3326
|
|
|
|
|
|
|
|
3327
|
|
|
|
|
|
|
=over 4 |
3328
|
|
|
|
|
|
|
|
3329
|
|
|
|
|
|
|
The amount of space ( top and bottom ) to leave as a margin for the report. |
3330
|
|
|
|
|
|
|
|
3331
|
|
|
|
|
|
|
=back |
3332
|
|
|
|
|
|
|
|
3333
|
|
|
|
|
|
|
=head1 DATA DEFINITION |
3334
|
|
|
|
|
|
|
|
3335
|
|
|
|
|
|
|
The data definition wraps up most of the previous definitions, apart from the report definition. |
3336
|
|
|
|
|
|
|
You can now safely replace the entire data definition after a render() operation, allowing you |
3337
|
|
|
|
|
|
|
to define different 'sections' of a report. After replacing the data definition, you simply |
3338
|
|
|
|
|
|
|
render() with a new data array. |
3339
|
|
|
|
|
|
|
|
3340
|
|
|
|
|
|
|
Attributes for the data definition: |
3341
|
|
|
|
|
|
|
|
3342
|
|
|
|
|
|
|
=head2 cell_borders |
3343
|
|
|
|
|
|
|
|
3344
|
|
|
|
|
|
|
=over 4 |
3345
|
|
|
|
|
|
|
|
3346
|
|
|
|
|
|
|
Whether to render cell borders or not. This is a legacy option - not that there's any |
3347
|
|
|
|
|
|
|
pressing need to remove it - but this is a precursor to background->{border} support, |
3348
|
|
|
|
|
|
|
which can be defined per-cell. Setting cell_borders in the data definition will cause |
3349
|
|
|
|
|
|
|
all data cells to be filled out with: background->{border} set to grey. |
3350
|
|
|
|
|
|
|
|
3351
|
|
|
|
|
|
|
=back |
3352
|
|
|
|
|
|
|
|
3353
|
|
|
|
|
|
|
=head2 upper_buffer / lower_buffer |
3354
|
|
|
|
|
|
|
|
3355
|
|
|
|
|
|
|
=over 4 |
3356
|
|
|
|
|
|
|
|
3357
|
|
|
|
|
|
|
These 2 keys set the respective buffers ( ie whitespace ) that separates each row of data |
3358
|
|
|
|
|
|
|
from things above ( upper ) and below ( lower ) them. If you don't specify any |
3359
|
|
|
|
|
|
|
buffers, default values of zero will be set to emulate legacy behaviour. |
3360
|
|
|
|
|
|
|
|
3361
|
|
|
|
|
|
|
=back |
3362
|
|
|
|
|
|
|
|
3363
|
|
|
|
|
|
|
=head2 no_field_headers |
3364
|
|
|
|
|
|
|
|
3365
|
|
|
|
|
|
|
=over 4 |
3366
|
|
|
|
|
|
|
|
3367
|
|
|
|
|
|
|
Set to disable rendering field headers when beginning a new page or group. |
3368
|
|
|
|
|
|
|
|
3369
|
|
|
|
|
|
|
=back |
3370
|
|
|
|
|
|
|
|
3371
|
|
|
|
|
|
|
=head2 fields |
3372
|
|
|
|
|
|
|
|
3373
|
|
|
|
|
|
|
=over 4 |
3374
|
|
|
|
|
|
|
|
3375
|
|
|
|
|
|
|
This is your field definition hash, from above. |
3376
|
|
|
|
|
|
|
|
3377
|
|
|
|
|
|
|
=back |
3378
|
|
|
|
|
|
|
|
3379
|
|
|
|
|
|
|
=head2 groups |
3380
|
|
|
|
|
|
|
|
3381
|
|
|
|
|
|
|
=over 4 |
3382
|
|
|
|
|
|
|
|
3383
|
|
|
|
|
|
|
This is your group definition hash, from above. |
3384
|
|
|
|
|
|
|
|
3385
|
|
|
|
|
|
|
=back |
3386
|
|
|
|
|
|
|
|
3387
|
|
|
|
|
|
|
=head2 data_array |
3388
|
|
|
|
|
|
|
|
3389
|
|
|
|
|
|
|
=over 4 |
3390
|
|
|
|
|
|
|
|
3391
|
|
|
|
|
|
|
This is the data to render. |
3392
|
|
|
|
|
|
|
You *MUST* sort the data yourself. If you are grouping by A, then B and you want all data |
3393
|
|
|
|
|
|
|
sorted by C, then make sure you sort by A, B, C. We currently don't do *any* sorting of data, |
3394
|
|
|
|
|
|
|
as I only intended this module to be used in conjunction with a database server, and database |
3395
|
|
|
|
|
|
|
servers are perfect for sorting data :) |
3396
|
|
|
|
|
|
|
|
3397
|
|
|
|
|
|
|
=back |
3398
|
|
|
|
|
|
|
|
3399
|
|
|
|
|
|
|
=head2 page |
3400
|
|
|
|
|
|
|
|
3401
|
|
|
|
|
|
|
=over 4 |
3402
|
|
|
|
|
|
|
|
3403
|
|
|
|
|
|
|
This is a hash describing page headers and footers - see below. |
3404
|
|
|
|
|
|
|
|
3405
|
|
|
|
|
|
|
=back |
3406
|
|
|
|
|
|
|
|
3407
|
|
|
|
|
|
|
=head1 PAGE DEFINITION |
3408
|
|
|
|
|
|
|
|
3409
|
|
|
|
|
|
|
The page definition is a hash describing page headers and footers. Possible keys are: |
3410
|
|
|
|
|
|
|
|
3411
|
|
|
|
|
|
|
=head2 header |
3412
|
|
|
|
|
|
|
|
3413
|
|
|
|
|
|
|
=head2 footer |
3414
|
|
|
|
|
|
|
|
3415
|
|
|
|
|
|
|
Each of these keys is an array of cell definitions. Unique to the page *footer* is the ability |
3416
|
|
|
|
|
|
|
to define the following special tags: |
3417
|
|
|
|
|
|
|
|
3418
|
|
|
|
|
|
|
=over 4 |
3419
|
|
|
|
|
|
|
|
3420
|
|
|
|
|
|
|
%TIME% |
3421
|
|
|
|
|
|
|
|
3422
|
|
|
|
|
|
|
%PAGE% |
3423
|
|
|
|
|
|
|
|
3424
|
|
|
|
|
|
|
%PAGES% |
3425
|
|
|
|
|
|
|
|
3426
|
|
|
|
|
|
|
=back |
3427
|
|
|
|
|
|
|
|
3428
|
|
|
|
|
|
|
These will be replaced with the relevant data when rendered. |
3429
|
|
|
|
|
|
|
|
3430
|
|
|
|
|
|
|
If you don't specify a page footer, one will be supplied for you. This is to provide maximum |
3431
|
|
|
|
|
|
|
compatibility with previous versions, which had page footers hard-coded. If you want to supress |
3432
|
|
|
|
|
|
|
this behaviour, then set a value for $self->{data}->{page}->{footerless} |
3433
|
|
|
|
|
|
|
|
3434
|
|
|
|
|
|
|
=head1 MULTI-LINE ROWS |
3435
|
|
|
|
|
|
|
|
3436
|
|
|
|
|
|
|
=over 4 |
3437
|
|
|
|
|
|
|
|
3438
|
|
|
|
|
|
|
You can define 'multi-line' rows of cell definitions by simply appending all subsequent lines |
3439
|
|
|
|
|
|
|
to the array of cell definitions. When PDF::ReportWriter sees a cell with a percentage that would |
3440
|
|
|
|
|
|
|
push the combined percentage beyond 100%, a new-line is assumed. |
3441
|
|
|
|
|
|
|
|
3442
|
|
|
|
|
|
|
=back |
3443
|
|
|
|
|
|
|
|
3444
|
|
|
|
|
|
|
=back |
3445
|
|
|
|
|
|
|
|
3446
|
|
|
|
|
|
|
=head1 METHODS |
3447
|
|
|
|
|
|
|
|
3448
|
|
|
|
|
|
|
=head2 new ( report_definition ) |
3449
|
|
|
|
|
|
|
|
3450
|
|
|
|
|
|
|
=over 4 |
3451
|
|
|
|
|
|
|
|
3452
|
|
|
|
|
|
|
Object constructor. Pass the report definition in. |
3453
|
|
|
|
|
|
|
|
3454
|
|
|
|
|
|
|
=back |
3455
|
|
|
|
|
|
|
|
3456
|
|
|
|
|
|
|
=head2 render_data ( data_definition ) |
3457
|
|
|
|
|
|
|
|
3458
|
|
|
|
|
|
|
=over 4 |
3459
|
|
|
|
|
|
|
|
3460
|
|
|
|
|
|
|
Renders the data passed in. |
3461
|
|
|
|
|
|
|
|
3462
|
|
|
|
|
|
|
You can call 'render_data' as many times as you want, with different data and definitions. |
3463
|
|
|
|
|
|
|
If you want do call render_data multiple times, though, be aware that you will have to destroy |
3464
|
|
|
|
|
|
|
$report->{data}->{field_headers} if you expect new field headers to be automatically generated |
3465
|
|
|
|
|
|
|
from your cells ( ie if you don't provide your own field_headers, which is probably normally |
3466
|
|
|
|
|
|
|
the case ). Otherwise if you don't destroy $report->{data}->{field_headers} and you don't provide |
3467
|
|
|
|
|
|
|
your own, you will get the field headers from the last render_data() operation. |
3468
|
|
|
|
|
|
|
|
3469
|
|
|
|
|
|
|
=back |
3470
|
|
|
|
|
|
|
|
3471
|
|
|
|
|
|
|
=head2 render_report ( xml [, data ] ) |
3472
|
|
|
|
|
|
|
|
3473
|
|
|
|
|
|
|
=over 4 |
3474
|
|
|
|
|
|
|
|
3475
|
|
|
|
|
|
|
Should be used when dealing with xml format reports. One call to rule them all. |
3476
|
|
|
|
|
|
|
The first argument can be either an xml filename or a C |
3477
|
|
|
|
|
|
|
object. The 2nd argument is the real data to be used in your report. |
3478
|
|
|
|
|
|
|
Example of usage for first case (xml file): |
3479
|
|
|
|
|
|
|
|
3480
|
|
|
|
|
|
|
my $rw = PDF::ReportWriter->new(); |
3481
|
|
|
|
|
|
|
my @data = ( |
3482
|
|
|
|
|
|
|
[2004, 'Income', 1000.000 ], |
3483
|
|
|
|
|
|
|
[2004, 'Expenses', 500.000 ], |
3484
|
|
|
|
|
|
|
[2005, 'Income', 5000.000 ], |
3485
|
|
|
|
|
|
|
[2005, 'Expenses', 600.000 ], |
3486
|
|
|
|
|
|
|
[2006, 'Income (projection)', 9999.000 ], |
3487
|
|
|
|
|
|
|
[2006, 'Expenses (projection), 900.000 ], |
3488
|
|
|
|
|
|
|
); |
3489
|
|
|
|
|
|
|
$rw->render_report('./account.xml', \@data); |
3490
|
|
|
|
|
|
|
|
3491
|
|
|
|
|
|
|
# Save to disk |
3492
|
|
|
|
|
|
|
$rw->save(); |
3493
|
|
|
|
|
|
|
|
3494
|
|
|
|
|
|
|
# or get a scalar with all pdf document |
3495
|
|
|
|
|
|
|
my $pdf_doc = $rw->stringify(); |
3496
|
|
|
|
|
|
|
|
3497
|
|
|
|
|
|
|
For an example of xml report file, take a look at C |
3498
|
|
|
|
|
|
|
folder in the PDF::ReportWriter distribution or to |
3499
|
|
|
|
|
|
|
C documentation. |
3500
|
|
|
|
|
|
|
|
3501
|
|
|
|
|
|
|
The alternative form allows for more flexibility. You can pass a |
3502
|
|
|
|
|
|
|
C basic object with a report profile |
3503
|
|
|
|
|
|
|
already loaded. Example: |
3504
|
|
|
|
|
|
|
|
3505
|
|
|
|
|
|
|
my $rw = PDF::ReportWriter->new(); |
3506
|
|
|
|
|
|
|
my $rp = PDF::ReportWriter::Report->new('./account.xml'); |
3507
|
|
|
|
|
|
|
# ... Assume @data as before ... |
3508
|
|
|
|
|
|
|
$rw->render_report($rp, \@data); |
3509
|
|
|
|
|
|
|
$rw->save(); |
3510
|
|
|
|
|
|
|
|
3511
|
|
|
|
|
|
|
If you desire the maximum flexibility, you can also pass B object |
3512
|
|
|
|
|
|
|
in the world that supports C and C methods, where |
3513
|
|
|
|
|
|
|
C should return a B (TO BE CONTINUED), |
3514
|
|
|
|
|
|
|
and C should return an arrayref with all actual records that |
3515
|
|
|
|
|
|
|
you want your report to include, as returned by DBI's C |
3516
|
|
|
|
|
|
|
method. |
3517
|
|
|
|
|
|
|
|
3518
|
|
|
|
|
|
|
As with C, you can call C as many times as you want. |
3519
|
|
|
|
|
|
|
The PDF file will grow as necessary. There is only one problem in rendering |
3520
|
|
|
|
|
|
|
of header sections when re-calling C. |
3521
|
|
|
|
|
|
|
|
3522
|
|
|
|
|
|
|
=back |
3523
|
|
|
|
|
|
|
|
3524
|
|
|
|
|
|
|
=head2 fetch_group_results( { cell => "cell_name", group => "group_name" } ) |
3525
|
|
|
|
|
|
|
|
3526
|
|
|
|
|
|
|
=over 4 |
3527
|
|
|
|
|
|
|
|
3528
|
|
|
|
|
|
|
This is a convenience function that allows you to retrieve current aggregate values. |
3529
|
|
|
|
|
|
|
Pass a hash with the items 'cell' ( the name of the cell with the aggregate function ) and |
3530
|
|
|
|
|
|
|
'group' ( the group level you want results from ). A good place to use this function is in |
3531
|
|
|
|
|
|
|
conjunction with a cell's custom_render_func(). For example, you might create a |
3532
|
|
|
|
|
|
|
custom_render_func to do some calculations on running totals, and use fetch_group_results() to |
3533
|
|
|
|
|
|
|
get access to those running totals. |
3534
|
|
|
|
|
|
|
|
3535
|
|
|
|
|
|
|
=back |
3536
|
|
|
|
|
|
|
|
3537
|
|
|
|
|
|
|
=head2 new_page |
3538
|
|
|
|
|
|
|
|
3539
|
|
|
|
|
|
|
=over 4 |
3540
|
|
|
|
|
|
|
|
3541
|
|
|
|
|
|
|
Creates a new page, which in turn calls ->page_template ( see below ). |
3542
|
|
|
|
|
|
|
|
3543
|
|
|
|
|
|
|
=back |
3544
|
|
|
|
|
|
|
|
3545
|
|
|
|
|
|
|
=head2 page_template ( [ path_to_template ] ) |
3546
|
|
|
|
|
|
|
|
3547
|
|
|
|
|
|
|
=over 4 |
3548
|
|
|
|
|
|
|
|
3549
|
|
|
|
|
|
|
This function creates a new page ( and is in fact called by ->new_page ).< |
3550
|
|
|
|
|
|
|
If called with no arguements, it will either use default template, or if there is none, |
3551
|
|
|
|
|
|
|
it will simply create a blank page. Alternatively, you can pass it the path to a PDF |
3552
|
|
|
|
|
|
|
to use as a template for the new page ( the 1st page of the PDF that you pass will |
3553
|
|
|
|
|
|
|
be used ). |
3554
|
|
|
|
|
|
|
|
3555
|
|
|
|
|
|
|
=back |
3556
|
|
|
|
|
|
|
|
3557
|
|
|
|
|
|
|
=head2 save |
3558
|
|
|
|
|
|
|
|
3559
|
|
|
|
|
|
|
=over 4 |
3560
|
|
|
|
|
|
|
|
3561
|
|
|
|
|
|
|
Saves the pdf file ( in the location specified in the report definition ). |
3562
|
|
|
|
|
|
|
|
3563
|
|
|
|
|
|
|
=back |
3564
|
|
|
|
|
|
|
|
3565
|
|
|
|
|
|
|
=head2 saveas ( newfile ) |
3566
|
|
|
|
|
|
|
|
3567
|
|
|
|
|
|
|
=over 4 |
3568
|
|
|
|
|
|
|
|
3569
|
|
|
|
|
|
|
Saves the pdf file in the location specified by C string and |
3570
|
|
|
|
|
|
|
overrides default report C property. |
3571
|
|
|
|
|
|
|
|
3572
|
|
|
|
|
|
|
=back |
3573
|
|
|
|
|
|
|
|
3574
|
|
|
|
|
|
|
=head2 stringify |
3575
|
|
|
|
|
|
|
|
3576
|
|
|
|
|
|
|
=over 4 |
3577
|
|
|
|
|
|
|
|
3578
|
|
|
|
|
|
|
Returns the pdf document as a scalar. |
3579
|
|
|
|
|
|
|
|
3580
|
|
|
|
|
|
|
=back |
3581
|
|
|
|
|
|
|
|
3582
|
|
|
|
|
|
|
=head2 print ( options ) |
3583
|
|
|
|
|
|
|
|
3584
|
|
|
|
|
|
|
=over 4 |
3585
|
|
|
|
|
|
|
|
3586
|
|
|
|
|
|
|
Tries to print the report pdf file to a CUPS print queue. For now, it only works |
3587
|
|
|
|
|
|
|
with CUPS, though you can supply several options to drive the print job as you like. |
3588
|
|
|
|
|
|
|
Allowed options, to be specified as an hash reference, with their default values, |
3589
|
|
|
|
|
|
|
are the following: |
3590
|
|
|
|
|
|
|
|
3591
|
|
|
|
|
|
|
=over 4 |
3592
|
|
|
|
|
|
|
|
3593
|
|
|
|
|
|
|
=item command |
3594
|
|
|
|
|
|
|
|
3595
|
|
|
|
|
|
|
The command to be launched to spool the pdf report (C). |
3596
|
|
|
|
|
|
|
|
3597
|
|
|
|
|
|
|
=item printer |
3598
|
|
|
|
|
|
|
|
3599
|
|
|
|
|
|
|
Name of CUPS printer to print to (no default). If not specified, |
3600
|
|
|
|
|
|
|
takes your system default printer. |
3601
|
|
|
|
|
|
|
|
3602
|
|
|
|
|
|
|
=item tempdir |
3603
|
|
|
|
|
|
|
|
3604
|
|
|
|
|
|
|
Temporary directory where to put the spool file (C). |
3605
|
|
|
|
|
|
|
|
3606
|
|
|
|
|
|
|
=item unlink |
3607
|
|
|
|
|
|
|
|
3608
|
|
|
|
|
|
|
If true, deletes the temporary spool file (C). |
3609
|
|
|
|
|
|
|
|
3610
|
|
|
|
|
|
|
=back |
3611
|
|
|
|
|
|
|
|
3612
|
|
|
|
|
|
|
=back |
3613
|
|
|
|
|
|
|
|
3614
|
|
|
|
|
|
|
=head1 EXAMPLES |
3615
|
|
|
|
|
|
|
|
3616
|
|
|
|
|
|
|
=over 4 |
3617
|
|
|
|
|
|
|
|
3618
|
|
|
|
|
|
|
Check out the C folder in the main PDF::ReportWriter distribution that |
3619
|
|
|
|
|
|
|
contains a simple demonstration of results that can be achieved. |
3620
|
|
|
|
|
|
|
|
3621
|
|
|
|
|
|
|
=back |
3622
|
|
|
|
|
|
|
|
3623
|
|
|
|
|
|
|
=head1 AUTHORS |
3624
|
|
|
|
|
|
|
|
3625
|
|
|
|
|
|
|
=over 4 |
3626
|
|
|
|
|
|
|
|
3627
|
|
|
|
|
|
|
Dan |
3628
|
|
|
|
|
|
|
Cosimo Streppone |
3629
|
|
|
|
|
|
|
|
3630
|
|
|
|
|
|
|
=back |
3631
|
|
|
|
|
|
|
|
3632
|
|
|
|
|
|
|
=head1 BUGS |
3633
|
|
|
|
|
|
|
|
3634
|
|
|
|
|
|
|
=over 4 |
3635
|
|
|
|
|
|
|
|
3636
|
|
|
|
|
|
|
I think you must be mistaken. |
3637
|
|
|
|
|
|
|
|
3638
|
|
|
|
|
|
|
=back |
3639
|
|
|
|
|
|
|
|
3640
|
|
|
|
|
|
|
=head1 ISSUES |
3641
|
|
|
|
|
|
|
|
3642
|
|
|
|
|
|
|
=over 4 |
3643
|
|
|
|
|
|
|
|
3644
|
|
|
|
|
|
|
In the last release of PDF::ReportWriter, I complained bitterly about printing PDFs from Linux. |
3645
|
|
|
|
|
|
|
I am very happy to be able to say that this situation has improved significantly. Using the |
3646
|
|
|
|
|
|
|
latest versions of evince and poppler ( v0.5.1 ), I am now getting *perfect* results when |
3647
|
|
|
|
|
|
|
printing. If you are having issues printing, I suggest updating to the above. |
3648
|
|
|
|
|
|
|
|
3649
|
|
|
|
|
|
|
=back |
3650
|
|
|
|
|
|
|
|
3651
|
|
|
|
|
|
|
=head1 Other cool things you should know about: |
3652
|
|
|
|
|
|
|
|
3653
|
|
|
|
|
|
|
=over 4 |
3654
|
|
|
|
|
|
|
|
3655
|
|
|
|
|
|
|
This module is part of an umbrella project, 'Axis Not Evil', which aims to make |
3656
|
|
|
|
|
|
|
Rapid Application Development of database apps using open-source tools a reality. |
3657
|
|
|
|
|
|
|
The project includes: |
3658
|
|
|
|
|
|
|
|
3659
|
|
|
|
|
|
|
Gtk2::Ex::DBI - forms |
3660
|
|
|
|
|
|
|
Gtk2::Ex::Datasheet::DBI - datasheets |
3661
|
|
|
|
|
|
|
PDF::ReportWriter - reports |
3662
|
|
|
|
|
|
|
|
3663
|
|
|
|
|
|
|
All the above modules are available via cpan, or for more information, screenshots, etc, see: |
3664
|
|
|
|
|
|
|
http://entropy.homelinux.org/axis |
3665
|
|
|
|
|
|
|
|
3666
|
|
|
|
|
|
|
=back |
3667
|
|
|
|
|
|
|
|
3668
|
|
|
|
|
|
|
=head1 Crank ON! |
3669
|
|
|
|
|
|
|
|
3670
|
|
|
|
|
|
|
=cut |