File Coverage

blib/lib/PDF/ReportWriter.pm
Criterion Covered Total %
statement 67 823 8.1
branch 5 438 1.1
condition 0 193 0.0
subroutine 21 50 42.0
pod 11 30 36.6
total 104 1534 6.7


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