File Coverage

blib/lib/PDF/API2/Content.pm
Criterion Covered Total %
statement 774 1035 74.7
branch 209 372 56.1
condition 56 160 35.0
subroutine 128 146 87.6
pod 79 101 78.2
total 1246 1814 68.6


line stmt bran cond sub pod time code
1             package PDF::API2::Content;
2              
3 39     39   300 use base 'PDF::API2::Basic::PDF::Dict';
  39         93  
  39         5643  
4              
5 39     39   279 use strict;
  39         104  
  39         1024  
6 39     39   182 use warnings;
  39         77  
  39         2703  
7              
8             our $VERSION = '2.048'; # VERSION
9              
10 39     39   297 use Carp;
  39         81  
  39         2380  
11 39     39   230 use Compress::Zlib ();
  39         79  
  39         889  
12 39     39   201 use Encode;
  39         92  
  39         3705  
13 39     39   234 use Math::Trig;
  39         77  
  39         8089  
14 39     39   18316 use PDF::API2::Matrix;
  39         127  
  39         1450  
15              
16 39     39   283 use PDF::API2::Basic::PDF::Utils;
  39         81  
  39         3261  
17 39     39   239 use PDF::API2::Util;
  39         126  
  39         579439  
18              
19             =head1 NAME
20              
21             PDF::API2::Content - Methods for adding graphics and text to a PDF
22              
23             =head1 SYNOPSIS
24              
25             # Start with a PDF page (new or opened)
26             my $pdf = PDF::API2->new();
27             my $page = $pdf->page();
28              
29             # Add a new content object
30             my $content = $page->graphics();
31             my $content = $page->text();
32              
33             # Then call the methods below to add graphics and text to the page.
34              
35             =cut
36              
37             sub new {
38 122     122 1 276 my $class = shift();
39 122         573 my $self = $class->SUPER::new();
40              
41 122         342 $self->{' stream'} = '';
42 122         300 $self->{' poststream'} = '';
43 122         289 $self->{' font'} = undef;
44 122         292 $self->{' fontset'} = 0;
45 122         308 $self->{' fontsize'} = 0;
46 122         397 $self->{' charspace'} = 0;
47 122         311 $self->{' hscale'} = 100;
48 122         291 $self->{' wordspace'} = 0;
49 122         289 $self->{' leading'} = 0;
50 122         328 $self->{' rise'} = 0;
51 122         251 $self->{' render'} = 0;
52 122         429 $self->{' matrix'} = [1, 0, 0, 1, 0, 0];
53 122         429 $self->{' textmatrix'} = [1, 0, 0, 1, 0, 0];
54 122         326 $self->{' textlinematrix'} = [0, 0];
55 122         306 $self->{' textlinestart'} = 0;
56 122         375 $self->{' fillcolor'} = [0];
57 122         390 $self->{' strokecolor'} = [0];
58 122         333 $self->{' translate'} = [0, 0];
59 122         350 $self->{' scale'} = [1, 1];
60 122         316 $self->{' skew'} = [0, 0];
61 122         477 $self->{' rotate'} = 0;
62 122         337 $self->{' apiistext'} = 0;
63              
64 122         325 return $self;
65             }
66              
67             sub outobjdeep {
68 114     114 1 247 my $self = shift();
69 114         469 $self->textend();
70 114 50 66     392 if ($self->{'-docompress'} and $self->{'Filter'}) {
71 6         46 $self->{' stream'} = Compress::Zlib::compress($self->{' stream'});
72 6         1874 $self->{' nofilt'} = 1;
73 6         18 delete $self->{'-docompress'};
74             }
75 114         436 return $self->SUPER::outobjdeep(@_);
76             }
77              
78             =head1 COORDINATE TRANSFORMATIONS
79              
80             The methods in this section change the coordinate system for the current content
81             object relative to the rest of the document.
82              
83             Changes to the coordinate system only affect subsequent paths or text.
84              
85             A call to any of the methods in this section resets the coordinate system before
86             applying its changes, unless the C option is set.
87              
88             =head2 translate
89              
90             $content = $content->translate($x, $y);
91              
92             Moves the origin along the x and y axes.
93              
94             =cut
95              
96             sub _translate {
97 12     12   31 my ($x, $y) = @_;
98 12         42 return (1, 0, 0, 1, $x, $y);
99             }
100              
101             sub translate {
102 2     2 1 11 my ($self, $x, $y) = @_;
103 2         16 $self->transform(translate => [$x, $y]);
104             }
105              
106             =head2 rotate
107              
108             $content = $content->rotate($degrees);
109              
110             Rotates the coordinate system counter-clockwise.
111              
112             Use a negative argument to rotate clockwise.
113              
114             =cut
115              
116             sub _rotate {
117 9     9   36 my $a = deg2rad(shift());
118 9         141 return (cos($a), sin($a), -sin($a), cos($a), 0, 0);
119             }
120              
121             sub rotate {
122 1     1 1 9 my ($self, $a) = @_;
123 1         5 $self->transform(rotate => $a);
124             }
125              
126             =head2 scale
127              
128             $content = $content->scale($x, $y);
129              
130             Scales (stretches) the coordinate systems along the x and y axes. A value of 1
131             for either C<$x> or C<$y> represents 100% scale (i.e. no change).
132              
133             =cut
134              
135             sub _scale {
136 9     9   22 my ($x, $y) = @_;
137 9         31 return ($x, 0, 0, $y, 0, 0);
138             }
139              
140             sub scale {
141 1     1 1 9 my ($self, $sx, $sy) = @_;
142 1         7 $self->transform(scale => [$sx, $sy]);
143             }
144              
145             =head2 skew
146              
147             $content = $content->skew($a, $b);
148              
149             Skews the coordinate system by C<$a> degrees (counter-clockwise) from the x axis
150             and C<$b> degrees (clockwise) from the y axis.
151              
152             =cut
153              
154             sub _skew {
155 9     9   40 my $a = deg2rad(shift());
156 9         132 my $b = deg2rad(shift());
157 9         100 return (1, tan($a), tan($b), 1, 0, 0);
158             }
159              
160             sub skew {
161 1     1 1 9 my ($self, $a, $b) = @_;
162 1         7 $self->transform(skew => [$a, $b]);
163             }
164              
165             =head2 transform
166              
167             $content = $content->transform(
168             translate => [$x, $y],
169             rotate => $degrees,
170             scale => [$x, $y],
171             skew => [$a, $b],
172             repeat => $boolean,
173             );
174              
175             Performs multiple coordinate transformations, in the order recommended by the
176             PDF specification (translate, rotate, scale, then skew). Omitted options will
177             be unchanged.
178              
179             If C is true and if this is not the first call to a transformation
180             method, the previous transformation will be performed again, modified by any
181             other provided arguments.
182              
183             =cut
184              
185             sub _to_matrix {
186 39     39   459 my @array = @_;
187 39         223 return PDF::API2::Matrix->new([$array[0], $array[1], 0],
188             [$array[2], $array[3], 0],
189             [$array[4], $array[5], 1]);
190             }
191              
192             sub _transform {
193 15     15   50 my %opts = @_;
194 15         221 my $m = PDF::API2::Matrix->new([1, 0, 0], [0, 1, 0], [0, 0, 1]);
195              
196             # Undocumented; only used by textpos()
197 15 50       66 if (defined $opts{'-matrix'}) {
198 0         0 $m = $m->multiply(_to_matrix(@{$opts{'-matrix'}}));
  0         0  
199             }
200              
201             # Note that the transformations are applied in reverse order. See PDF 1.7
202             # specification section 8.3.4: Transformation Matrices.
203 15 100       48 if (defined $opts{'skew'}) {
204 9         16 $m = $m->multiply(_to_matrix(_skew(@{$opts{'skew'}})));
  9         30  
205             }
206 15 100       83 if (defined $opts{'scale'}) {
207 9         22 $m = $m->multiply(_to_matrix(_scale(@{$opts{'scale'}})));
  9         30  
208             }
209 15 100       77 if (defined $opts{'rotate'}) {
210 9         30 $m = $m->multiply(_to_matrix(_rotate($opts{'rotate'})));
211             }
212 15 100       74 if (defined $opts{'translate'}) {
213 12         26 $m = $m->multiply(_to_matrix(_translate(@{$opts{'translate'}})));
  12         69  
214             }
215              
216             # Undocumented; only used by textpos()
217 15 50       103 if ($opts{'-point'}) {
218             my $mp = PDF::API2::Matrix->new([$opts{'-point'}->[0],
219 0         0 $opts{'-point'}->[1], 1]);
220 0         0 $mp = $mp->multiply($m);
221 0         0 return ($mp->[0][0], $mp->[0][1]);
222             }
223              
224             return (
225 15         137 $m->[0][0], $m->[0][1],
226             $m->[1][0], $m->[1][1],
227             $m->[2][0], $m->[2][1]
228             );
229             }
230              
231             # Transformations are described in the PDF 1.7 specification, section 8.3.3:
232             # Common Transformations.
233             sub transform {
234 16     16 1 5317 my ($self, %options) = @_;
235 16 50       59 return $self->transform_rel(%options) if $options{'repeat'};
236              
237             # Deprecated (renamed to 'repeat' to avoid confusion)
238 16 100       51 return $self->transform_rel(%options) if $options{'relative'};
239              
240             # Deprecated options (remove hyphens)
241 15         51 foreach my $option (qw(translate rotate scale skew)) {
242 60 100       174 if (exists $options{'-' . $option}) {
243 14   33     88 $options{$option} //= delete $options{'-' . $option};
244             }
245             }
246              
247             # Apply the transformations
248 15         69 $self->matrix(_transform(%options));
249              
250             # Store the transformations for lookup or future relative transformations
251 15   100     84 $self->{' translate'} = $options{'translate'} // [0, 0];
252 15   100     61 $self->{' rotate'} = $options{'rotate'} // 0;
253 15   100     86 $self->{' scale'} = $options{'scale'} // [1, 1];
254 15   100     71 $self->{' skew'} = $options{'skew'} // [0, 0];
255              
256 15         59 return $self;
257             }
258              
259             sub transform_rel {
260 2     2 1 19 my ($self, %options) = @_;
261              
262             # Deprecated options (remove hyphens)
263 2         7 foreach my $option (qw(translate rotate scale skew)) {
264 8 100       25 if (exists $options{'-' . $option}) {
265 4   33     21 $options{$option} //= delete $options{'-' . $option};
266             }
267             }
268              
269 2 50       5 my ($sa1, $sb1) = @{$options{'skew'} ? $options{'skew'} : [0, 0]};
  2         12  
270 2         6 my ($sa0, $sb0) = @{$self->{' skew'}};
  2         7  
271              
272 2 50       5 my ($sx1, $sy1) = @{$options{'scale'} ? $options{'scale'} : [1, 1]};
  2         23  
273 2         5 my ($sx0, $sy0) = @{$self->{' scale'}};
  2         6  
274              
275 2   50     8 my $r1 = $options{'rotate'} // 0;
276 2         35 my $r0 = $self->{' rotate'};
277              
278 2 50       5 my ($tx1, $ty1) = @{$options{'translate'} ? $options{'translate'} : [0, 0]};
  2         9  
279 2         4 my ($tx0, $ty0) = @{$self->{' translate'}};
  2         12  
280              
281 2         21 $self->transform(
282             skew => [$sa0 + $sa1, $sb0 + $sb1],
283             scale => [$sx0 * $sx1, $sy0 * $sy1],
284             rotate => $r0 + $r1,
285             translate => [$tx0 + $tx1, $ty0 + $ty1],
286             );
287              
288 2         11 return $self;
289             }
290              
291             =head2 matrix
292              
293             $graphics = $graphics->matrix($a, $b, $c, $d, $e, $f);
294              
295             ($a, $b, $c, $d, $e, $f) = $text->matrix($a, $b, $c, $d, $e, $f);
296              
297             Sets the current transformation matrix manually. Unless you have a particular
298             need to enter transformations manually, you should use the C method
299             instead.
300              
301             The return value differs based on whether the caller is a graphics content
302             object or a text content object.
303              
304             =cut
305              
306             sub _matrix_text {
307 3     3   9 my ($a, $b, $c, $d, $e, $f) = @_;
308 3         18 return (floats($a, $b, $c, $d, $e, $f), 'Tm');
309             }
310              
311             sub _matrix_gfx {
312 23     23   70 my ($a, $b, $c, $d, $e, $f) = @_;
313 23         3434 return (floats($a, $b, $c, $d, $e, $f), 'cm');
314             }
315              
316             sub matrix {
317 26     26 1 69 my $self = shift();
318 26 50       101 if (scalar(@_)) {
319 26         76 my ($a, $b, $c, $d, $e, $f) = @_;
320 26 100       113 if ($self->_in_text_object()) {
321 3         14 $self->add(_matrix_text($a, $b, $c, $d, $e, $f));
322 3         15 $self->{' textmatrix'} = [$a, $b, $c, $d, $e, $f];
323 3         12 $self->{' textlinematrix'} = [0, 0];
324             }
325             else {
326 23         115 $self->add(_matrix_gfx($a, $b, $c, $d, $e, $f));
327             }
328             }
329              
330 26 100       94 if ($self->_in_text_object()) {
331 3         6 return @{$self->{' textmatrix'}};
  3         9  
332             }
333             else {
334 23         68 return $self;
335             }
336             }
337              
338             sub matrix_update {
339 70     70 0 146 my ($self, $tx, $ty) = @_;
340 70         147 $self->{' textlinematrix'}->[0] += $tx;
341 70         108 $self->{' textlinematrix'}->[1] += $ty;
342 70         100 return $self;
343             }
344              
345             =head1 GRAPHICS STATE
346              
347             =head2 save
348              
349             $content = $content->save();
350              
351             Saves the current graphics state on a stack.
352              
353             =cut
354              
355             sub _save {
356 11     11   48 return 'q';
357             }
358              
359             sub save {
360 11     11 1 52 my $self = shift;
361 11 50       69 if ($self->_in_text_object()) {
362 0         0 carp 'Calling save from a text content object has no effect';
363 0         0 return;
364             }
365              
366 11         40 $self->add(_save());
367              
368 11         21 return $self;
369             }
370              
371             =head2 restore
372              
373             $content = $content->restore();
374              
375             Restores the most recently saved graphics state, removing it from the stack.
376              
377             =cut
378              
379             sub _restore {
380 11     11   38 return 'Q';
381             }
382              
383             sub restore {
384 11     11 1 30 my $self = shift;
385 11 50       37 if ($self->_in_text_object()) {
386 0         0 carp 'Calling restore from a text content object has no effect';
387 0         0 return;
388             }
389              
390 11         49 $self->add(_restore());
391              
392 11         20 return $self;
393             }
394              
395             =head2 line_width
396              
397             $content = $content->line_width($points);
398              
399             Sets the width of the stroke in points.
400              
401             =cut
402              
403             sub _linewidth {
404 88     88   157 my $linewidth = shift();
405 88         336 return ($linewidth, 'w');
406             }
407              
408             # Deprecated (renamed)
409 87     87 1 230 sub linewidth { return line_width(@_) }
410              
411             sub line_width {
412 88     88 1 191 my ($self, $line_width) = @_;
413              
414 88         221 $self->add(_linewidth($line_width));
415              
416 88         236 return $self;
417             }
418              
419             =head2 line_cap
420              
421             $content = $content->line_cap($style);
422              
423             Sets the shape that will be used at the ends of open subpaths (and dashes, if
424             any) when they are stroked.
425              
426             =over
427              
428             =item * "butt" or 0 = Butt Cap, default
429              
430             The stroke ends at the end of the path, with no projection.
431              
432             =item * "round" or 1 = Round Cap)
433              
434             An arc is drawn around the end of the path with a diameter equal to the line
435             width, and is filled in.
436              
437             =item * "square" or 2 = Projecting Square Cap
438              
439             The stroke continues past the end of the path for half the line width.
440              
441             =back
442              
443             =cut
444              
445             sub _linecap {
446 2     2   4 my $linecap = shift();
447 2         13 return ($linecap, 'J');
448             }
449              
450             # Deprecated (renamed)
451 1     1 1 10 sub linecap { return line_cap(@_) }
452              
453             sub line_cap {
454 2     2 1 7 my $self = shift();
455              
456 2 50 66     12 if ($self->{' graphics'} and not @_) {
457 0         0 croak "Missing argument to line_cap";
458             }
459              
460 2   50     6 my $style = shift() // 0;
461 2 50       7 $style = 0 if $style eq 'butt';
462 2 100       7 $style = 1 if $style eq 'round';
463 2 50       6 $style = 2 if $style eq 'square';
464              
465 2 50 33     10 unless ($style >= 0 and $style <= 2) {
466 0 0       0 if ($self->{' graphics'}) {
467 0         0 croak "Unknown line cap style \"$style\"";
468             }
469             else {
470 0         0 confess "Unknown line cap style \"$style\"";
471             }
472             }
473              
474 2         8 $self->add(_linecap($style));
475              
476 2         4 return $self;
477             }
478              
479             =head2 line_join
480              
481             $content = $content->line_join($style);
482              
483             Sets the style of join to be used at corners of a path.
484              
485             =over
486              
487             =item * "miter" or 0 = Miter Join, default
488              
489             The outer edges of the stroke extend until they meet, up to the limit specified
490             below. If the limit would be surpassed, a bevel join is used instead.
491              
492             =item * "round" or 1 = Round Join
493              
494             A circle with a diameter equal to the linewidth is drawn around the corner
495             point, producing a rounded corner.
496              
497             =item * "bevel" or 2 = Bevel Join
498              
499             A triangle is drawn to fill in the notch between the two strokes.
500              
501             =back
502              
503             =cut
504              
505             sub _linejoin {
506 2     2   5 my $linejoin = shift();
507 2         8 return ($linejoin, 'j');
508             }
509              
510             # Deprecated (renamed)
511 1     1 1 10 sub linejoin { return line_join(@_) }
512              
513             sub line_join {
514 2     2 1 7 my $self = shift();
515              
516 2 50 66     10 if ($self->{' graphics'} and not @_) {
517 0         0 croak "Missing argument to line_join";
518             }
519              
520 2   50     6 my $style = shift() // 0;
521 2 50       7 $style = 0 if $style eq 'miter';
522 2 50       6 $style = 1 if $style eq 'round';
523 2 100       20 $style = 2 if $style eq 'bevel';
524              
525 2 50 33     12 unless ($style >= 0 and $style <= 2) {
526 0 0       0 if ($self->{' graphics'}) {
527 0         0 croak "Unknown line join style \"$style\"";
528             }
529             else {
530 0         0 confess "Unknown line join style \"$style\"";
531             }
532             }
533              
534 2         8 $self->add(_linejoin($style));
535              
536 2         4 return $self;
537             }
538              
539             =head2 miter_limit
540              
541             $content = $content->miter_limit($ratio);
542              
543             Sets the miter limit when the line join style is a miter join.
544              
545             The C<$ratio> is the maximum length of the miter (inner to outer corner) divided
546             by the line width. Any miter above this ratio will be converted to a bevel
547             join. The practical effect is that lines meeting at shallow angles are chopped
548             off instead of producing long pointed corners.
549              
550             There is no documented default miter limit.
551              
552             =cut
553              
554             sub _miterlimit {
555 3     3   6 my $limit = shift();
556 3         14 return ($limit, 'M');
557             }
558              
559             # Deprecated; miterlimit was originally named incorrectly
560 1     1 1 10 sub meterlimit { return miter_limit(@_) }
561              
562             # Deprecated (renamed)
563 1     1 1 10 sub miterlimit { return miter_limit(@_) }
564              
565             sub miter_limit {
566 3     3 1 13 my ($self, $limit) = @_;
567              
568 3         12 $self->add(_miterlimit($limit));
569              
570 3         8 return $self;
571             }
572              
573             =head2 line_dash_pattern
574              
575             # Solid line
576             $content = $content->line_dash_pattern();
577              
578             # Equal length lines and gaps
579             $content = $content->line_dash_pattern($length);
580              
581             # Specified line and gap lengths
582             $content = $content->line_dash_pattern($line1, $gap1, $line2, $gap2, ...);
583              
584             # Offset the starting point
585             $content = $content->line_dash_pattern(
586             pattern => [$line1, $gap1, $line2, $gap2, ...],
587             offset => $points,
588             );
589              
590             Sets the line dash pattern.
591              
592             If called without any arguments, a solid line will be drawn.
593              
594             If called with one argument, the dashes and gaps will have equal lengths.
595              
596             If called with two or more arguments, the arguments represent alternating dash
597             and gap lengths.
598              
599             If called with a hash of arguments, a dash phase may be set, which specifies the
600             distance into the pattern at which to start the dash.
601              
602             =cut
603              
604             sub _linedash {
605 10     10   25 my @options = @_;
606              
607 10 100       36 unless (@options) {
608 7         34 return ('[', ']', '0', 'd');
609             }
610              
611 3 50       14 if ($options[0] =~ /^\d/) {
612 3         15 return ('[', floats(@options), '] 0 d');
613             }
614              
615 0         0 my %options = @options;
616              
617             # Deprecated option names
618 0 0       0 if ($options{'-pattern'}) {
619 0   0     0 $options{'pattern'} //= delete $options{'-pattern'};
620             }
621 0 0       0 if ($options{'-shift'}) {
622 0   0     0 $options{'offset'} //= delete $options{'-shift'};
623             }
624              
625             # Deprecated: the -full and -clear options will be removed in a future
626             # release
627 0 0 0     0 if (exists $options{'-full'} or exists $options{'-clear'}) {
628 0   0     0 $options{'pattern'} //= [$options{'-full'} // 0, $options{'-clear'} // 0];
      0        
      0        
629             }
630              
631 0         0 return ('[', floats(@{$options{'pattern'}}), ']',
632 0   0     0 ($options{'offset'} || 0), 'd');
633             }
634              
635             # Deprecated (renamed)
636 7     7 1 30 sub linedash { return line_dash_pattern(@_) }
637              
638             sub line_dash_pattern {
639 10     10 1 36 my ($self, @a) = @_;
640              
641 10         32 $self->add(_linedash(@a));
642              
643 10         28 return $self;
644             }
645              
646             =head2 flatness_tolerance
647              
648             $content = $content->flatness_tolerance($tolerance);
649              
650             Sets the maximum distance in device pixels between the mathematically correct
651             path for a curve and an approximation constructed from straight line segments.
652              
653             C<$tolerance> is an integer between 0 and 100, where 0 represents the device's
654             default flatness tolerance.
655              
656             =cut
657              
658             sub _flatness {
659 2     2   3 my $flatness = shift();
660 2         10 return ($flatness, 'i');
661             }
662              
663             # Deprecated (renamed)
664 1     1 1 9 sub flatness { return flatness_tolerance(@_) }
665              
666             sub flatness_tolerance {
667 2     2 1 10 my ($self, $flatness) = @_;
668              
669 2         8 $self->add(_flatness($flatness));
670              
671 2         5 return $self;
672             }
673              
674             =head2 egstate
675              
676             $content = $content->egstate($object);
677              
678             Adds a L object containing a set of graphics
679             state parameters.
680              
681             =cut
682              
683             sub egstate {
684 0     0 1 0 my ($self, $egstate) = @_;
685 0         0 $self->add('/' . $egstate->name(), 'gs');
686 0         0 $self->resource('ExtGState', $egstate->name(), $egstate);
687 0         0 return $self;
688             }
689              
690             =head1 PATH CONSTRUCTION (DRAWING)
691              
692             Note that paths will not appear until a path painting method is called
693             (L, L, or L).
694              
695             =head2 move
696              
697             $content = $content->move($x, $y);
698              
699             Starts a new path at the specified coordinates.
700              
701             =cut
702              
703             sub _move {
704 0     0   0 my ($x, $y) =@_;
705 0         0 return (floats($x, $y), 'm');
706             }
707              
708             sub move {
709 107     107 1 258 my $self = shift();
710 107         200 my ($x, $y);
711 107         280 while (defined($x = shift())) {
712 107         176 $y = shift();
713 107 50       297 if ($self->_in_text_object()) {
714 0         0 $self->add_post(floats($x, $y), 'm');
715             }
716             else {
717 107         376 $self->add(floats($x, $y), 'm');
718             }
719 107         321 $self->{' x'} = $self->{' mx'} = $x;
720 107         378 $self->{' y'} = $self->{' my'} = $y;
721             }
722 107         263 return $self;
723             }
724              
725             =head2 line
726              
727             $content = $content->line($x, $y);
728              
729             Extends the path in a line from the current coordinates to the specified
730             coordinates.
731              
732             =cut
733              
734             sub _line {
735 0     0   0 my ($x, $y) = @_;
736 0         0 return (floats($x, $y), 'l');
737             }
738              
739             sub line {
740 95     95 1 171 my $self = shift();
741 95         202 my ($x, $y);
742 95         246 while (defined($x = shift())) {
743 96         155 $y = shift();
744 96 50       208 if ($self->_in_text_object()) {
745 0         0 $self->add_post(floats($x, $y), 'l');
746             }
747             else {
748 96         294 $self->add(floats($x, $y), 'l');
749             }
750 96         234 $self->{' x'} = $x;
751 96         291 $self->{' y'} = $y;
752             }
753 95         252 return $self;
754             }
755              
756             =head2 hline
757              
758             $content = $content->hline($x);
759              
760             Extends the path in a horizontal line from the current position to the specified
761             x coordinate.
762              
763             =cut
764              
765             sub hline {
766 2     2 1 13 my ($self, $x) = @_;
767 2         27 $self->{' x'} = $x;
768 2 50       8 if ($self->_in_text_object()) {
769 0         0 $self->add_post(floats($x, $self->{' y'}), 'l');
770             }
771             else {
772 2         10 $self->add(floats($x, $self->{' y'}), 'l');
773             }
774 2         7 return $self;
775             }
776              
777             =head2 vline
778              
779             $content = $content->vline($x);
780              
781             Extends the path in a vertical line from the current position to the specified y
782             coordinate.
783              
784             =cut
785              
786             sub vline {
787 1     1 1 6 my ($self, $y) = @_;
788 1 50       2 if ($self->_in_text_object()) {
789 0         0 $self->add_post(floats($self->{' x'}, $y), 'l');
790             }
791             else {
792 1         4 $self->add(floats($self->{' x'}, $y), 'l');
793             }
794 1         2 $self->{' y'} = $y;
795 1         2 return $self;
796             }
797              
798             =head2 polyline
799              
800             $content = $content->polyline($x1, $y1, $x2, $y2, ...);
801              
802             Extends the path from the current position in one or more straight lines.
803              
804             =cut
805              
806             sub polyline {
807 2     2 1 17 my $self = shift();
808 2 50       12 unless (@_ % 2 == 0) {
809 0         0 croak 'polyline requires pairs of coordinates';
810             }
811              
812 2         7 while (@_) {
813 3         7 my $x = shift();
814 3         4 my $y = shift();
815 3         12 $self->line($x, $y);
816             }
817              
818 2         6 return $self;
819             }
820              
821             # Deprecated; replace with move and polyline. Deprecated because poly breaks
822             # the convention followed by every other path-drawing method (other than
823             # enclosed shapes) of extending the path from the current position.
824             sub poly {
825 2     2 1 16 my $self = shift();
826 2         4 my $x = shift();
827 2         3 my $y = shift();
828 2         9 $self->move($x, $y);
829 2         7 $self->line(@_);
830 2         3 return $self;
831             }
832              
833             =head2 curve
834              
835             $content = $content->curve($cx1, $cy1, $cx2, $cy2, $x, $y);
836              
837             Extends the path in a curve from the current point to C<($x, $y)>, using the two
838             specified points to create a cubic Bezier curve.
839              
840             =cut
841              
842             sub curve {
843 78     78 1 138 my $self = shift();
844 78         123 my ($x1, $y1, $x2, $y2, $x3, $y3);
845 78         140 while (defined($x1 = shift())) {
846 78         106 $y1 = shift();
847 78         137 $x2 = shift();
848 78         100 $y2 = shift();
849 78         98 $x3 = shift();
850 78         89 $y3 = shift();
851 78 50       142 if ($self->_in_text_object()) {
852 0         0 $self->add_post(floats($x1, $y1, $x2, $y2, $x3, $y3), 'c');
853             }
854             else {
855 78         181 $self->add(floats($x1, $y1, $x2, $y2, $x3, $y3), 'c');
856             }
857 78         168 $self->{' x'} = $x3;
858 78         201 $self->{' y'} = $y3;
859             }
860 78         121 return $self;
861             }
862              
863             =head2 spline
864              
865             $content = $content->spline($cx1, $cy1, $x, $y);
866              
867             Extends the path in a curve from the current point to C<($x, $y)>, using the two
868             specified points to create a spline.
869              
870             =cut
871              
872             sub spline {
873 1     1 1 8 my $self = shift();
874              
875 1         5 while (scalar @_ >= 4) {
876 1         3 my $cx = shift();
877 1         2 my $cy = shift();
878 1         3 my $x = shift();
879 1         2 my $y = shift();
880 1         5 my $c1x = (2 * $cx + $self->{' x'}) / 3;
881 1         3 my $c1y = (2 * $cy + $self->{' y'}) / 3;
882 1         3 my $c2x = (2 * $cx + $x) / 3;
883 1         4 my $c2y = (2 * $cy + $y) / 3;
884 1         4 $self->curve($c1x, $c1y, $c2x, $c2y, $x, $y);
885             }
886             }
887              
888             =head2 arc
889              
890             $content = $content->arc($x, $y, $major, $minor, $a, $b);
891              
892             Extends the path along an arc of an ellipse centered at C<[$x, $y]>. C<$major>
893             and C<$minor> represent the axes of the ellipse, and the arc moves from C<$a>
894             degrees to C<$b> degrees.
895              
896             =cut
897              
898             # Private
899             sub arctocurve {
900 144     144 0 237 my ($a, $b, $alpha, $beta) = @_;
901 144 100       250 if (abs($beta - $alpha) > 30) {
902             return (
903 68         158 arctocurve($a, $b, $alpha, ($beta + $alpha) / 2),
904             arctocurve($a, $b, ($beta + $alpha) / 2, $beta)
905             );
906             }
907             else {
908 76         96 $alpha = ($alpha * pi / 180);
909 76         105 $beta = ($beta * pi / 180);
910              
911 76         155 my $bcp = (4.0 / 3 * (1 - cos(($beta - $alpha) / 2)) / sin(($beta - $alpha) / 2));
912 76         121 my $sin_alpha = sin($alpha);
913 76         102 my $sin_beta = sin($beta);
914 76         129 my $cos_alpha = cos($alpha);
915 76         105 my $cos_beta = cos($beta);
916              
917 76         106 my $p0_x = $a * $cos_alpha;
918 76         102 my $p0_y = $b * $sin_alpha;
919 76         105 my $p1_x = $a * ($cos_alpha - $bcp * $sin_alpha);
920 76         112 my $p1_y = $b * ($sin_alpha + $bcp * $cos_alpha);
921 76         128 my $p2_x = $a * ($cos_beta + $bcp * $sin_beta);
922 76         110 my $p2_y = $b * ($sin_beta - $bcp * $cos_beta);
923 76         99 my $p3_x = $a * $cos_beta;
924 76         94 my $p3_y = $b * $sin_beta;
925              
926 76         322 return ($p0_x, $p0_y, $p1_x, $p1_y, $p2_x, $p2_y, $p3_x, $p3_y);
927             }
928             }
929              
930             sub arc {
931 4     4 1 23 my ($self, $x, $y, $a, $b, $alpha, $beta, $move) = @_;
932 4         18 my @points = arctocurve($a, $b, $alpha, $beta);
933 4         22 my ($p0_x, $p0_y, $p1_x, $p1_y, $p2_x, $p2_y, $p3_x, $p3_y);
934              
935 4         14 $p0_x = $x + shift(@points);
936 4         8 $p0_y = $y + shift(@points);
937              
938             # Deprecated
939 4 100       25 $self->move($p0_x, $p0_y) if $move;
940              
941 4         22 while (scalar @points) {
942 40         73 $p1_x = $x + shift(@points);
943 40         68 $p1_y = $y + shift(@points);
944 40         74 $p2_x = $x + shift(@points);
945 40         58 $p2_y = $y + shift(@points);
946 40         68 $p3_x = $x + shift(@points);
947 40         61 $p3_y = $y + shift(@points);
948 40         114 $self->curve($p1_x, $p1_y, $p2_x, $p2_y, $p3_x, $p3_y);
949 40         73 shift(@points);
950 40         66 shift(@points);
951 40         74 $self->{' x'} = $p3_x;
952 40         94 $self->{' y'} = $p3_y;
953             }
954 4         12 return $self;
955             }
956              
957             # Extends the path along an arc of a circle of the specified radius from
958             # C<[x1,y1]> to C<[x2,y2]>.
959             #
960             # Set C<$move> to a true value if this arc is the beginning of a new path
961             # instead of the continuation of an existing path.
962             #
963             # Set C<$outer> to a true value to draw the larger arc between the two points
964             # instead of the smaller one.
965             #
966             # Set C<$reverse> to a true value to draw the mirror image of the specified arc.
967             #
968             # C<$radius * 2> cannot be smaller than the distance from C<[x1,y1]> to
969             # C<[x2,y2]>.
970              
971             # Deprecated; recreate using arc (Bogen is German for arc)
972             sub bogen {
973 4     4 0 40 my ($self, $x1, $y1, $x2, $y2, $r, $move, $larc, $spf) = @_;
974 4         9 my ($p0_x, $p0_y, $p1_x, $p1_y, $p2_x, $p2_y, $p3_x, $p3_y);
975 4         8 my $x = $x2 - $x1;
976 4         8 my $y = $y2 - $y1;
977 4         13 my $z = sqrt($x ** 2 + $y ** 2);
978 4         31 my $alpha_rad = asin($y / $z);
979              
980 4 50 33     50 $alpha_rad += pi / 2 if $x < 0 and $y > 0;
981 4 50 33     16 $alpha_rad -= pi / 2 if $x < 0 and $y < 0;
982              
983 4         22 my $alpha = rad2deg($alpha_rad);
984             # use the complement angle for span
985 4 100 66     75 $alpha -= 180 if $spf and $spf > 0;
986              
987 4         7 my $d = 2 * $r;
988 4         7 my ($beta, $beta_rad, @points);
989              
990 4         12 $beta = rad2deg(2 * asin($z / $d));
991 4 100 66     57 $beta = 360 - $beta if $larc and $larc > 0;
992              
993 4         13 $beta_rad = deg2rad($beta);
994              
995 4         56 @points = arctocurve($r, $r, 90 + $alpha + $beta / 2, 90 + $alpha - $beta / 2);
996              
997 4 100 66     10 if ($spf and $spf > 0) {
998 1         4 my @pts = @points;
999 1         2 @points = ();
1000 1         4 while ($y = pop(@pts)) {
1001 15         14 $x = pop(@pts);
1002 15         23 push(@points, $x, $y);
1003             }
1004             }
1005              
1006 4         8 $p0_x = shift(@points);
1007 4         7 $p0_y = shift(@points);
1008 4         8 $x = $x1 - $p0_x;
1009 4         6 $y = $y1 - $p0_y;
1010              
1011 4 100       13 $self->move($x1, $y1) if $move;
1012              
1013 4         10 while (scalar @points) {
1014 36         35 $p1_x = $x + shift(@points);
1015 36         37 $p1_y = $y + shift(@points);
1016 36         36 $p2_x = $x + shift(@points);
1017 36         36 $p2_y = $y + shift(@points);
1018             # if we run out of data points, use the end point instead
1019 36 100       44 if (scalar @points == 0) {
1020 1         2 $p3_x = $x2;
1021 1         2 $p3_y = $y2;
1022             }
1023             else {
1024 35         33 $p3_x = $x + shift(@points);
1025 35         35 $p3_y = $y + shift(@points);
1026             }
1027 36         75 $self->curve($p1_x, $p1_y, $p2_x, $p2_y, $p3_x, $p3_y);
1028 36         35 shift(@points);
1029 36         56 shift(@points);
1030             }
1031 4         11 return $self;
1032             }
1033              
1034             =head2 close
1035              
1036             $content = $content->close();
1037              
1038             Closes the current path by extending a line from the current position to the
1039             starting position.
1040              
1041             =cut
1042              
1043             sub close {
1044 13     13 1 72 my $self = shift();
1045 13         53 $self->add('h');
1046 13         46 $self->{' x'} = $self->{' mx'};
1047 13         39 $self->{' y'} = $self->{' my'};
1048 13         31 return $self;
1049             }
1050              
1051             =head1 SHAPE CONSTRUCTION (DRAWING)
1052              
1053             The following are convenience methods for drawing closed paths.
1054              
1055             Note that shapes will not appear until a path painting method is called
1056             (L, L, or L).
1057              
1058             =head2 rectangle
1059              
1060             $content = $content->rectangle($x1, $y1, $x2, $y2);
1061              
1062             Creates a new rectangle-shaped path, between the two points C<[$x1, $y1]>
1063             and C<[$x2, $y2]>.
1064              
1065             =cut
1066              
1067             sub rectangle {
1068 2     2 1 15 my ($self, $x1, $y1, $x2, $y2) = @_;
1069              
1070             # Ensure that x1,y1 is lower-left and x2,y2 is upper-right
1071 2 100       9 if ($x2 < $x1) {
1072 1         3 my $x = $x1;
1073 1         2 $x1 = $x2;
1074 1         2 $x2 = $x;
1075             }
1076 2 50       6 if ($y2 < $y1) {
1077 0         0 my $y = $y1;
1078 0         0 $y1 = $y2;
1079 0         0 $y2 = $y;
1080             }
1081              
1082 2         11 $self->add(floats($x1, $y1, ($x2 - $x1), ($y2 - $y1)), 're');
1083 2         6 $self->{' x'} = $x1;
1084 2         5 $self->{' y'} = $y1;
1085              
1086 2         6 return $self;
1087             }
1088              
1089             # Deprecated; replace with individual calls to rectangle
1090             sub rect {
1091 5     5 1 17 my $self = shift();
1092 5         7 my ($x, $y, $w, $h);
1093 5         9 while (defined($x = shift())) {
1094 6         8 $y = shift();
1095 6         5 $w = shift();
1096 6         8 $h = shift();
1097 6         18 $self->add(floats($x, $y, $w, $h), 're');
1098             }
1099 5         8 $self->{' x'} = $x;
1100 5         7 $self->{' y'} = $y;
1101 5         6 return $self;
1102             }
1103              
1104             # Deprecated; replace with rectangle, converting x2/y2 to w/h.
1105             sub rectxy {
1106 2     2 1 13 my ($self, $x, $y, $x2, $y2) = @_;
1107 2         21 $self->rect($x, $y, ($x2 - $x), ($y2 - $y));
1108 2         5 return $self;
1109             }
1110              
1111             =head2 circle
1112              
1113             $content = $content->circle($x, $y, $radius);
1114              
1115             Creates a new circular path centered on C<[$x, $y]> with the specified radius.
1116              
1117             =cut
1118              
1119             sub circle {
1120 1     1 1 9 my ($self, $x, $y, $r) = @_;
1121 1         7 $self->arc($x, $y, $r, $r, 0, 360, 1);
1122 1         5 $self->close();
1123 1         3 return $self;
1124             }
1125              
1126             =head2 ellipse
1127              
1128             $content = $content->ellipse($x, $y, $major, $minor);
1129              
1130             Creates a new elliptical path centered on C<[$x, $y]> with the specified major
1131             and minor axes.
1132              
1133             =cut
1134              
1135             sub ellipse {
1136 1     1 1 10 my ($self, $x, $y, $a, $b) = @_;
1137 1         7 $self->arc($x, $y, $a, $b, 0, 360, 1);
1138 1         7 $self->close();
1139 1         4 return $self;
1140             }
1141              
1142             =head2 pie
1143              
1144             $content = $content->pie($x, $y, $major, $minor, $a, $b);
1145              
1146             Creates a new wedge-shaped path from an ellipse centered on C<[$x, $y]> with the
1147             specified major and minor axes, extending from C<$a> degrees to C<$b> degrees.
1148              
1149             =cut
1150              
1151             sub pie {
1152 0     0 1 0 my $self = shift();
1153 0         0 my ($x, $y, $a, $b, $alpha, $beta) = @_;
1154 0         0 my ($p0_x, $p0_y) = arctocurve($a, $b, $alpha, $beta);
1155 0         0 $self->move($x, $y);
1156 0         0 $self->line($p0_x + $x, $p0_y + $y);
1157 0         0 $self->arc($x, $y, $a, $b, $alpha, $beta);
1158 0         0 $self->close();
1159             }
1160              
1161             =head1 PATH PAINTING (DRAWING)
1162              
1163             =head2 stroke_color
1164              
1165             $content->stroke_color($color, @arguments);
1166              
1167             Sets the stroke color, which is black by default.
1168              
1169             # Use a named color
1170             $content->stroke_color('blue');
1171              
1172             # Use an RGB color (start with '#')
1173             $content->stroke_color('#FF0000');
1174              
1175             # Use a CMYK color (start with '%')
1176             $content->stroke_color('%FF000000');
1177              
1178             # Use a spot color with 100% coverage.
1179             my $spot = $pdf->colorspace('spot', 'PANTONE Red 032 C', '#EF3340');
1180             $content->stroke_color($spot, 1.0);
1181              
1182             RGB and CMYK colors can have one-byte, two-byte, three-byte, or four-byte values
1183             for each color, depending on the level of precision needed. For instance, cyan
1184             can be given as C<%F000> or C<%FFFF000000000000>.
1185              
1186             =head2 fill_color
1187              
1188             $content->fill_color($color, @arguments);
1189              
1190             Sets the fill color, which is black by default. Arguments are the same as in
1191             L.
1192              
1193             =cut
1194              
1195             # default colorspaces: rgb/hsv/named cmyk/hsl lab
1196             # ... only one text string
1197             #
1198             # pattern or shading space
1199             # ... only one object
1200             #
1201             # legacy greylevel
1202             # ... only one value
1203             sub _makecolor {
1204 26     26   107 my ($self, $sf, @clr) = @_;
1205              
1206 26 100 33     189 if ($clr[0] =~ /^[a-z\#\!]+/) {
    100 33        
    50          
    50          
    50          
    50          
    0          
    0          
    0          
1207             # colorname or #! specifier
1208             # with rgb target colorspace
1209             # namecolor returns always a RGB
1210 23 100       116 return namecolor($clr[0]), ($sf ? 'rg' : 'RG');
1211             }
1212             elsif ($clr[0] =~ /^[\%]+/) {
1213             # % specifier
1214             # with cmyk target colorspace
1215 2 100       13 return namecolor_cmyk($clr[0]), ($sf ? 'k' : 'K');
1216             }
1217             elsif ($clr[0] =~ /^[\$\&]/) {
1218             # &$ specifier
1219             # with L*a*b target colorspace
1220 0 0       0 if (!defined $self->resource('ColorSpace', 'LabS')) {
1221 0         0 my $dc = PDFDict();
1222 0         0 my $cs = PDFArray(PDFName('Lab'), $dc);
1223 0         0 $dc->{'WhitePoint'} = PDFArray(map { PDFNum($_) } qw(1 1 1));
  0         0  
1224 0         0 $dc->{'Range'} = PDFArray(map { PDFNum($_) } qw(-128 127 -128 127));
  0         0  
1225 0         0 $dc->{'Gamma'} = PDFArray(map { PDFNum($_) } qw(2.2 2.2 2.2));
  0         0  
1226 0         0 $self->resource('ColorSpace', 'LabS', $cs);
1227             }
1228 0 0       0 return '/LabS', ($sf ? 'cs' : 'CS'), namecolor_lab($clr[0]), ($sf ? 'sc' : 'SC');
    0          
1229             }
1230             elsif (scalar @clr == 1 and ref($clr[0])) {
1231             # pattern or shading space
1232 0 0       0 return '/Pattern', ($sf ? 'cs' : 'CS'), '/' . ($clr[0]->name()), ($sf ? 'scn' : 'SCN');
    0          
1233             }
1234             elsif (scalar @clr == 1) {
1235             # grey color spec.
1236 0 0       0 return $clr[0], $sf ? 'g' : 'G';
1237             }
1238             elsif (scalar @clr > 1 and ref($clr[0])) {
1239             # indexed colorspace plus color-index
1240             # or custom colorspace plus param
1241 1         3 my $cs = shift(@clr);
1242 1 50       5 return '/' . $cs->name(), ($sf ? 'cs' : 'CS'), $cs->param(@clr), ($sf ? 'sc' : 'SC');
    50          
1243             }
1244             elsif (scalar @clr == 2) {
1245             # indexed colorspace plus color-index
1246             # or custom colorspace plus param
1247 0 0       0 return '/' . $clr[0]->name(), ($sf ? 'cs' : 'CS'), $clr[0]->param($clr[1]), ($sf ? 'sc' : 'SC');
    0          
1248             }
1249             elsif (scalar @clr == 3) {
1250             # legacy rgb color-spec (0 <= x <= 1)
1251 0 0       0 return floats($clr[0], $clr[1], $clr[2]), ($sf ? 'rg' : 'RG');
1252             }
1253             elsif (scalar @clr == 4) {
1254             # legacy cmyk color-spec (0 <= x <= 1)
1255 0 0       0 return floats($clr[0], $clr[1], $clr[2], $clr[3]), ($sf ? 'k' : 'K');
1256             }
1257             else {
1258 0         0 die 'invalid color specification.';
1259             }
1260             }
1261              
1262             sub _fillcolor {
1263 15     15   49 my ($self, @clrs) = @_;
1264 15 50       130 if (ref($clrs[0]) =~ m|^PDF::API2::Resource::ColorSpace|) {
    50          
1265 0         0 $self->resource('ColorSpace', $clrs[0]->name(), $clrs[0]);
1266             }
1267             elsif (ref($clrs[0]) =~ m|^PDF::API2::Resource::Pattern|) {
1268 0         0 $self->resource('Pattern', $clrs[0]->name(), $clrs[0]);
1269             }
1270              
1271 15         75 return $self->_makecolor(1, @clrs);
1272             }
1273              
1274             # Deprecated
1275 13     13 0 74 sub fillcolor { return fill_color(@_) }
1276              
1277             sub fill_color {
1278 15     15 1 40 my $self = shift();
1279 15 50       75 if (@_) {
1280 15         31 @{$self->{' fillcolor'}} = @_;
  15         50  
1281 15         91 $self->add($self->_fillcolor(@_));
1282             }
1283 15         33 return @{$self->{' fillcolor'}};
  15         52  
1284             }
1285              
1286             sub _strokecolor {
1287 11     11   30 my ($self, @clrs) = @_;
1288 11 100       65 if (ref($clrs[0]) =~ m|^PDF::API2::Resource::ColorSpace|) {
    50          
1289 1         7 $self->resource('ColorSpace', $clrs[0]->name(), $clrs[0]);
1290             }
1291             elsif (ref($clrs[0]) =~ m|^PDF::API2::Resource::Pattern|) {
1292 0         0 $self->resource('Pattern', $clrs[0]->name(), $clrs[0]);
1293             }
1294 11         70 return $self->_makecolor(0, @clrs);
1295             }
1296              
1297             # Deprecated
1298 8     8 0 41 sub strokecolor { return stroke_color(@_) }
1299              
1300             sub stroke_color {
1301 11     11 1 38 my $self = shift();
1302 11 50       34 if (@_) {
1303 11         25 @{$self->{' strokecolor'}} = @_;
  11         37  
1304 11         67 $self->add($self->_strokecolor(@_));
1305             }
1306 11         28 return @{$self->{' strokecolor'}};
  11         36  
1307             }
1308              
1309             =head2 stroke
1310              
1311             $content = $content->stroke();
1312              
1313             Strokes the current path.
1314              
1315             =cut
1316              
1317             sub _stroke {
1318 121     121   398 return 'S';
1319             }
1320              
1321             sub stroke {
1322 121     121 1 303 my $self = shift();
1323 121         293 $self->add(_stroke());
1324 121         252 return $self;
1325             }
1326              
1327             =head2 fill
1328              
1329             $content = $content->fill(rule => $rule);
1330              
1331             Fills the current path.
1332              
1333             C<$rule> describes which areas are filled in when the path intersects with itself.
1334              
1335             =over
1336              
1337             =item * nonzero (default)
1338              
1339             Use the nonzero winding number rule. This tends to mean that the entire area
1340             enclosed by the path is filled in, with some exceptions depending on the
1341             direction of the path.
1342              
1343             =item * even-odd
1344              
1345             Use the even-odd rule. This tends to mean that the presence of fill alternates
1346             each time the path is intersected.
1347              
1348             =back
1349              
1350             See PDF specification 1.7 section 8.5.3.3, Filling, for more details.
1351              
1352             =cut
1353              
1354             sub fill {
1355 3     3 1 93 my $self = shift();
1356              
1357 3         5 my $even_odd;
1358 3 100       15 if (@_ == 2) {
1359 1         6 my %options = @_;
1360 1 50 50     8 if (($options{'rule'} // 'nonzero') eq 'even-odd') {
1361 1         3 $even_odd = 1;
1362             }
1363             }
1364             else {
1365             # Deprecated
1366 2         4 $even_odd = shift();
1367             }
1368              
1369 3 100       30 $self->add($even_odd ? 'f*' : 'f');
1370              
1371 3         9 return $self;
1372             }
1373              
1374             =head2 paint
1375              
1376             $content = $content->paint(rule => $rule);
1377              
1378             Fills and strokes the current path. C<$rule> is as described in L.
1379              
1380             =cut
1381              
1382             # Deprecated (renamed)
1383 2     2 1 21 sub fillstroke { return paint(@_) }
1384              
1385             sub paint {
1386 4     4 1 20 my $self = shift();
1387              
1388 4         9 my $even_odd;
1389 4 100       15 if (@_ == 2) {
1390 1         5 my %options = @_;
1391 1 50 50     9 if (($options{'rule'} // 'nonzero') eq 'even-odd') {
1392 1         3 $even_odd = 1;
1393             }
1394             }
1395             else {
1396             # Deprecated
1397 3         8 $even_odd = shift();
1398             }
1399              
1400 4 100       23 $self->add($even_odd ? 'B*' : 'B');
1401              
1402 4         11 return $self;
1403             }
1404              
1405             =head2 clip
1406              
1407             $content = $content->clip(rule => $rule);
1408              
1409             Modifies the current clipping path (initially the entire page) by intersecting
1410             it with the current path following the next path-painting command. C<$rule> is
1411             as described in L.
1412              
1413             =cut
1414              
1415             sub clip {
1416 3     3 1 19 my $self = shift();
1417              
1418 3         6 my $even_odd;
1419 3 100       19 if (@_ == 2) {
1420 1         2 my %options = @_;
1421 1 50 50     6 if (($options{'rule'} // 'nonzero') eq 'even-odd') {
1422 1         2 $even_odd = 1;
1423             }
1424             }
1425             else {
1426             # Deprecated
1427 2         5 $even_odd = shift();
1428             }
1429              
1430 3 100       15 $self->add($even_odd ? 'W*' : 'W');
1431              
1432 3         8 return $self;
1433             }
1434              
1435             =head2 end
1436              
1437             $content = $content->end();
1438              
1439             Ends the current path without filling or stroking. This is used primarily for
1440             the side effect of changing the current clipping path.
1441              
1442             =cut
1443              
1444             # Deprecated (renamed)
1445 1     1 1 8 sub endpath { return end(@_) }
1446              
1447             sub end {
1448 2     2 1 11 my $self = shift();
1449 2         7 $self->add('n');
1450 2         4 return $self;
1451             }
1452              
1453             sub shade {
1454 0     0 0 0 my ($self, $shade, @cord) = @_;
1455              
1456 0         0 my @tm = (
1457             $cord[2] - $cord[0], 0,
1458             0 , $cord[3] - $cord[1],
1459             $cord[0] , $cord[1],
1460             );
1461              
1462 0         0 $self->save();
1463 0         0 $self->matrix(@tm);
1464 0         0 $self->add('/' . $shade->name(), 'sh');
1465 0         0 $self->resource('Shading', $shade->name(), $shade);
1466 0         0 $self->restore();
1467              
1468 0         0 return $self;
1469             }
1470              
1471             =head1 EXTERNAL OBJECTS
1472              
1473             =head2 object
1474              
1475             $content = $content->object($object, $x, $y, $scale_x, $scale_y);
1476              
1477             Places an image or other external object (a.k.a. XObject) on the page in the
1478             specified location.
1479              
1480             If C<$x> and C<$y> are omitted, the object will be placed at C<[0, 0]>.
1481              
1482             For images, C<$scale_x> and C<$scale_y> represent the width and height of the
1483             image on the page in points. If C<$scale_x> is omitted, it will default to 72
1484             pixels per inch. If C<$scale_y> is omitted, the image will be scaled
1485             proportionally based on the image dimensions.
1486              
1487             For other external objects, the scale is a multiplier, where 1 (the default)
1488             represents 100% (i.e. no change).
1489              
1490             If coordinate transformations have been made (see Coordinate Transformations
1491             above), the position and scale will be relative to the updated coordinates.
1492              
1493             If no coordinate transformations are needed, this method can be called directly
1494             from the L object instead.
1495              
1496             =cut
1497              
1498             # Behavior based on argument count
1499             # 0: Place at 0, 0, 100%
1500             # 2: Place at X, Y, 100%
1501             # 3: Place at X, Y, scaled
1502             # 4: Place at X, Y, scale_w, scale_h
1503             sub object {
1504 0     0 1 0 my ($self, $object, $x, $y, $scale_x, $scale_y) = @_;
1505 0   0     0 $x //= 0;
1506 0   0     0 $y //= 0;
1507 0 0       0 if ($object->isa('PDF::API2::Resource::XObject::Image')) {
1508 0   0     0 $scale_x //= $object->width();
1509 0   0     0 $scale_y //= $object->height() * $scale_x / $object->width();
1510             }
1511             else {
1512 0   0     0 $scale_x //= 1;
1513 0   0     0 $scale_y //= $scale_x;
1514             }
1515              
1516 0         0 $self->save();
1517 0         0 $self->matrix($scale_x, 0, 0, $scale_y, $x, $y);
1518 0         0 $self->add('/' . $object->name(), 'Do');
1519 0         0 $self->restore();
1520              
1521 0         0 $self->resource('XObject', $object->name(), $object);
1522              
1523 0         0 return $self;
1524             }
1525              
1526             # Deprecated
1527             sub image {
1528 8     8 1 90 my $self = shift;
1529 8         21 my $img = shift;
1530 8         26 my ($x, $y, $w, $h) = @_;
1531 8 50       29 if (defined $img->{'Metadata'}) {
1532 0         0 $self->metaStart('PPAM:PlacedImage', $img->{'Metadata'});
1533             }
1534 8         38 $self->save();
1535 8 50 33     41 unless (defined $w) {
1536 0         0 $h = $img->height();
1537 0         0 $w = $img->width();
1538             }
1539             elsif (not defined $h) {
1540             $h = $img->height() * $w;
1541             $w = $img->width() * $w;
1542             }
1543 8         41 $self->matrix($w, 0, 0, $h, $x, $y);
1544 8         45 $self->add('/' . $img->name(), 'Do');
1545 8         33 $self->restore();
1546 8         189 $self->{' x'} = $x;
1547 8         21 $self->{' y'} = $y;
1548 8         27 $self->resource('XObject', $img->name(), $img);
1549 8 50       29 if (defined $img->{'Metadata'}) {
1550 0         0 $self->metaEnd();
1551             }
1552 8         50 return $self;
1553             }
1554              
1555             # Deprecated
1556             sub formimage {
1557 2     2 1 12 my ($self, $img, $x, $y, $s) = @_;
1558 2         11 $self->save();
1559 2 50       8 if (defined $s) {
1560 2         10 $self->matrix($s, 0, 0, $s, $x, $y);
1561             }
1562             else {
1563 0         0 $self->matrix(1, 0, 0, 1, $x, $y);
1564             }
1565 2         12 $self->add('/' . $img->name(), 'Do');
1566 2         13 $self->restore();
1567 2         8 $self->resource('XObject', $img->name(), $img);
1568 2         7 return $self;
1569             }
1570              
1571             =head1 TEXT STATE
1572              
1573             All of the following parameters that take a size are applied before any scaling
1574             takes place, so you don't need to adjust values to counteract scaling.
1575              
1576             =head2 font
1577              
1578             $content = $content->font($font, $size);
1579              
1580             Sets the font and font size. C<$font> is an object created by calling
1581             L to add the font to the document.
1582              
1583             my $pdf = PDF::API2->new();
1584             my $page = $pdf->page();
1585             my $text = $page->text();
1586              
1587             my $font = $pdf->font('Helvetica');
1588             $text->font($font, 24);
1589             $text->position(72, 720);
1590             $text->text('Hello, World!');
1591              
1592             $pdf->save('sample.pdf');
1593              
1594             =cut
1595              
1596             sub _font {
1597 18     18   50 my ($font, $size) = @_;
1598 18 100       56 if ($font->isvirtual()) {
1599 1         4 return('/' . $font->fontlist->[0]->name() . ' ' . float($size) . ' Tf');
1600             }
1601             else {
1602 17         95 return('/' . $font->name() . ' ' . float($size) . ' Tf');
1603             }
1604             }
1605             sub font {
1606 19     19 1 833 my ($self, $font, $size) = @_;
1607 19 100       55 unless ($size) {
1608 1         130 croak q{A font size is required};
1609             }
1610 18         111 $self->fontset($font, $size);
1611 18         69 $self->add(_font($font, $size));
1612 18         39 $self->{' fontset'} = 1;
1613 18         40 return $self;
1614             }
1615              
1616             sub fontset {
1617 18     18 0 44 my ($self, $font, $size) = @_;
1618 18         45 $self->{' font'} = $font;
1619 18         47 $self->{' fontsize'} = $size;
1620 18         60 $self->{' fontset'} = 0;
1621              
1622 18 100       126 if ($font->isvirtual()) {
1623 1         2 foreach my $f (@{$font->fontlist()}) {
  1         3  
1624 2         9 $self->resource('Font', $f->name(), $f);
1625             }
1626             }
1627             else {
1628 17         117 $self->resource('Font', $font->name(), $font);
1629             }
1630              
1631 18         39 return $self;
1632             }
1633              
1634             =head2 character_spacing
1635              
1636             $spacing = $content->character_spacing($spacing);
1637              
1638             Sets the spacing between characters. This is initially zero.
1639              
1640             =cut
1641              
1642             sub _charspace {
1643 3     3   6 my $spacing = shift();
1644 3         20 return float($spacing, 6) . ' Tc';
1645             }
1646              
1647             # Deprecated (renamed)
1648 3     3 1 1147 sub charspace { return character_spacing(@_) }
1649              
1650             sub character_spacing {
1651 4     4 1 17 my ($self, $spacing) = @_;
1652 4 100       17 if (defined $spacing) {
1653 3         7 $self->{' charspace'} = $spacing;
1654 3         15 $self->add(_charspace($spacing));
1655             }
1656 4         15 return $self->{' charspace'};
1657             }
1658              
1659             =head2 word_spacing
1660              
1661             $spacing = $content->word_spacing($spacing);
1662              
1663             Sets the spacing between words. This is initially zero (i.e. just the width of
1664             the space).
1665              
1666             Word spacing might only affect simple fonts and composite fonts where the space
1667             character is a single-byte code. This is a limitation of the PDF specification
1668             at least as of version 1.7 (see section 9.3.3). It's possible that a later
1669             version of the specification will support word spacing in fonts that use
1670             multi-byte codes.
1671              
1672             =cut
1673              
1674             sub _wordspace {
1675 15     15   21 my $spacing = shift();
1676 15         52 return float($spacing, 6) . ' Tw';
1677             }
1678              
1679             # Deprecated (renamed)
1680 24     24 1 608 sub wordspace { return word_spacing(@_) }
1681              
1682             sub word_spacing {
1683 25     25 1 42 my ($self, $spacing) = @_;
1684 25 100       53 if (defined $spacing) {
1685 15         25 $self->{' wordspace'} = $spacing;
1686 15         32 $self->add(_wordspace($spacing));
1687             }
1688 25         71 return $self->{' wordspace'};
1689             }
1690              
1691             =head2 hscale
1692              
1693             $scale = $content->hscale($scale);
1694              
1695             Sets/gets the percentage of horizontal text scaling. Enter a scale greater than
1696             100 to stretch text, less than 100 to squeeze text, or 100 to disable any
1697             existing scaling.
1698              
1699             =cut
1700              
1701             sub _hscale {
1702 2     2   6 my $scale = shift();
1703 2         14 return float($scale, 6) . ' Tz';
1704             }
1705              
1706             sub hscale {
1707 14     14 1 35 my ($self, $scale) = @_;
1708 14 100       50 if (defined $scale) {
1709 2         6 $self->{' hscale'} = $scale;
1710 2         10 $self->add(_hscale($scale));
1711             }
1712 14         47 return $self->{' hscale'};
1713             }
1714              
1715             # Deprecated: hscale was originally named incorrectly (as hspace)
1716 1     1 1 14 sub hspace { return hscale(@_) }
1717 0     0   0 sub _hspace { return _hscale(@_) }
1718              
1719             =head2 leading
1720              
1721             $leading = $content->leading($leading);
1722              
1723             Sets/gets the text leading, which is the distance between baselines. This is
1724             initially zero (i.e. the lines will be printed on top of each other).
1725              
1726             =cut
1727              
1728             # Deprecated: leading is the correct name for this operator
1729 0     0   0 sub _lead { return _leading(@_) }
1730 1     1 1 12 sub lead { return leading(@_) }
1731              
1732             sub _leading {
1733 11     11   21 my $leading = shift();
1734 11         37 return float($leading) . ' TL';
1735             }
1736             sub leading {
1737 46     46 1 147 my ($self, $leading) = @_;
1738 46 100       92 if (defined ($leading)) {
1739 11         31 $self->{' leading'} = $leading;
1740 11         36 $self->add(_leading($leading));
1741             }
1742 46         126 return $self->{' leading'};
1743             }
1744              
1745             =head2 render
1746              
1747             $mode = $content->render($mode);
1748              
1749             Sets the text rendering mode.
1750              
1751             =over
1752              
1753             =item * 0 = Fill text
1754              
1755             =item * 1 = Stroke text (outline)
1756              
1757             =item * 2 = Fill, then stroke text
1758              
1759             =item * 3 = Neither fill nor stroke text (invisible)
1760              
1761             =item * 4 = Fill text and add to path for clipping
1762              
1763             =item * 5 = Stroke text and add to path for clipping
1764              
1765             =item * 6 = Fill, then stroke text and add to path for clipping
1766              
1767             =item * 7 = Add text to path for clipping
1768              
1769             =back
1770              
1771             =cut
1772              
1773             sub _render {
1774 1     1   2 my $mode = shift();
1775 1         5 return intg($mode) . ' Tr';
1776             }
1777              
1778             sub render {
1779 1     1 1 7 my ($self, $mode) = @_;
1780 1 50       4 if (defined ($mode)) {
1781 1         2 $self->{' render'} = $mode;
1782 1         4 $self->add(_render($mode));
1783             }
1784 1         3 return $self->{' render'};
1785             }
1786              
1787             =head2 rise
1788              
1789             $distance = $content->rise($distance);
1790              
1791             Adjusts the baseline up or down from its current location. This is initially
1792             zero.
1793              
1794             Use this to create superscripts or subscripts (usually with an adjustment to the
1795             font size as well).
1796              
1797             =cut
1798              
1799             sub _rise {
1800 1     1   1 my $distance = shift();
1801 1         4 return float($distance) . ' Ts';
1802             }
1803              
1804             sub rise {
1805 1     1 1 6 my ($self, $distance) = @_;
1806 1 50       3 if (defined ($distance)) {
1807 1         1 $self->{' rise'} = $distance;
1808 1         4 $self->add(_rise($distance));
1809             }
1810 1         2 return $self->{' rise'};
1811             }
1812              
1813             # Formerly documented; still used internally
1814             sub textstate {
1815 0     0 0 0 my $self = shift();
1816 0         0 my %state;
1817 0 0       0 if (@_) {
1818 0         0 %state = @_;
1819 0         0 foreach my $k (qw(charspace hscale wordspace leading rise render)) {
1820 0 0       0 next unless $state{$k};
1821 0         0 $self->can($k)->($self, $state{$k});
1822             }
1823 0 0 0     0 if ($state{'font'} and $state{'fontsize'}) {
1824 0         0 $self->font($state{'font'}, $state{'fontsize'});
1825             }
1826 0 0       0 if ($state{'textmatrix'}) {
1827 0         0 $self->matrix(@{$state{'textmatrix'}});
  0         0  
1828 0         0 @{$self->{' translate'}} = @{$state{'translate'}};
  0         0  
  0         0  
1829 0         0 $self->{' rotate'} = $state{'rotate'};
1830 0         0 @{$self->{' scale'}} = @{$state{'scale'}};
  0         0  
  0         0  
1831 0         0 @{$self->{' skew'}} = @{$state{'skew'}};
  0         0  
  0         0  
1832             }
1833 0 0       0 if ($state{'fillcolor'}) {
1834 0         0 $self->fillcolor(@{$state{'fillcolor'}});
  0         0  
1835             }
1836 0 0       0 if ($state{'strokecolor'}) {
1837 0         0 $self->strokecolor(@{$state{'strokecolor'}});
  0         0  
1838             }
1839 0         0 %state = ();
1840             }
1841             else {
1842 0         0 foreach my $k (qw(font fontsize charspace hscale wordspace leading rise render)) {
1843 0         0 $state{$k} = $self->{" $k"};
1844             }
1845 0         0 $state{'matrix'} = [@{$self->{' matrix'}}];
  0         0  
1846 0         0 $state{'textmatrix'} = [@{$self->{' textmatrix'}}];
  0         0  
1847 0         0 $state{'textlinematrix'} = [@{$self->{' textlinematrix'}}];
  0         0  
1848 0         0 $state{'rotate'} = $self->{' rotate'};
1849 0         0 $state{'scale'} = [@{$self->{' scale'}}];
  0         0  
1850 0         0 $state{'skew'} = [@{$self->{' skew'}}];
  0         0  
1851 0         0 $state{'translate'} = [@{$self->{' translate'}}];
  0         0  
1852 0         0 $state{'fillcolor'} = [@{$self->{' fillcolor'}}];
  0         0  
1853 0         0 $state{'strokecolor'} = [@{$self->{' strokecolor'}}];
  0         0  
1854             }
1855 0         0 return %state;
1856             }
1857              
1858             =head1 TEXT PLACEMENT
1859              
1860             =head2 position
1861              
1862             # Set
1863             $content = $content->position($x, $y);
1864              
1865             # Get
1866             ($x, $y) = $content->position();
1867              
1868             If called with arguments, moves to the start of the current line of text, offset
1869             by C<$x> and C<$y>.
1870              
1871             If called without arguments, returns the current position of the cursor (before
1872             the effects of any coordinate transformation methods).
1873              
1874             =cut
1875              
1876             sub position {
1877 59     59 1 149 my ($self, $x, $y) = @_;
1878              
1879 59 50 66     161 if (defined $x and not defined $y) {
1880 0         0 croak 'position requires either 0 or 2 arguments';
1881             }
1882              
1883 59 100       113 if (defined $x) {
1884 1         5 $self->add(float($x), float($y), 'Td');
1885 1         5 $self->matrix_update($x, $y);
1886 1         2 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'} + $x;
1887 1         2 $self->{' textlinestart'} = $self->{' textlinematrix'}->[0];
1888 1         2 return $self;
1889             }
1890              
1891 58         81 return @{$self->{' textlinematrix'}};
  58         193  
1892             }
1893              
1894             # Deprecated; replace with position
1895             sub distance {
1896 1     1 1 11 my ($self, $dx, $dy) = @_;
1897 1         8 $self->add(float($dx), float($dy), 'Td');
1898 1         6 $self->matrix_update($dx, $dy);
1899 1         4 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'} + $dx;
1900 1         4 $self->{' textlinestart'} = $self->{' textlinematrix'}->[0];
1901             }
1902              
1903             # Deprecated; use position (ignores leading) or crlf (uses leading) instead
1904             sub cr {
1905 3     3 1 19 my ($self, $offset) = @_;
1906 3 100       12 if (defined $offset) {
1907 2         11 $self->add(0, float($offset), 'Td');
1908 2         8 $self->matrix_update(0, $offset);
1909             }
1910             else {
1911 1         6 $self->add('T*');
1912 1         34 $self->matrix_update(0, $self->leading() * -1);
1913             }
1914              
1915 3         10 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
1916             }
1917              
1918             =head2 crlf
1919              
1920             $content = $content->crlf();
1921              
1922             Moves to the start of the next line, based on the L setting.
1923              
1924             If leading isn't set, a default distance of 120% of the font size will be used.
1925              
1926             =cut
1927              
1928             sub crlf {
1929 23     23 1 46 my $self = shift();
1930 23         59 my $leading = $self->leading();
1931 23 100 100     82 if ($leading or not $self->{' fontsize'}) {
1932 22         47 $self->add('T*');
1933             }
1934             else {
1935 1         5 $leading = $self->{' fontsize'} * 1.2;
1936 1         8 $self->add(0, float($leading * -1), 'Td');
1937             }
1938              
1939 23         76 $self->matrix_update(0, $leading * -1);
1940 23         44 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
1941 23         57 return $self;
1942             }
1943              
1944             # Deprecated; replace with crlf
1945             sub nl {
1946 1     1 1 8 my $self = shift();
1947 1         5 $self->add('T*');
1948 1         6 $self->matrix_update(0, $self->leading() * -1);
1949 1         5 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
1950             }
1951              
1952             sub _textpos {
1953 0     0   0 my ($self, @xy) = @_;
1954              
1955 0         0 my ($x, $y) = (0, 0);
1956 0         0 while (@xy) {
1957 0         0 $x += shift(@xy);
1958 0         0 $y += shift(@xy);
1959             }
1960             my (@m) = _transform(
1961 0         0 -matrix => $self->{' textmatrix'},
1962             -point => [$x, $y],
1963             );
1964              
1965 0         0 return ($m[0], $m[1]);
1966             }
1967              
1968             # Deprecated
1969             sub textpos {
1970 0     0 0 0 my $self = shift();
1971 0         0 return $self->_textpos(@{$self->{' textlinematrix'}});
  0         0  
1972             }
1973              
1974             # Deprecated; replace with position (without arguments)
1975             sub textpos2 {
1976 0     0 0 0 my $self = shift();
1977 0         0 return $self->position();
1978             }
1979              
1980             =head2 text
1981              
1982             my $width = $content->text($text, %options);
1983              
1984             Places text on the page. Returns the width of the text in points.
1985              
1986             Options:
1987              
1988             =over
1989              
1990             =item * align
1991              
1992             One of C (default), C
, or C. Text will be placed such that
1993             it begins, is centered on, or ends at the current text position, respectively.
1994              
1995             In each case, the position will then be moved to the end of the text.
1996              
1997             =item * indent
1998              
1999             Indents the text by the number of points.
2000              
2001             If C is set to anything other than C, this setting will be ignored.
2002              
2003             =item * underline
2004              
2005             Underlines the text. The value may be one of the following:
2006              
2007             =over
2008              
2009             =item * auto
2010              
2011             Determines the underline distance from the text based on the font and font size.
2012              
2013             =item * $distance
2014              
2015             Manually set the underline distance in points. A positive distance moves the
2016             line downward.
2017              
2018             =item * [$distance, $thickness, ...]
2019              
2020             Manually set both the underline distance and line thickness, both in points.
2021              
2022             Repeat these arguments to include multiple underlines.
2023              
2024             =back
2025              
2026             =back
2027              
2028             =cut
2029              
2030             sub _text_underline {
2031 0     0   0 my ($self, $xy1, $xy2, $underline, $color) = @_;
2032 0   0     0 $color ||= 'black';
2033              
2034 0         0 my @underline;
2035 0 0       0 if (ref($underline) eq 'ARRAY') {
2036 0         0 @underline = @$underline;
2037             }
2038             else {
2039 0         0 @underline = ($underline, 1);
2040             }
2041 0 0       0 push @underline, 1 if @underline % 2;
2042              
2043 0   0     0 my $underlineposition = (-$self->{' font'}->underlineposition() * $self->{' fontsize'} / 1000 || 1);
2044 0   0     0 my $underlinethickness = ($self->{' font'}->underlinethickness() * $self->{' fontsize'} / 1000 || 1);
2045 0         0 my $pos = 1;
2046              
2047 0         0 while (@underline) {
2048 0         0 $self->add_post(_save());
2049              
2050 0         0 my $distance = shift(@underline);
2051 0         0 my $thickness = shift(@underline);
2052 0         0 my $scolor = $color;
2053 0 0       0 if (ref($thickness)) {
2054 0         0 ($thickness, $scolor) = @$thickness;
2055             }
2056 0 0       0 if ($distance eq 'auto') {
2057 0         0 $distance = $pos * $underlineposition;
2058             }
2059 0 0       0 if ($thickness eq 'auto') {
2060 0         0 $thickness = $underlinethickness;
2061             }
2062              
2063 0         0 my ($x1, $y1) = $self->_textpos(@$xy1, 0, -($distance + ($thickness / 2)));
2064 0         0 my ($x2, $y2) = $self->_textpos(@$xy2, 0, -($distance + ($thickness / 2)));
2065              
2066 0         0 $self->add_post($self->_strokecolor($scolor));
2067 0         0 $self->add_post(_linewidth($thickness));
2068 0         0 $self->add_post(_move($x1, $y1));
2069 0         0 $self->add_post(_line($x2, $y2));
2070 0         0 $self->add_post(_stroke());
2071              
2072 0         0 $self->add_post(_restore());
2073 0         0 $pos++;
2074             }
2075             }
2076              
2077             sub text {
2078 30     30 1 119 my ($self, $text, %opts) = @_;
2079 30 100       95 unless ($self->{' fontset'}) {
2080 1 50 33     7 unless (defined $self->{' font'} and $self->{' fontsize'}) {
2081 1         332 croak q{Can't add text without first setting a font and font size};
2082             }
2083 0         0 $self->font($self->{' font'}, $self->{' fontsize'});
2084 0         0 $self->{' fontset'} = 1;
2085             }
2086              
2087             # Deprecated options (remove hyphens)
2088 29 100       73 if (exists $opts{'-indent'}) {
2089 10   33     50 $opts{'indent'} //= delete $opts{'-indent'};
2090             }
2091 29 50       76 if (exists $opts{'-underline'}) {
2092 0   0     0 $opts{'underline'} //= delete $opts{'-underline'};
2093             }
2094              
2095 29         85 my $width = $self->text_width($text);
2096              
2097 29 100       89 if (defined $opts{'align'}) {
2098 12 100       88 if ($opts{'align'} eq 'left') {
    100          
    50          
2099             # NOOP
2100             }
2101             elsif ($opts{'align'} eq 'center') {
2102 3         9 $opts{'indent'} = -($width / 2);
2103             }
2104             elsif ($opts{'align'} eq 'right') {
2105 3         8 $opts{'indent'} = -$width;
2106             }
2107             else {
2108 0         0 croak 'Invalid alignment: ' . $opts{'align'};
2109             }
2110             }
2111              
2112 29 100       78 if (defined $opts{'indent'}) {
2113 12         64 $self->matrix_update($opts{'indent'}, 0);
2114             }
2115              
2116 29         104 my $underline_start = [$self->position()];
2117              
2118 29 100       80 if (defined $opts{'indent'}) {
2119 12         22 my $indent = -$opts{'indent'};
2120 12         64 $indent *= (1000 / $self->{' fontsize'}) * (100 / $self->hscale());
2121 12         62 $self->add($self->{' font'}->text($text, $self->{' fontsize'}, $indent));
2122             }
2123             else {
2124 17         78 $self->add($self->{' font'}->text($text, $self->{' fontsize'}));
2125             }
2126              
2127 29         111 $self->matrix_update($width, 0);
2128              
2129 29         68 my $underline_end = [$self->position()];
2130              
2131 29 50       78 if (defined $opts{'underline'}) {
2132             $self->_text_underline($underline_start, $underline_end,
2133             $opts{'underline'},
2134 0         0 $opts{'-strokecolor'});
2135             }
2136              
2137 29         96 return $width;
2138             }
2139              
2140             # Deprecated; replace with text($line, align => 'center')
2141             sub text_center {
2142 6     6 1 54 my ($self, $text, @opts) = @_;
2143 6         30 my $width = $self->advancewidth($text);
2144 6         41 return $self->text($text, -indent => -($width / 2), @opts);
2145             }
2146              
2147             # Deprecated; replace with text($line, align => 'right')
2148             sub text_right {
2149 3     3 1 20 my ($self, $text, @opts) = @_;
2150 3         7 my $width = $self->advancewidth($text);
2151 3         15 return $self->text($text, -indent => -$width, @opts);
2152             }
2153              
2154             =head2 text_justified
2155              
2156             my $width = $content->text_justified($text, $width, %options);
2157              
2158             As C, filling the specified width by adjusting the space between words.
2159              
2160             =cut
2161              
2162             sub text_justified {
2163 1     1 1 14 my ($self, $text, $width, %opts) = @_;
2164 1         6 my $initial_width = $self->text_width($text);
2165 1         4 my $space_count = (split /\s/, $text) - 1;
2166 1         4 my $ws = $self->wordspace();
2167 1 50       5 $self->wordspace(($width - $initial_width) / $space_count) if $space_count;
2168 1         4 $self->text($text, %opts);
2169 1         3 $self->wordspace($ws);
2170 1         3 return $width;
2171             }
2172              
2173             sub _text_fill_line {
2174 19     19   44 my ($self, $text, $width) = @_;
2175 19         86 my @words = split(/\x20/, $text);
2176 19         37 my @line = ();
2177 19         44 local $" = ' ';
2178 19         36 while (@words) {
2179 112         167 push @line, (shift @words);
2180 112 100       354 last if $self->advancewidth("@line") > $width;
2181             }
2182 19 100 66     90 if ((scalar @line > 1) and ($self->advancewidth("@line") > $width)) {
2183 12         37 unshift @words, pop @line;
2184             }
2185 19         59 my $ret = "@words";
2186 19         31 my $line = "@line";
2187 19         90 return $line, $ret;
2188             }
2189              
2190             sub text_fill_left {
2191 7     7 0 14 my ($self, $text, $width, %opts) = @_;
2192 7         25 my ($line, $ret) = $self->_text_fill_line($text, $width);
2193 7         25 $width = $self->text($line, %opts);
2194 7         24 return $width, $ret;
2195             }
2196              
2197             sub text_fill_center {
2198 2     2 0 5 my ($self, $text, $width, %opts) = @_;
2199 2         7 my ($line, $ret) = $self->_text_fill_line($text, $width);
2200 2         7 $width = $self->text_center($line, %opts);
2201 2         6 return $width, $ret;
2202             }
2203              
2204             sub text_fill_right {
2205 2     2 0 8 my ($self, $text, $width, %opts) = @_;
2206 2         10 my ($line, $ret) = $self->_text_fill_line($text, $width);
2207 2         9 $width = $self->text_right($line, %opts);
2208 2         8 return $width, $ret;
2209             }
2210              
2211             sub text_fill_justified {
2212 8     8 0 26 my ($self, $text, $width, %opts) = @_;
2213 8         28 my ($line, $ret) = $self->_text_fill_line($text, $width);
2214 8         43 my $ws = $self->wordspace();
2215 8         17 my $w = $self->advancewidth($line);
2216 8         49 my $space_count = (split /\s/, $line) - 1;
2217              
2218             # Normal Line
2219 8 100       29 if ($ret) {
2220 4 50       21 $self->wordspace(($width - $w) / $space_count) if $space_count;
2221 4         19 $width = $self->text($line, %opts, align => 'left');
2222 4         17 $self->wordspace($ws);
2223 4         14 return $width, $ret;
2224             }
2225              
2226             # Last Line
2227 4 100       12 if ($opts{'align-last'}) {
2228 3 50       24 unless ($opts{'align-last'} =~ /^(left|center|right|justified)$/) {
2229 0         0 croak 'Invalid align-last (must be left, center, right, or justified)';
2230             }
2231             }
2232 4   100     15 my $align_last = $opts{'align-last'} // 'left';
2233 4 100       19 if ($align_last eq 'left') {
    100          
    100          
2234 1         3 $width = $self->text($line, %opts, align => 'left');
2235             }
2236             elsif ($align_last eq 'center') {
2237 1         5 $width = $self->text($line, %opts, align => 'center');
2238             }
2239             elsif ($align_last eq 'right') {
2240 1         4 $width = $self->text($line, %opts, align => 'right');
2241             }
2242             else {
2243 1 50       7 $self->wordspace(($width - $w) / $space_count) if $space_count;
2244 1         3 $width = $self->text($line, %opts, align => 'left');
2245 1         3 $self->wordspace($ws);
2246             }
2247 4         12 return $width, $ret;
2248             }
2249              
2250             =head2 paragraph
2251              
2252             # Scalar context
2253             $overflow_text = $content->paragraph($text, $width, $height, %options);
2254              
2255             # Array context
2256             ($overflow, $height) = $content->paragraph($text, $width, $height, %options);
2257              
2258             Fills the rectangle with as much of the provided text as will fit.
2259              
2260             In array context, returns the remaining text (if any) of the positioned text and
2261             the remaining (unused) height. In scalar context, returns the remaining text
2262             (if any).
2263              
2264             Line spacing follows L, if set, or 120% of the font size by default.
2265              
2266             B
2267              
2268             =over
2269              
2270             =item * align
2271              
2272             Specifies the alignment for each line of text. May be set to C (default),
2273             C
, C, or C.
2274              
2275             =item * align-last
2276              
2277             Specifies the alignment for the last line of justified text. May be set to
2278             C (default), C
, C, or C.
2279              
2280             =item * underline
2281              
2282             As described in L.
2283              
2284             =back
2285              
2286             =cut
2287              
2288             sub paragraph {
2289 10     10 1 98 my ($self, $text, $width, $height, %opts) = @_;
2290              
2291             # Deprecated options (remove hyphens)
2292 10 100       29 if (exists $opts{'-align'}) {
2293 6   33     46 $opts{'align'} //= delete $opts{'-align'};
2294             }
2295 10 100       28 if (exists $opts{'-align-last'}) {
2296 3   33     15 $opts{'align-last'} //= delete $opts{'-align-last'};
2297             }
2298 10 50       22 if (exists $opts{'-underline'}) {
2299 0   0     0 $opts{'underline'} //= delete $opts{'-underline'};
2300             }
2301              
2302 10         23 my $leading = $self->leading();
2303 10 50       24 unless ($leading) {
2304 0         0 $leading = $self->{' fontsize'} * 1.2;
2305             }
2306              
2307             # If the text contains newlines, call paragraph on each line
2308 10 100       31 if ($text =~ /\n/) {
2309 1         3 my $overflow = '';
2310 1         4 foreach my $line (split /\n/, $text) {
2311             # If there's overflow, no more text can be placed.
2312 3 50       18 if (length($overflow)) {
2313 0         0 $overflow .= "\n" . $line;
2314 0         0 next;
2315             }
2316              
2317             # Place a blank line if there are consecutive newlines
2318 3 100       6 unless (length($line)) {
2319 1         3 $self->crlf();
2320 1         2 $height -= $leading;
2321 1         2 next;
2322             }
2323              
2324 2         8 ($line, $height) = $self->paragraph($line, $width, $height, %opts);
2325 2 100       7 $overflow .= $line if length($line);
2326             }
2327              
2328 1 50       4 return ($overflow, $height) if wantarray();
2329 1         4 return $overflow;
2330             }
2331              
2332 9         17 my $w;
2333 9         32 while (length($text) > 0) {
2334 21         30 $height -= $leading;
2335 21 100       55 last if $height < 0;
2336              
2337 19   100     56 my $align = $opts{'align'} // 'left';
2338 19 100 66     72 if ($align eq 'justified' or $align eq 'justify') {
    100          
    100          
2339 8         64 ($w, $text) = $self->text_fill_justified($text, $width, %opts);
2340             }
2341             elsif ($align eq 'right') {
2342 2         21 ($w, $text) = $self->text_fill_right($text, $width, %opts);
2343             }
2344             elsif ($align eq 'center') {
2345 2         15 ($w, $text) = $self->text_fill_center($text, $width, %opts);
2346             }
2347             else {
2348 7         29 ($w, $text) = $self->text_fill_left($text, $width, %opts);
2349             }
2350 19         66 $self->crlf();
2351             }
2352              
2353 9 100       26 return ($text, $height) if wantarray();
2354 7         24 return $text;
2355             }
2356              
2357             # Deprecated former name
2358 0     0 1 0 sub section { return paragraphs(@_) }
2359              
2360             # Deprecated; merged into paragraph
2361 1     1 1 19 sub paragraphs { return paragraph(@_) }
2362              
2363             sub textlabel {
2364 0     0 0 0 my ($self, $x, $y, $font, $size, $text, %opts, $wht) = @_;
2365 0         0 my %trans_opts = (-translate => [$x,$y]);
2366 0         0 my %text_state;
2367 0 0       0 $trans_opts{'-rotate'} = $opts{'-rotate'} if $opts{'-rotate'};
2368              
2369 0         0 my $wastext = $self->_in_text_object();
2370 0 0       0 if ($wastext) {
2371 0         0 %text_state = $self->textstate();
2372 0         0 $self->textend();
2373             }
2374 0         0 $self->save();
2375 0         0 $self->textstart();
2376              
2377 0         0 $self->transform(%trans_opts);
2378              
2379 0 0       0 if ($opts{'-color'}) {
2380 0 0       0 my $color = ref($opts{'-color'}) ? @{$opts{'-color'}} : $opts{'-color'};
  0         0  
2381 0         0 $self->fillcolor($color);
2382             }
2383 0 0       0 if ($opts{'-strokecolor'}) {
2384             my $color = (ref($opts{'-strokecolor'})
2385 0         0 ? @{$opts{'-strokecolor'}}
2386 0 0       0 : $opts{'-strokecolor'});
2387 0         0 $self->strokecolor($color);
2388             }
2389              
2390 0         0 $self->font($font, $size);
2391              
2392 0 0       0 $self->charspace($opts{'-charspace'}) if $opts{'-charspace'};
2393 0 0       0 $self->hscale($opts{'-hscale'}) if $opts{'-hscale'};
2394 0 0       0 $self->wordspace($opts{'-wordspace'}) if $opts{'-wordspace'};
2395 0 0       0 $self->render($opts{'-render'}) if $opts{'-render'};
2396              
2397 0   0     0 my $align = $opts{'-align'} // 'left';
2398 0 0 0     0 if ($opts{'-right'} or $align =~ /^r/i) {
    0 0        
2399 0         0 $wht = $self->text_right($text, %opts);
2400             }
2401             elsif ($opts{'-center'} or $align =~ /^c/i) {
2402 0         0 $wht = $self->text_center($text, %opts);
2403             }
2404             else {
2405 0         0 $wht = $self->text($text, %opts);
2406             }
2407              
2408 0         0 $self->textend();
2409 0         0 $self->restore();
2410              
2411 0 0       0 if ($wastext) {
2412 0         0 $self->textstart();
2413 0         0 $self->textstate(%text_state);
2414             }
2415 0         0 return $wht;
2416             }
2417              
2418             =head2 text_width
2419              
2420             my $width = $content->text_width($line, %overrides);
2421              
2422             Returns the width of a line of text based on the current text state attributes.
2423             These can optionally be overridden:
2424              
2425             my $width = $content->text_width($line,
2426             font => $font,
2427             size => $size,
2428             character_spacing => $spacing,
2429             word_spacing => $spacing,
2430             hscale => $scale,
2431             );
2432              
2433             =cut
2434              
2435             # Deprecated (renamed)
2436 153     153 1 1479 sub advancewidth { return text_width(@_) }
2437              
2438             sub text_width {
2439 183     183 1 328 my ($self, $text, %opts) = @_;
2440 183 50 33     519 return 0 unless defined($text) and length($text);
2441              
2442             # Convert new names to old names
2443 183 50       379 if (exists $opts{'size'}) {
2444 0         0 $opts{'fontsize'} = delete $opts{'size'};
2445             }
2446 183 50       354 if (exists $opts{'character_spacing'}) {
2447 0         0 $opts{'charspace'} = delete $opts{'character_spacing'};
2448             }
2449 183 50       281 if (exists $opts{'word_spacing'}) {
2450 0         0 $opts{'charspace'} = delete $opts{'word_spacing'};
2451             }
2452              
2453 183         327 foreach my $k (qw(font fontsize wordspace charspace hscale)) {
2454 915 100       2153 $opts{$k} = $self->{" $k"} unless defined $opts{$k};
2455             }
2456              
2457             # Width of glyphs
2458 183         485 my $width = $opts{'font'}->width($text) * $opts{'fontsize'};
2459              
2460             # Width of space characters
2461 183         296 my $space_count = $text =~ y/\x20/\x20/;
2462 183         274 $width += $opts{'wordspace'} * $space_count;
2463              
2464             # Width of space between characters
2465 183         291 my $char_count = length($text);
2466 183         267 $width += $opts{'charspace'} * ($char_count - 1);
2467              
2468             # Horizontal scale multiplier
2469 183         296 $width *= $opts{'hscale'} / 100;
2470              
2471 183         591 return $width;
2472             }
2473              
2474             sub metaStart {
2475 0     0 0 0 my ($self, $tag, $obj) = @_;
2476 0         0 $self->add("/$tag");
2477 0 0       0 if (defined $obj) {
2478 0         0 my $dict = PDFDict();
2479 0         0 $dict->{'Metadata'} = $obj;
2480 0         0 $self->resource('Properties', $obj->name(), $dict);
2481 0         0 $self->add('/' . $obj->name());
2482 0         0 $self->add('BDC');
2483             }
2484             else {
2485 0         0 $self->add('BMC');
2486             }
2487 0         0 return $self;
2488             }
2489              
2490             sub metaEnd {
2491 0     0 0 0 my $self = shift();
2492 0         0 $self->add('EMC');
2493 0         0 return $self;
2494             }
2495              
2496             sub add_post {
2497 0     0 0 0 my $self = shift();
2498 0 0       0 if (@_) {
2499 0 0       0 unless ($self->{' poststream'} =~ /\s$/) {
2500 0         0 $self->{' poststream'} .= ' ';
2501             }
2502 0         0 $self->{' poststream'} .= join(' ', @_) . ' ';
2503             }
2504 0         0 return $self;
2505             }
2506              
2507             sub add {
2508 778     778 1 1212 my $self = shift();
2509 778 50       1732 if (@_) {
2510 778 100       2940 unless ($self->{' stream'} =~ /\s$/) {
2511 129         2702 $self->{' stream'} .= ' ';
2512             }
2513 778         5770 $self->{' stream'} .= encode('iso-8859-1', join(' ', @_) . ' ');
2514             }
2515 778         16667 return $self;
2516             }
2517              
2518             # Shortcut method for determining if we're inside a text object
2519             # (i.e. between BT and ET). See textstart and textend.
2520             sub _in_text_object {
2521 500     500   1031 my $self = shift();
2522 500         1358 return $self->{' apiistext'};
2523             }
2524              
2525             sub compressFlate {
2526 28     28 0 72 my $self = shift();
2527 28         130 $self->{'Filter'} = PDFArray(PDFName('FlateDecode'));
2528 28         86 $self->{'-docompress'} = 1;
2529 28         74 return $self;
2530             }
2531              
2532             sub textstart {
2533 19     19 0 41 my $self = shift();
2534 19 50       69 unless ($self->_in_text_object()) {
2535 19         87 $self->add(' BT ');
2536 19         40 $self->{' apiistext'} = 1;
2537 19         40 $self->{' font'} = undef;
2538 19         48 $self->{' fontset'} = 0;
2539 19         31 $self->{' fontsize'} = 0;
2540 19         32 $self->{' charspace'} = 0;
2541 19         41 $self->{' hscale'} = 100;
2542 19         39 $self->{' wordspace'} = 0;
2543 19         35 $self->{' leading'} = 0;
2544 19         29 $self->{' rise'} = 0;
2545 19         30 $self->{' render'} = 0;
2546 19         36 $self->{' textlinestart'} = 0;
2547 19         71 @{$self->{' matrix'}} = (1, 0, 0, 1, 0, 0);
  19         56  
2548 19         55 @{$self->{' textmatrix'}} = (1, 0, 0, 1, 0, 0);
  19         46  
2549 19         28 @{$self->{' textlinematrix'}} = (0, 0);
  19         42  
2550 19         43 @{$self->{' fillcolor'}} = 0;
  19         37  
2551 19         31 @{$self->{' strokecolor'}} = 0;
  19         36  
2552 19         26 @{$self->{' translate'}} = (0, 0);
  19         46  
2553 19         29 @{$self->{' scale'}} = (1, 1);
  19         75  
2554 19         29 @{$self->{' skew'}} = (0, 0);
  19         35  
2555 19         67 $self->{' rotate'} = 0;
2556             }
2557 19         44 return $self;
2558             }
2559              
2560             sub textend {
2561 123     123 0 228 my $self = shift();
2562 123 100       422 if ($self->_in_text_object()) {
2563 15         64 $self->add(' ET ', $self->{' poststream'});
2564 15         34 $self->{' apiistext'} = 0;
2565 15         33 $self->{' poststream'} = '';
2566             }
2567 123         248 return $self;
2568             }
2569              
2570             sub resource {
2571 31     31 0 110 my ($self, $type, $key, $obj, $force) = @_;
2572 31 100       120 if ($self->{' apipage'}) {
2573             # we are a content stream on a page.
2574 29         274 return $self->{' apipage'}->resource($type, $key, $obj, $force);
2575             }
2576             else {
2577             # we are a self-contained content stream.
2578 2   33     7 $self->{'Resources'} //= PDFDict();
2579              
2580 2         4 my $dict = $self->{'Resources'};
2581 2 50       9 $dict->realise() if ref($dict) =~ /Objind$/;
2582              
2583 2   33     16 $dict->{$type} ||= PDFDict();
2584 2 50       10 $dict->{$type}->realise() if ref($dict->{$type}) =~ /Objind$/;
2585 2 50       6 unless (defined $obj) {
2586 0           return $dict->{$type}->{$key};
2587             }
2588             else {
2589 2 50       6 if ($force) {
2590 0         0 $dict->{$type}->{$key} = $obj;
2591             }
2592             else {
2593 2   33     13 $dict->{$type}->{$key} //= $obj;
2594             }
2595 2         5 return $dict;
2596             }
2597             }
2598             }
2599              
2600             =head1 MIGRATION
2601              
2602             See L for an overview.
2603              
2604             =over
2605              
2606             =item transform(%hyphen_prefixed_options);
2607              
2608             Remove hyphens from option names (C<-translate> becomes C, etc.).
2609              
2610             =item transform_rel
2611              
2612             Replace with L, setting option C to true. Remove
2613             hyphens from the names of other options.
2614              
2615             =item linewidth
2616              
2617             Replace with L.
2618              
2619             =item linecap
2620              
2621             Replace with L.
2622              
2623             =item linejoin
2624              
2625             Replace with L.
2626              
2627             =item meterlimit
2628              
2629             =item miterlimit
2630              
2631             Replace with L.
2632              
2633             =item linedash
2634              
2635             Replace with L. Remove hyphens from option names. Rename
2636             C<-shift> to C.
2637              
2638             =item flatness
2639              
2640             Replace with L.
2641              
2642             =item poly
2643              
2644             Replace with L (first two arguments) and L (remaining
2645             arguments).
2646              
2647             =item endpath
2648              
2649             Replace with L.
2650              
2651             =item rect
2652              
2653             Replace with L, converting the C<$w> (third) and C<$h> (fourth)
2654             arguments to the X and Y values of the upper-right corner:
2655              
2656             # Old
2657             $content->rect($x, $y, $w, $h);
2658              
2659             # New
2660             $content->rectangle($x, $y, $x + $w, $y + $h);
2661              
2662             =item rectxy
2663              
2664             Replace with L.
2665              
2666             =item fill(1)
2667              
2668             Replace with C<$content-Efill(rule =E 'even-odd')>.
2669              
2670             =item fillstroke
2671              
2672             Replace with L.
2673              
2674             =item clip(1)
2675              
2676             Replace with C<$content-Eclip(rule =E 'even-odd')>.
2677              
2678             =item image
2679              
2680             =item formimage
2681              
2682             Replace with L.
2683              
2684             =item charspace
2685              
2686             Replace with L.
2687              
2688             =item wordspace
2689              
2690             Replace with L.
2691              
2692             =item hspace
2693              
2694             Replace with L.
2695              
2696             =item lead
2697              
2698             Replace with L.
2699              
2700             =item distance
2701              
2702             Replace with L.
2703              
2704             =item cr
2705              
2706             Replace with either L (if called with arguments) or L (if
2707             called without arguments).
2708              
2709             =item nl
2710              
2711             Replace with L.
2712              
2713             =item text(%hyphen_prefixed_options)
2714              
2715             Remove initial hyphens from option names.
2716              
2717             =item text_center
2718              
2719             Replace with L, setting C to C
.
2720              
2721             =item text_right
2722              
2723             Replace with L, setting C to C.
2724              
2725             =item paragraph(%hyphen_prefixed_options)
2726              
2727             Remove initial hyphens from option names. C<-align-last> becomes C.
2728              
2729             =item section
2730              
2731             =item paragraphs
2732              
2733             Replace with L.
2734              
2735             =item advancewidth
2736              
2737             Replace with L.
2738              
2739             =back
2740              
2741             =cut
2742              
2743             1;