File Coverage

blib/lib/PDF/Kit.pm
Criterion Covered Total %
statement 27 316 8.5
branch 0 90 0.0
condition 0 59 0.0
subroutine 9 23 39.1
pod 13 13 100.0
total 49 501 9.7


line stmt bran cond sub pod time code
1             #!/
2             # --------------------------------------
3             #
4             # Title: PDF Kit
5             # Purpose: A collection of subroutines for PDF::API2.
6             #
7             # Name: PDF::Kit
8             # File: Kit.pm
9             # Created: June 2, 2009
10             #
11             # Copyright: Copyright 2009 by Shawn H. Corey. All rights reserved.
12             #
13             # This program is free software; you can redistribute it and/or modify
14             # it under the terms of the GNU General Public License as published by
15             # the Free Software Foundation, version 3 of the License, or
16             # (at your option) any later version.
17             #
18             # This program is distributed in the hope that it will be useful,
19             # but WITHOUT ANY WARRANTY; without even the implied warranty of
20             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21             # GNU General Public License for more details.
22             #
23             # You should have received a copy of the GNU General Public License
24             # along with this program; if not, write to the Free Software
25             # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26              
27             # --------------------------------------
28             # Package
29             package PDF::Kit;
30              
31             # --------------------------------------
32             # Pragmas
33              
34 1     1   19603 use strict;
  1         2  
  1         33  
35 1     1   4 use warnings;
  1         2  
  1         24  
36              
37             # --------------------------------------
38             # Version
39 1     1   690 use version; our $VERSION = qv(v1.0.5);
  1         2010  
  1         36  
40              
41             # --------------------------------------
42             # Exports
43 1     1   83 use base qw( Exporter );
  1         2  
  1         144  
44             our @EXPORT = qw(
45             in2pts
46             cm2pts
47             mm2pts
48             baselines
49             column_blocks
50             small_caps
51             flatten
52             as_text
53             format_paragraph
54             align_lines
55             justify_lines
56             print_lines
57             print_paragraph
58             );
59             our @EXPORT_OK = qw(
60             add_fonts
61             );
62             our %EXPORT_TAGS = (
63             all => [ @EXPORT, @EXPORT_OK ],
64             );
65              
66             # --------------------------------------
67             # Modules
68 1     1   5 use Carp;
  1         1  
  1         84  
69 1     1   339818 use Data::Dumper;
  1         15134  
  1         76  
70 1     1   850 use English qw( -no_match_vars ) ; # Avoids regex performance penalty
  1         7702  
  1         7  
71 1     1   541 use File::Basename;
  1         3  
  1         71  
72 1     1   906 use POSIX;
  1         9875  
  1         7  
