File Coverage

blib/lib/PDF/API2/Ladder.pm
Criterion Covered Total %
statement 22 220 10.0
branch 0 142 0.0
condition 0 30 0.0
subroutine 8 15 53.3
pod 0 7 0.0
total 30 414 7.2


line stmt bran cond sub pod time code
1             package PDF::API2::Ladder;
2              
3 1     1   20406 use strict;
  1         2  
  1         33  
4 1     1   23 use 5.008_005;
  1         2  
  1         49  
5             our $VERSION = '0.03';
6              
7 1     1   2917 BEGIN {
8 1     1   1133 use PDF::API2;
  1         288607  
  1         58  
9 1     1   14 use constant mm => 25.4 / 72;
  1         2  
  1         108  
10 1     1   6 use constant in => 1 / 72;
  1         2  
  1         47  
11 1     1   7 use constant pt => 1;
  1         2  
  1         127  
12              
13 1     1   7 use utf8;
  1         2  
  1         10  
14             }
15              
16             =encoding utf-8
17              
18             =head1 NAME
19              
20             PDF::API2::Ladder - Creates PDFs a line at a time, much like the rungs on a ladder.
21              
22             =head1 SYNOPSIS
23              
24             use PDF::API2::Ladder;
25              
26             # Create a new PDF::Ladder Object
27             $pdf = PDF::Ladder->new( font_size => $font_size,
28             lead => $lead,
29             font => 'Georgia',
30             show_margins => $show_margins,
31             margin_top => .5/in,
32             margin_bottom => .5/in,
33             line_height => $line_height);
34              
35             # Create a Rung at the top of the page (since its the first) that is center.
36             $pdf->add_rung("An Amazing Play", align => 'center');
37             # Create a Rung underneath that, which is not centered
38             $pdf->add_rung("Actor1: 'To be or not 2 B. That is the answer!');
39              
40             =head1 DESCRIPTION
41              
42             PDF::API2::Ladder is a simplified way of creating PDFs using the awesome module PDF::API2. PDF::API2::Ladder builds PDFs in a top down fashion much like rungs on a ladder. The exception to the rung style is what is called a Blob. Blobs do not have a set height and adapt to their contents height instead. New pages are created automatically when a rung or blob goes off the end of the page.
43              
44             =head1 METHODS
45              
46             TODO
47              
48             =over
49              
50             =cut
51              
52             sub new {
53 0     0 0   my $class=shift(@_);
54 0           my %opt=@_;
55 0           my $self={};
56 0           bless($self, $class);
57              
58             # File Preferences
59 0 0         $self->{path} = ($opt{'path'}) ? $opt{'path'} : './';
60 0           $self->{file_name} = $opt{'filename'};
61              
62             # ----- General Format -----
63 0 0         $self->{media_width} = ($opt{'media_width'}) ? $opt{'media_width'} : 8.5/in;
64 0 0         $self->{media_height} = ($opt{'media_height'}) ? $opt{'media_height'} : 11/in;
65              
66 0           $self->{show_margins} = $opt{'show_margins'};
67              
68             # Margins
69 0 0         $self->{margin_top} = ($opt{'margin_top'}) ? $opt{'margin_top'} : 1/in;
70 0 0         $self->{margin_right} = ($opt{'margin_right'}) ? $opt{'margin_right'} : 1/in;
71 0 0         $self->{margin_bottom} = ($opt{'margin_bottom'}) ? $opt{'margin_bottom'} : 1/in;
72 0 0         $self->{margin_left} = ($opt{'margin_left'}) ? $opt{'margin_left'} : 1/in;
73 0 0         if ($opt{'margin'}) {
74 0           $self->{margin_top} = $self->{margin_right} = $self->{margin_bottom} = $self->{margin_left} = $opt{'margin'};
75             }
76              
77             # Line Format
78 0 0         $self->{line_height} = ($opt{'line_height'}) ? $opt{'line_height'} : 1/in;
79 0           $self->{current_line_offset} = 0; # Lines are indexed starting at 1
80              
81             # Fonts
82 0 0         $self->{font} = ($opt{'font'}) ? $opt{'font'} : 'Helvetica';
83 0 0         $self->{font_size} = ($opt{'font_size'}) ? $opt{'font_size'} : 12/pt;
84 0 0         $self->{lead} = ($opt{'lead'}) ? $opt{'lead'} : 7/pt;
85 0 0         $self->{font_color} = ($opt{'font_color'}) ? $opt{'font_color'} : 'black';
86 0 0         $self->{charspace} = (defined $opt{'charspace'}) ? $opt{'charspace'} : 0;
87              
88             # Setup
89 0 0         if ($self->{file_name}) {
90 0           $self->{pdf} = PDF::API2->new( -file => $self->{path}.$self->{file_name} );
91             } else {
92 0           $self->{pdf} = PDF::API2->new();
93             }
94              
95             # Declar the included fonts
96 0           $self->{fonts} = {
97             Helvetica => {
98             Bold => $self->{pdf}->corefont( 'Helvetica-Bold', -encoding => 'latin1' ),
99             Roman => $self->{pdf}->corefont( 'Helvetica', -encoding => 'latin1' ),
100             Italic => $self->{pdf}->corefont( 'Helvetica-Oblique', -encoding => 'latin1' ),
101             BoldItalic => $self->{pdf}->corefont( 'Helvetica-BoldOblique', -encoding => 'latin1' ),
102             Italic => $self->{pdf}->corefont( 'Helvetica-Oblique', -encoding => 'latin1' ),
103             },
104             Times => {
105             Bold => $self->{pdf}->corefont( 'Times-Bold', -encoding => 'latin1' ),
106             Roman => $self->{pdf}->corefont( 'Times', -encoding => 'latin1' ),
107             Italic => $self->{pdf}->corefont( 'Times-Italic', -encoding => 'latin1' ),
108             BoldItalic => $self->{pdf}->corefont( 'Times-BoldItalic', -encoding => 'latin1' ),
109             },
110             Georgia => {
111             Bold => $self->{pdf}->corefont( 'Georgia,Bold', -encoding => 'latin1' ),
112             Roman => $self->{pdf}->corefont( 'Georgia', -encoding => 'latin1' ),
113             Italic => $self->{pdf}->corefont( 'Georgia,Italic', -encoding => 'latin1' ),
114             BoldItalic => $self->{pdf}->corefont( 'Georgia,BoldItalic', -encoding => 'latin1' ),
115             },
116             };
117 0 0         if ($opt{'fonts'}) {
118 0           $self->{fonts} = $opt{'fonts'};
119             }
120              
121 0           $self->start_new_page();
122              
123 0           return $self;
124             }
125              
126             #=item $pdf->add_rung %opts
127             #
128             #Creates a new "rung" or line in the PDF. When provided with specific parameters, the text is changed accordingly.
129             #
130             #B
131             #
132             # $pdf = PDF::API2->new();
133             # ...
134             # print $pdf->stringify;
135             #
136             # $pdf = PDF::API2->new();
137             # ...
138             # $pdf->saveas("our/new.pdf");
139             #
140             # $pdf = PDF::API2->new(-file => 'our/new.pdf');
141             # ...
142             # $pdf->save;
143             #
144             #=cut
145             sub add_rung {
146 0     0 0   my $self=shift;
147 0           my $text = shift;
148 0           my %options = @_;
149              
150 0 0         my $bold = ($options{'bold'}) ? 1 : 0;
151 0 0         my $oblique = ($options{'oblique'}) ? 1 : 0;
152 0 0         my $indent = ($options{'indent'}) ? $options{'indent'} : 0;
153 0 0         my $align = ($options{'align'}) ? $options{'align'} : 'left';
154             # Font parameters
155 0 0         my $font_size = ($options{'font_size'}) ? $options{'font_size'} : $self->{font_size};
156 0 0         my $fonts = ($options{'fonts'}) ? $options{'fonts'} : $self->{fonts};
157 0 0         my $font = ($options{'font'}) ? $options{'font'} : $self->{font};
158 0 0         my $font_color = ($options{'font_color'}) ? $options{'font_color'} : $self->{font_color};
159 0 0         my $charspace = (defined $options{'charspace'}) ? $options{'charspace'} : $self->{charspace};
160              
161 0 0         my $line_height = ($options{'line_height'}) ? $options{'line_height'} : $self->{line_height};
162 0 0         my $lead = ($options{'lead'}) ? $options{'lead'} : $self->{lead};
163              
164             # Check to see if the next rung will fit
165 0 0         if ( $self->{media_height} - ($self->{margin_top} + $self->{margin_bottom} + $self->{current_line_offset} ) < $line_height ) {
166 0           $self->start_new_page();
167             }
168              
169 0           my $text_element = $self->{current_page}->text;
170              
171             # Font
172 0           my $font_key = '';
173 0 0         if ($bold) { $font_key .= "Bold"; }
  0            
174 0 0         if ($oblique) { $font_key .= "Italic"; }
  0            
175 0 0         if ($font_key eq '') { $font_key = 'Roman'; }
  0            
176              
177 0           $text_element->font( $fonts->{$font}{$font_key}, $font_size );
178 0           $text_element->fillcolor($font_color);
179 0           $text_element->charspace($charspace);
180              
181 0           my ( $endw, $ypos, $paragraph ) = text_block(
182             $text_element,
183             $text,
184             -x => $self->{margin_left},
185             -y => $self->{'media_height'} - ( $self->{margin_top} + $self->{current_line_offset} + $lead ),
186             -w => $self->{media_width} - $self->{margin_left} - $self->{margin_right},
187             -h => $line_height,
188             -lead => $lead,
189             -indent => $indent,
190             -parspace => 6/pt,
191             -align => $align,
192             );
193              
194 0           $self->{current_line_offset} += $line_height;
195            
196 0           return 1;
197             }
198              
199             #=item $pdf->add_blob %opts
200             #
201             #Creates a new set of lines with undetermined height in the PDF.
202             #
203             #B
204             #
205             # $pdf = PDF::API2->new();
206             # ...
207             # print $pdf->stringify;
208             #
209             # $pdf = PDF::API2->new();
210             # ...
211             # $pdf->saveas("our/new.pdf");
212             #
213             # $pdf = PDF::API2->new(-file => 'our/new.pdf');
214             # ...
215             # $pdf->save;
216             #
217             #=cut
218             sub add_blob {
219 0     0 0   my $self=shift;
220 0           my $text = shift;
221 0           my %options = @_;
222              
223 0 0         my $fonts = ($options{'fonts'}) ? $options{'fonts'} : $self->{fonts};
224 0 0         my $font = ($options{'font'}) ? $options{'font'} : $self->{font};
225 0 0         my $bold = ($options{'bold'}) ? 1 : 0;
226 0 0         my $oblique = ($options{'oblique'}) ? 1 : 0;
227 0 0         my $font_color = ($options{'font_color'}) ? $options{'font_color'} : $self->{font_color};
228 0 0         my $charspace = (defined $options{'charspace'}) ? $options{'charspace'} : $self->{charspace};
229              
230 0 0         my $line_height = ($options{'line_height'}) ? $options{'line_height'} : $self->{line_height};
231 0 0         my $lead = ($options{'lead'}) ? $options{'lead'} : $self->{lead};
232              
233             # Check to see if the next rung will fit
234 0 0         if ( $self->{media_height} - ($self->{margin_top} + $self->{margin_bottom} + $self->{current_line_offset} ) < $line_height ) {
235 0           $self->start_new_page();
236             }
237              
238 0           my $text_element = $self->{current_page}->text;
239             # Font
240 0           my $font_key = '';
241 0 0         if ($bold) { $font_key .= "Bold"; }
  0            
242 0 0         if ($oblique) { $font_key .= "Italic"; }
  0            
243 0 0         if ($font_key eq '') { $font_key = 'Roman'; }
  0            
244              
245 0           $text_element->font( $fonts->{$font}{$font_key}, $self->{font_size} );
246 0           $text_element->fillcolor($font_color);
247 0           $text_element->charspace($charspace);
248              
249             # Check to see if the next rung will fit
250 0           my ( $endw, $ypos, $paragraph ) = text_block(
251             $text_element,
252             $text,
253             -x => $self->{margin_left},
254             -y => $self->{'media_height'} - ( $self->{margin_top} + $self->{current_line_offset} + $lead ),
255             -w => $self->{media_width} - $self->{margin_left} - $self->{margin_right},
256             -lead => $lead,
257             -heightless => 1,
258             -measure => 1,
259             -parspace => 6/pt,
260             -align => 'left',
261             );
262 0 0         if ( $ypos < $self->{margin_top} ) {
263 0           $self->start_new_page();
264 0           $text_element = $self->{current_page}->text;
265             # Font
266 0           my $font_key = '';
267 0 0         if ($bold) { $font_key .= "Bold"; }
  0            
268 0 0         if ($oblique) { $font_key .= "Italic"; }
  0            
269 0 0         if ($font_key eq '') { $font_key = 'Roman'; }
  0            
270              
271 0           $text_element->font( $fonts->{$font}{$font_key}, $self->{font_size} );
272 0           $text_element->fillcolor($font_color);
273             }
274              
275 0           my ( $endw, $ypos, $paragraph ) = text_block(
276             $text_element,
277             $text,
278             -x => $self->{margin_left},
279             -y => $self->{'media_height'} - ( $self->{margin_top} + $self->{current_line_offset} + $lead ),
280             -w => $self->{media_width} - $self->{margin_left} - $self->{margin_right},
281             -heightless => 1,
282             -lead => $lead,
283             -parspace => 6/pt,
284             -align => 'left',
285             );
286              
287 0           $self->{current_line_offset} += $self->{'media_height'} - ( $self->{margin_top} + $self->{current_line_offset} + $lead ) - $ypos;
288            
289 0           return 1;
290             }
291              
292             #=item $pdf->start_new_page %opts
293             #
294             #Ends current page PDF.
295             #
296             #B
297             #
298             # $pdf = PDF::API2->new();
299             # ...
300             # print $pdf->stringify;
301             #
302             # $pdf = PDF::API2->new();
303             # ...
304             # $pdf->saveas("our/new.pdf");
305             #
306             # $pdf = PDF::API2->new(-file => 'our/new.pdf');
307             # ...
308             # $pdf->save;
309             #
310             #=cut
311             sub start_new_page {
312 0     0 0   my $self=shift;
313 0           my %options = @_;
314              
315 0           $self->{current_page} = $self->{pdf}->page;
316            
317             # Set pdf sizes
318 0           $self->{current_page}->mediabox($self->{media_width},$self->{media_height});
319              
320 0           $self->{current_line_offset} = 0;
321              
322             # Margin debuggin
323 0 0         if ($self->{show_margins}) {
324 0           my $margins = $self->{'current_page'}->gfx();
325 0           $margins->strokecolor('red');
326              
327             # top margin
328 0           $margins->move($self->{margin_left}, $self->{media_height} - $self->{margin_top});
329 0           $margins->line($self->{media_width} - $self->{margin_right}, $self->{media_height} - $self->{margin_top});
330              
331             # right margin
332 0           $margins->move($self->{media_width} - $self->{margin_right}, $self->{media_height} - $self->{margin_top});
333 0           $margins->line($self->{media_width} - $self->{margin_right}, $self->{margin_bottom});
334              
335             # bottom margin
336 0           $margins->move($self->{media_width} - $self->{margin_right}, $self->{margin_bottom});
337 0           $margins->line($self->{margin_left}, $self->{margin_bottom});
338              
339             # left margin
340 0           $margins->move($self->{margin_left}, $self->{margin_bottom});
341 0           $margins->line($self->{margin_left}, $self->{media_height} - $self->{margin_top});
342            
343             # Stroke lines
344 0           $margins->stroke;
345             }
346              
347 0           return 1;
348             }
349              
350             #=item $pdf->save %opts
351             #
352             #Saves out the PDF.
353             #
354             #B
355             #
356             # $pdf = PDF::API2->new();
357             # ...
358             # print $pdf->stringify;
359             #
360             # $pdf = PDF::API2->new();
361             # ...
362             # $pdf->saveas("our/new.pdf");
363             #
364             # $pdf = PDF::API2->new(-file => 'our/new.pdf');
365             # ...
366             # $pdf->save;
367             #
368             #=cut
369             sub save {
370 0     0 0   my $self=shift;
371 0           my %options = @_;
372              
373 0           $self->{pdf}->save;
374 0           $self->{pdf}->end();
375            
376 0           return 1;
377             }
378              
379             #=item $pdf->stringify %opts
380             #
381             #Saves out the PDF as string.
382             #
383             #B
384             #
385             # $pdf = PDF::API2->new();
386             # ...
387             # print $pdf->stringify;
388             #
389             # $pdf = PDF::API2->new();
390             # ...
391             # $pdf->saveas("our/new.pdf");
392             #
393             # $pdf = PDF::API2->new(-file => 'our/new.pdf');
394             # ...
395             # $pdf->save;
396             #
397             #=cut
398             sub stringify {
399 0     0 0   my $self=shift;
400 0           my %options = @_;
401              
402 0           return $self->{pdf}->stringify();
403             }
404              
405             #--- Text block -------------------------------------------------------------
406             # This code was borrowed from a tutorial. It is an easy way to create paragraphs in PDFs.
407             sub text_block {
408 0     0 0   my $text_object = shift;
409 0           my $text = shift;
410            
411 0           my $endw;
412            
413 0           my %arg = @_;
414            
415             # Get the text in paragraphs
416 0           my @paragraphs = split( /\n/, $text );
417            
418             # calculate width of all words
419 0           my $space_width = $text_object->advancewidth(' ');
420            
421 0           my @words = split( /\s+/, $text );
422 0           my %width = ();
423 0           foreach (@words) {
424 0 0         next if exists $width{$_};
425 0           $width{$_} = $text_object->advancewidth($_);
426             }
427            
428 0           my $ypos = $arg{'-y'};
429 0           my @paragraph = split( / /, shift(@paragraphs) );
430            
431 0           my $first_line = 1;
432 0           my $first_paragraph = 1;
433              
434 0 0         if (not exists $arg{'-h'}) { $arg{'-heightless'} = 1; }
  0            
435            
436             # while we can add another line
437              
438 0   0       while ( ( $ypos >= $arg{'-y'} - $arg{'-h'} + $arg{'-lead'} ) or $arg{'-heightless'} ) {
439 0 0         unless (@paragraph) {
440 0 0         last unless scalar @paragraphs;
441            
442 0           @paragraph = split( / /, shift(@paragraphs) );
443            
444 0 0         $ypos -= $arg{'-parspace'} if $arg{'-parspace'};
445 0 0         if (not $arg{'-heightless'}) {
446 0 0         last unless $ypos >= $arg{'-y'} - $arg{'-h'};
447             }
448            
449 0           $first_line = 1;
450 0           $first_paragraph = 0;
451             }
452            
453 0           my $xpos = $arg{'-x'};
454            
455             # while there's room on the line, add another word
456 0           my @line = ();
457            
458 0           my $line_width = 0;
459 0 0 0       if ( $first_line && exists $arg{'-hang'} ) {
    0 0        
    0 0        
    0          
460            
461 0           my $hang_width = $text_object->advancewidth( $arg{'-hang'} );
462            
463 0 0 0       if (not ($arg{'-heightless'} and $arg{'-measure'}) ) { # skip adding text if just measuring
464 0           $text_object->translate( $xpos, $ypos );
465 0           $text_object->text( $arg{'-hang'} );
466             }
467            
468 0           $xpos += $hang_width;
469 0           $line_width += $hang_width;
470 0 0         $arg{'-indent'} += $hang_width if $first_paragraph;
471            
472             }
473             elsif ( $first_line && exists $arg{'-flindent'} ) {
474            
475 0           $xpos += $arg{'-flindent'};
476 0           $line_width += $arg{'-flindent'};
477            
478             }
479             elsif ( $first_paragraph && exists $arg{'-fpindent'} ) {
480            
481 0           $xpos += $arg{'-fpindent'};
482 0           $line_width += $arg{'-fpindent'};
483            
484             }
485             elsif ( exists $arg{'-indent'} ) {
486            
487 0           $xpos += $arg{'-indent'};
488 0           $line_width += $arg{'-indent'};
489            
490             }
491            
492 0   0       while ( @paragraph
493             and $line_width + ( scalar(@line) * $space_width ) +
494             $width{ $paragraph[0] } < $arg{'-w'} )
495             {
496            
497 0           $line_width += $width{ $paragraph[0] };
498 0           push( @line, shift(@paragraph) );
499            
500             }
501            
502             # calculate the space width
503 0           my ( $wordspace, $align );
504 0 0 0       if ( $arg{'-align'} eq 'fulljustify'
      0        
505             or ( $arg{'-align'} eq 'justify' and @paragraph ) )
506             {
507            
508 0 0         if ( scalar(@line) == 1 ) {
509 0           @line = split( //, $line[0] );
510            
511             }
512 0           $wordspace = ( $arg{'-w'} - $line_width ) / ( scalar(@line) - 1 );
513            
514 0           $align = 'justify';
515             }
516             else {
517 0 0         $align = ( $arg{'-align'} eq 'justify' ) ? 'left' : $arg{'-align'};
518            
519 0           $wordspace = $space_width;
520             }
521 0           $line_width += $wordspace * ( scalar(@line) - 1 );
522            
523 0 0         if ( $align eq 'justify' ) {
524 0           foreach my $word (@line) {
525            
526 0 0 0       if (not ($arg{'-heightless'} and $arg{'-measure'}) ) { # skip adding text if just measuring
527 0           $text_object->translate( $xpos, $ypos );
528 0           $text_object->text($word, -indent => $arg{'-indent'});
529             }
530            
531 0 0         $xpos += ( $width{$word} + $wordspace ) if (@line);
532            
533             }
534 0           $endw = $arg{'-w'};
535             }
536             else {
537            
538             # calculate the left hand position of the line
539 0 0         if ( $align eq 'right' ) {
    0          
540 0           $xpos += $arg{'-w'} - $line_width;
541            
542             }
543             elsif ( $align eq 'center' ) {
544 0           $xpos += ( $arg{'-w'} / 2 ) - ( $line_width / 2 );
545            
546             }
547            
548             # render the line
549 0 0 0       if (not ($arg{'-heightless'} and $arg{'-measure'}) ) { # skip adding text if just measuring
550 0           $text_object->translate( $xpos, $ypos );
551            
552 0           $endw = $text_object->text( join( ' ', @line ), -indent => $arg{'-indent'} );
553             }
554            
555             }
556 0           $ypos -= $arg{'-lead'};
557 0           $first_line = 0;
558            
559             }
560              
561 0 0         unshift( @paragraphs, join( ' ', @paragraph ) ) if scalar(@paragraph);
562            
563 0           return ( $endw, $ypos, join( "\n", @paragraphs ) )
564            
565             }
566              
567             1;
568             __END__