| 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 |