73              
74             # --------------------------------------
75             # Configuration Parameters
76              
77             my $SPACE = "\x20";
78              
79             # Make Data::Dumper pretty
80             $Data::Dumper::Sortkeys = 1;
81             $Data::Dumper::Indent = 1;
82             $Data::Dumper::Maxdepth = 0;
83              
84             # --------------------------------------
85             # Variables
86              
87             # --------------------------------------
88             # Subroutines
89              
90             # --------------------------------------
91             # Name: in2pts
92             # Usage: @points = in2pts( @inches );
93             # $points = in2pts( @inches );
94             # Purpose: Convert inches to points.
95             # Parameters: @inches -- List of numbers in inches.
96             # Returns: $points -- Single value in points.
97             # @points -- List of values in points.
98             #
99             sub in2pts {
100 0     0 1   my @points = @_;
101              
102 0           $_ *= 72 for @points;
103 0 0         return wantarray ? @points : $points[0];
104             }
105              
106             # --------------------------------------
107             # Name: cm2pts
108             # Usage: @points = cm2pts( @cm );
109             # $points = cm2pts( @cm );
110             # Purpose: Convert centimetres to points.
111             # Parameters: @cm -- List of numbers in centimetres.
112             # Returns: $points -- Single value in points.
113             # @points -- List of values in points.
114             #
115             sub cm2pts {
116 0     0 1   my @points = @_;
117              
118 0           $_ *= 72/2.54 for @points;
119 0 0         return wantarray ? @points : $points[0];
120             }
121              
122             # --------------------------------------
123             # Name: mm2pts
124             # Usage: @points = mm2pts( @mm );
125             # $points = mm2pts( @mm );
126             # Purpose: Convert millimetres to points.
127             # Parameters: @mm -- List of numbers in millimetres.
128             # Returns: $points -- Single value in points.
129             # @points -- List of values in points.
130             #
131             sub mm2pts {
132 0     0 1   my @points = @_;
133              
134 0           $_ *= 72/25.4 for @points;
135 0 0         return wantarray ? @points : $points[0];
136             }
137              
138             # --------------------------------------
139             # Name: baselines
140             # Usage: @y_values = baselines( $height, $size; $spacing, $bottom );
141             # Purpose: Compute the Y values for the baselines.
142             # Parameters: $height -- The height of the box.
143             # $size -- Size of the font.
144             # $spacing -- Optional line spacing. Typical values are 1.0, 1.5, 2.0. Default is 1.0.
145             # $bottom -- Optional bottom margin; will be added to the Y values.
146             # Returns: @y_values -- List of Y values.
147             #
148             sub baselines {
149 0     0 1   my $height = shift @_;
150 0           my $size = shift @_;
151 0   0       my $spacing = shift @_ || 1;
152 0   0       my $bottom = shift @_ || 0;
153 0           my $leading = $size * $spacing;
154              
155 0           my @y_values = ();
156 0           my $y = $height - $size;
157 0           while( $y >= 0 ){
158 0           push @y_values, $y + $bottom;
159 0           $y -= $leading;
160             }
161              
162 0           return @y_values;
163             }
164              
165             # --------------------------------------
166             # Name: column_blocks
167             # Usage: \@blocks = column_blocks( \@block, $columns; $gap );
168             # Purpose: Calculate the block of the column that fit in the given block
169             # Parameters: \@block -- [ min_x, min_y, max_x, max_y ]
170             # $columns -- Number of columns
171             # $gap -- Gap between columns; optional, default is 0 (zero).
172             # Returns: \@blocks -- List of columns
173             #
174             sub column_blocks {
175 0     0 1   my $block = shift @_;
176 0           my $columns = shift @_;
177 0   0       my $gap = shift @_ || 0;
178 0           my $blocks = [];
179              
180 0           my $width = ( $block->[2] - $block->[0] - $gap * ( $columns - 1 ) ) / $columns;
181              
182 0           my $offset = $block->[0];
183 0           for my $i ( 1 .. $columns ){
184 0           push @$blocks, [ @$block ]; # make a copy
185 0           $blocks->[-1][0] = $offset;
186 0           $offset += $width;
187 0           $blocks->[-1][2] = $offset;
188 0           $offset += $gap;
189             }
190              
191 0           return $blocks;
192             }
193              
194             # --------------------------------------
195             # Name: small_caps
196             # Usage: \@mut = small_caps( $size, $factor, @text );
197             # Purpose: Convert the text to small caps.
198             # Parameters: $size -- Size of resulting text.
199             # $factor -- The relative size of the lowercase characters.
200             # Recommended factors are from 0.65 to 0.75.
201             # @text -- List of text items.
202             # Returns: \@mut -- L.
203             #
204             sub small_caps {
205 0     0 1   my $size = shift @_;
206 0           my $factor = shift @_;
207 0           my @text = @_;
208 0           my $mut = [];
209 0           my $lc_size = $size * $factor;
210 0           my $prev = '';
211              
212 0           for my $text ( @text ){
213 0           for my $c ( split( //, $text ) ){
214 0 0         if( isupper( $c ) ){
215 0 0         if( $prev ne 'u' ){
216 0           push @$mut, { -size=>$size };
217 0           $prev = 'u';
218             }
219 0           push @$mut, $c;
220             }else{
221 0 0         if( $prev ne 'l' ){
222 0           push @$mut, { -size=>$lc_size };
223 0           $prev = 'l';
224             }
225 0           push @$mut, uc( $c );
226             }
227             }
228             }
229              
230 0           return [ $mut ];
231             }
232              
233             # --------------------------------------
234             # Name: flatten
235             # Usage: \@flattened = flatten( @attributed_text );
236             # Purpose: Change nested attributed text into flatten attributed text.
237             # Parameters: @attributed_text -- nested attributed text
238             # Returns: \@flattened -- flatten attributed text
239             #
240             sub flatten {
241 0     0 1   my $list = [];
242 0           my @context = (
243             {
244             -opts => {},
245             -items => [ @_ ],
246             }
247             );
248              
249 0           while( @context ){
250 0           my %opts = %{ $context[-1]{-opts} };
  0            
251 0           my $items = $context[-1]{-items};
252 0           pop @context;
253              
254 0 0 0       if( @$list && ref( $list->[-1] ) ){
255 0           $list->[-1] = { %opts };
256             }else{
257 0           push @$list, { %opts };
258             }
259              
260 0           while( @$items ){
261 0           my $item = shift @$items;
262              
263 0 0         if( my $ref = ref( $item ) ){
264 0 0         if( $ref eq 'ARRAY' ){
    0          
265 0           push @context, {
266             -opts => { %opts },
267             -items => [ @$items ],
268             };
269 0           $items = $item;
270             }elsif( $ref eq 'HASH' ){
271 0           @opts{ keys %$item } = values %$item;
272 0 0 0       if( @$list && ref( $list->[-1] ) ){
273 0           $list->[-1] = { %opts };
274             }else{
275 0           push @$list, { %opts };
276             }
277             }
278             }else{
279 0 0 0       unless( @$list && ! ref( $list->[-1] ) ){
280 0           push @$list, '';
281             }
282 0 0         $list->[-1] .= $SPACE if $opts{-space_before};
283 0           $list->[-1] .= $item;
284 0 0         $list->[-1] .= $SPACE if $opts{-space_after};
285             }
286             }
287             }
288              
289 0   0       while( @$list && ref( $list->[-1] ) ){
290 0           pop @$list;
291             }
292              
293 0           my $flattened = [];
294 0           while( @$list ){
295 0           my %opts = %{ shift @$list };
  0            
296 0 0         delete $opts{-space_before} if exists $opts{-space_before};
297 0 0         delete $opts{-space_after} if exists $opts{-space_after};
298 0           push @$flattened, [ { %opts }, shift @$list ];
299             }
300              
301 0           return $flattened;
302             }
303              
304             # --------------------------------------
305             # Name: as_text
306             # Usage: $text = as_text( @mut );
307             # Purpose: Convert nested attributed text to regular text.
308             # Parameters: @mut -- List of L items.
309             # Returns: $text -- Its text.
310             #
311             sub as_text {
312 0     0 1   my $mut = flatten( @_ );
313 0           my $text = '';
314              
315 0           for my $item ( @$mut ){
316 0 0         $text .= $item unless ref( $item );
317             }
318              
319 0           return $text;
320             }
321              
322             # --------------------------------------
323             # Name: format_paragraph
324             # Usage: ( \@lines, \@mut ) = format_paragraph( \%paragraph_options, @mut );
325             #
326             # Purpose: Format the L to fit into a paragraph.
327             # Items may be text, sub-lists, or mark-up options.
328             # Text is broken into words via whitespace, C.
329             # Use the UTF character C<\x{a0}> for non-breaking spaces.
330             # Leading whitespace in the first text item is ignored.
331             #
332             # Text will formatted with a single space character between each word
333             # unless the C<-two_spaces> option is used.
334             #
335             # See POD for more details.
336             #
337             #
338             # Parameters: \%paragraph_options -- Mark-up options for the paragraph.
339             # See Mark-up Text in the POD for details.
340             # @mut -- Mark-up Text to format.
341             # Returns: \@lines -- A list of formatted lines.
342             # \@mut -- Leftover mut that did not fit into the paragraph.
343             # These may be used, as is, in another L call.
344             #
345             sub format_paragraph {
346              
347             # flatten makes things easier
348 0     0 1   my $mut = flatten( @_ );
349              
350             # remove leading spaces in first text item
351 0 0         $mut->[1] =~ s{ \A \s+ }{}msx if @$mut > 1;
352              
353             # save the indent
354 0           my $indent = 0;
355 0 0 0       if( @$mut && exists( $mut->[0][0]{-indent} ) ){
356 0   0       $indent = $mut->[0][0]{-indent} || 0;
357             }
358              
359 0           my $lines = [];
360 0           my $space_pending = 0;
361 0           my $space_width = 0;
362 0           my $trailing_width = 0;
363 0           my $trailing_spaces = '';
364 0           my $two_spaces = 0;
365              
366 0           my $add_p_end = 1;
367              
368             MUT_LOOP:
369 0           while( @$mut ){
370 0           my %opts = %{ $mut->[0][0] };
  0            
371 0           my $text = $mut->[0][1];
372 0           shift @$mut;
373              
374             # add a new segment
375 0 0         if( @$lines ){
376 0           push @{ $lines->[-1]{-segments} }, {
  0            
377             %opts,
378             -offset => $lines->[-1]{-segments}[-1]{-offset} + $lines->[-1]{-segments}[-1]{-length},
379             -length => 0,
380             -text => '',
381             };
382             }else{
383 0           push @{ $lines->[0]{-segments} }, {
  0            
384             %opts,
385             -offset => $indent,
386             -length => 0,
387             -text => '',
388             };
389             }
390              
391 0 0         if( $space_pending ){
392 0           $trailing_spaces = $SPACE;
393 0           $trailing_width = $space_width;
394 0 0         if( $two_spaces ){
395 0           $trailing_spaces .= $SPACE;
396 0           $trailing_width .= $space_width;
397             }
398             }
399              
400 0           $space_width = &{$opts{-compute_length}}( { %opts, -print=>0, }, $SPACE ); # must redo every time since might have changed from previous
  0            
401              
402             # process the text
403 0           my @text = split m{ ( \s+ ) }msx, $text;
404 0           while( @text ){
405 0           my $word = shift @text;
406              
407 0 0         if( $word =~ m{ \A \s }msx ){
408 0           $space_pending = 1;
409 0           next;
410             }
411              
412 0           my $word_length = &{$opts{-compute_length}}( { %opts, -print=>0, }, $word );
  0            
413 0           my $extended_length = $word_length;
414             # a word at end of text may be joined to next
415 0 0         unless( @text ){
416 0           for my $item ( @$mut ){
417 0 0         last if $item->[1] =~ m{ \A \s }msx;
418 0 0         if( $item->[1] =~ m{ \A (\S+) (?=\s) }msx ){
419 0           my $look_ahead = $1;
420 0           $extended_length += &{$opts{-compute_length}}( { %opts, -print=>0, }, $look_ahead );
  0            
421 0           last;
422             }
423 0           $extended_length += &{$opts{-compute_length}}( { %opts, -print=>0, }, $item->[1] );
  0            
424             }
425             }
426 0           my $right = $lines->[-1]{-segments}[-1]{-offset} + $lines->[-1]{-segments}[-1]{-length} + $extended_length;
427 0           my $new_line = 0;
428              
429 0 0         if( $trailing_width ){
    0          
430 0 0         if( $trailing_width + $right > $lines->[-1]{-segments}[-1]{-width} ){
431 0           $new_line = 1;
432             }
433             }elsif( $space_pending ){
434 0           my $spw = $space_width;
435 0 0         $spw += $space_width if $two_spaces;
436 0 0         if( $spw + $right > $lines->[-1]{-segments}[-1]{-width} ){
437 0           $new_line = 1;
438             }
439             }
440              
441             # add a new line
442 0 0         if( $new_line ){
443 0           $lines->[-1]{-width} = $lines->[-1]{-segments}[-1]{-width};
444 0 0 0       if( exists( $opts{-max_lines} ) && @$lines >= $opts{-max_lines} ){
445 0           delete $opts{-offset};
446 0           delete $opts{-length};
447 0           delete $opts{-text};
448 0           $opts{-indent} = 0; # everything left over is still part of this paragraph, so it's indent must be zero.
449 0           unshift @$mut, [ { %opts }, join( '', $word, @text ) ];
450 0           $add_p_end = 0;
451 0           last MUT_LOOP;
452             }
453 0           push @$lines, {
454             -length => 0,
455             -segments =>[{
456             %opts,
457             -offset => 0,
458             -length => 0,
459             -text => '',
460             }],
461             };
462 0           $space_pending = 0;
463 0           $trailing_width = 0;
464             }
465              
466 0 0         if( $trailing_width ){
    0          
467 0           $lines->[-1]{-length} += $trailing_width;
468 0           $lines->[-1]{-segments}[-1]{-offset} += $trailing_width;
469 0           $lines->[-1]{-segments}[-2]{-length} += $trailing_width;
470 0           $lines->[-1]{-segments}[-2]{-text} .= $trailing_spaces;
471 0           $trailing_width = 0;
472             }elsif( $space_pending ){
473 0           $lines->[-1]{-length} += $space_width;
474 0           $lines->[-1]{-segments}[-1]{-length} += $space_width;
475 0           $lines->[-1]{-segments}[-1]{-text} .= $SPACE;
476 0 0         if( $two_spaces ){
477 0           $lines->[-1]{-length} += $space_width;
478 0           $lines->[-1]{-segments}[-1]{-length} += $space_width;
479 0           $lines->[-1]{-segments}[-1]{-text} .= $SPACE;
480             }
481             }
482              
483 0           $lines->[-1]{-length} += $word_length;
484 0           $lines->[-1]{-segments}[-1]{-length} += $word_length;
485 0           $lines->[-1]{-segments}[-1]{-text} .= $word;
486              
487 0           $space_pending = 0;
488 0           $trailing_width = 0;
489              
490             # check for two_spaces after word
491 0           $two_spaces = 0;
492 0 0         if( $opts{-two_spaces} ){
493 0   0       $two_spaces = $word =~ $opts{-two_spaces} || 0;
494             }
495             }
496             }
497 0           $lines->[-1]{-width} = $lines->[-1]{-segments}[-1]{-width};
498 0 0 0       if( @$lines && $add_p_end ){
499 0           $lines->[-1]{-last_line} = 1;
500             }
501              
502 0           return ( $lines, $mut );
503             }
504              
505             # --------------------------------------
506             # Name: align_lines
507             # Usage: align_lines( $alignment, $lines );
508             # Purpose: Change the offsets in the lines outputted by L to align the paragraph.
509             # Parameters: $alignment -- A value of 0.0 will left align;
510             # a value of 0.5 will center align;
511             # a value of 1.0 will right align.
512             # Other values will create weird, special effects.
513             # $lines -- Output from L.
514             # Returns: none
515             #
516             sub align_lines {
517 0     0 1   my $alignment = shift @_;
518 0           my $lines = shift @_;
519              
520             # first line may have indent, so do it separate
521 0   0       my $indent = $lines->[0]{-segments}[0]{-indent} || 0;
522 0           my $gap = $lines->[0]{-width} - $lines->[0]{-length};
523 0           my $offset = $gap * $alignment;
524 0           for my $segment ( @{ $lines->[0]{-segments} } ){
  0            
525 0           $segment->{-offset} = $offset;
526 0           $offset += $segment->{-length};
527             }
528              
529             # do the rest
530 0           for my $line ( @{ $lines }[ 1 .. $#$lines ] ){
  0            
531 0           $gap = $line->{-width} - $line->{-length};
532 0           $offset = $gap * $alignment;
533 0           for my $segment ( @{ $line->{-segments} } ){
  0            
534 0           $segment->{-offset} = $offset;
535 0           $offset += $segment->{-length};
536             }
537             }
538              
539 0           return;
540             }
541              
542             # --------------------------------------
543             # Name: _justify_line
544             # Usage: _justify_line( $word_spacing_weight, $character_spacing_weight, $horizontal_scaling_weight, $line; $indent );
545             # Purpose: Calculate the amount to adjust the spacing and scaling to justify the line.
546             # Parameters: $word_spacing_weight -- How much attributed to spaces between words.
547             # $character_spacing_weight -- How much attributed to spaces between characters.
548             # $horizontal_scaling_weight -- How much attributed to scaling the glyphs horizontally.
549             # $line -- The line to adjust.
550             # $indent -- Possible indentation of the line.
551             # Returns: none
552             #
553             sub _justify_line {
554 0     0     my $word_spacing_weight = shift @_;
555 0           my $character_spacing_weight = shift @_;
556 0           my $horizontal_scaling_weight = shift @_;
557 0           my $line = shift @_;
558 0   0       my $indent = shift @_ || 0;
559              
560 0           my $gap = $line->{-width} - $line->{-length} - $indent;
561              
562 0           my $char = 0;
563 0           my $sp = 0;
564 0           for my $segment ( @{ $line->{-segments} } ){
  0            
565 0           $char += length( $segment->{-text} );
566 0           $sp += $segment->{-text} =~ tr/\x20/\x20/;
567             }
568              
569             # calculate character spacing
570 0 0         if( $sp <= 0 ){
571             # no spaces if narrow width or non-breaking spaces
572 0           $word_spacing_weight = 0;
573 0           $line->{-wordspace} = 0;
574              
575 0           my $sum = $character_spacing_weight + $horizontal_scaling_weight;
576 0 0         if( $sum == 0 ){
577 0           $character_spacing_weight = 0.5;
578 0           $horizontal_scaling_weight = 0.5;
579             }else{
580 0           $character_spacing_weight /= $sum;
581 0           $horizontal_scaling_weight /= $sum;
582             }
583             }else{
584 0           $line->{-wordspace} = $gap * $word_spacing_weight / $sp;
585             }
586              
587             # calculate word spacing
588 0           $line->{-charspace} = $gap * $character_spacing_weight / $char;
589              
590             # calculate horizontal scaling
591 0           $line->{-hspace} = ( $line->{-width} - $indent ) / ( $line->{-length} + $gap * ( 1 - $horizontal_scaling_weight ) ) * 100;
592              
593             # calculate justified offsets
594 0           my $joffset = $indent;
595 0           for my $segment ( @{ $line->{-segments} } ){
  0            
596 0           $segment->{-joffset} = $joffset;
597 0           $segment->{-wordspace} = $line->{-wordspace};
598 0           $segment->{-charspace} = $line->{-charspace};
599 0           $segment->{-hspace} = $line->{-hspace};
600              
601 0           $char = length( $segment->{-text} );
602 0           $sp = $segment->{-text} =~ tr/\x20/\x20/;
603 0           $joffset += $line->{-wordspace} * $sp
604             + $line->{-charspace} * $char
605             + $line->{-hspace} * $segment->{-length} / 100;
606             }
607              
608 0           return;
609             }
610              
611             # --------------------------------------
612             # Name: justify_lines
613             # Usage: justify_lines( $word_spacing_weight, $character_spacing_weight, $horizontal_scaling_weight, $lines );
614             # Purpose: Modify the output of format_paragraph() so that the lines are fully justified.
615             # See the POD for details.
616             # Parameters: $word_spacing_weight -- The weight of the adjustment for word spacing.
617             # $character_spacing_weight -- The weight of the adjustment for character spacing.
618             # $horizontal_scaling_weight -- The weight of the adjustment for horizontal scaling.
619             # $lines -- The output of L.
620             # Returns: none
621             #
622             sub justify_lines {
623 0     0 1   my $word_spacing_weight = abs( shift @_ );
624 0           my $character_spacing_weight = abs( shift @_ );
625 0           my $horizontal_scaling_weight = abs( shift @_ );
626 0           my $lines = shift @_;
627              
628             # normalize the weights
629 0           my $sum = $word_spacing_weight + $character_spacing_weight + $horizontal_scaling_weight;
630 0           $word_spacing_weight /= $sum;
631 0           $character_spacing_weight /= $sum;
632 0           $horizontal_scaling_weight /= $sum;
633              
634             # first line may have an indent, so do it separately
635 0           _justify_line( $word_spacing_weight, $character_spacing_weight, $horizontal_scaling_weight, $lines->[0], $lines->[0]{-segments}[0]{-indent} );
636              
637 0           for my $line ( @{ $lines }[ 1 .. $#$lines ] ){
  0            
638             # don't do the last line.
639 0 0         next if $line->{-last_line};
640              
641 0           _justify_line( $word_spacing_weight, $character_spacing_weight, $horizontal_scaling_weight, $line );
642             }
643              
644 0           return;
645             }
646              
647             # --------------------------------------
648             # Name: print_lines
649             # Usage: ( \@y_values, $lines ) = print_lines( $left, \@y_values, $lines );
650             # Purpose: Print the lines created by L.
651             # Parameters: $left -- Left offset for the lines.
652             # \@y_values -- A list of baselines for the lines.
653             # $lines -- Formatted lines.
654             # Returns: \@y_values -- Left over baselines.
655             # $lines -- Left over lines.
656             #
657             sub print_lines {
658 0     0 1   my $left = shift @_;
659 0           my $y_values = shift @_;
660 0           my $lines = shift @_;
661              
662 0   0       while( @$y_values && @$lines ){
663 0           my $line = shift @$lines;
664 0           my $y = shift @$y_values;
665 0           for my $segment ( @{ $line->{-segments} } ){
  0            
666 0           my %opts = %$segment;
667 0           $opts{-print} = 1;
668              
669 0           my $x = $segment->{-offset};
670 0 0         if( exists( $segment->{-joffset} ) ){
671 0           $x = $segment->{-joffset};
672             }
673              
674 0           $opts{-x} = $x + $left;
675 0           $opts{-y} = $y;
676 0           &{ $segment->{-print_text} }( \%opts, $segment->{-text} );
  0            
677             }
678             }
679              
680 0           return ( $y_values, $lines );
681             }
682              
683             # --------------------------------------
684             # Name: print_paragraph
685             # Usage: ( $bottom, \@mut ) = print_paragraph( \%print_options, \%paragraph_options, @mut );
686             # Purpose: Print the paragraph.
687             # This subroutine uses L to format the paragraph
688             # and then determines where each segment of text should go.
689             # It uses the application specified print routine to print it.
690             #
691             # See the POD for details.
692             #
693             # Parameters: \%print_options -- See the POD for details.
694             # \%paragraph_options -- Mark-up Text for the paragraph.
695             # Same as format_paragraph().
696             # @mut -- A list of mut to print with L.
697             # Returns: $bottom -- The bottom of the paragraph.
698             # Can be used as the top of the next.
699             # \@mut -- A list of mut that did not fit into the paragraph.
700             #
701             sub print_paragraph {
702 0     0 1   my %opts = ();
703 0   0       while( @_ && ref( $_[0] ) eq 'HASH' ){
704 0           my %hash = %{ shift @_ };
  0            
705 0           @opts{keys %hash} = values %hash;
706             }
707              
708 0           my $height = $opts{-block}[3] - $opts{-block}[1];
709 0   0       my @y_values = baselines( $height, $opts{-size}, ( $opts{-spacing} || 1 ), $opts{-block}[1] );
710 0   0       my $bottom = $y_values[-1] - $opts{-size} * (( $opts{-spacing} || 1 ) - 1 );
711              
712 0           my $width = $opts{-block}[2] - $opts{-block}[0];
713 0           my ( $lines, $mut ) = format_paragraph( { %opts, -width=>$width, -max_lines=>scalar( @y_values ) }, @_ );
714              
715 0 0 0       if( $opts{-justify_word} || $opts{-justify_char} || $opts{-justify_scale} ){
    0 0        
716 0   0       justify_lines( $opts{-justify_word} || 0, $opts{-justify_char} || 0, $opts{-justify_scale} || 0, $lines );
      0        
      0        
717             }elsif( $opts{-alignment} ){
718 0           align_lines( $opts{-alignment}, $lines );
719             }
720              
721 0           my ( $y_values, undef ) = print_lines( $opts{-block}[0], \@y_values, $lines );
722 0 0         if( @$y_values ){
723 0   0       $bottom = $y_values->[0] - $opts{-size} * (( $opts{-spacing} || 1 ) - 1 );
724             }
725              
726 0           return $bottom, $mut;
727             }
728              
729             1;
730             __DATA__