File Coverage

blib/lib/PDF/Builder/Content.pm
Criterion Covered Total %
statement 988 1772 55.7
branch 310 710 43.6
condition 98 362 27.0
subroutine 121 149 81.2
pod 89 92 96.7
total 1606 3085 52.0


line stmt bran cond sub pod time code
1             package PDF::Builder::Content;
2              
3 39     39   300 use base 'PDF::Builder::Basic::PDF::Dict';
  39         105  
  39         5358  
4              
5 39     39   394 use strict;
  39         97  
  39         1029  
6 39     39   186 use warnings;
  39         88  
  39         3226  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.028'; # manually update whenever code is changed
10              
11 39     39   301 use Carp;
  39         91  
  39         3273  
12 39     39   285 use Compress::Zlib qw();
  39         92  
  39         1177  
13 39     39   228 use Encode;
  39         88  
  39         4100  
14 39     39   287 use Math::Trig; # CAUTION: deg2rad(0) = deg2rad(360) = 0!
  39         89  
  39         9722  
15 39     39   334 use List::Util qw(min max);
  39         94  
  39         3112  
16 39     39   20604 use PDF::Builder::Matrix;
  39         363  
  39         1694  
17              
18 39     39   301 use PDF::Builder::Basic::PDF::Utils;
  39         98  
  39         3963  
19 39     39   285 use PDF::Builder::Util;
  39         101  
  39         6844  
20 39     39   51903 use PDF::Builder::Content::Text;
  39         216  
  39         1006058  
21              
22             # unless otherwise noted, routines beginning with _ are internal helper
23             # functions and should not be used by others
24             #
25             =head1 NAME
26              
27             PDF::Builder::Content - Methods for adding graphics and text to a PDF
28              
29             Inherits from L<PDF::Builder::Basic::PDF::Dict>
30              
31             =head1 SYNOPSIS
32              
33             # Start with a PDF page (new or opened)
34             my $pdf = PDF::Builder->new();
35             my $page = $pdf->page();
36              
37             # Add new content object(s)
38             my $content = $page->graphics(); # or gfx()
39             # and/or (as separate object name)
40             my $content = $page->text();
41              
42             # Then call the methods below to add graphics and text to the page.
43             # Note that negative coordinates can have unpredictable effects, so
44             # keep your coordinates non-negative!
45              
46             These methods add content to I<streams> output for text or graphics objects.
47             Unless otherwise restricted by a check that we are in or out of text mode,
48             many methods listed here apply equally to text and graphics streams. It is
49             possible that there I<are> some which have no effect in one stream type or
50             the other, but are currently lacking a check to prevent them from being
51             inserted into an inapplicable stream.
52              
53             =head1 METHODS
54              
55             All public methods listed, I<except as otherwise noted,> return C<$self>,
56             for ease of chaining calls.
57              
58             =cut
59              
60             sub new {
61 152     152 1 497 my ($class) = @_;
62              
63 152         1026 my $self = $class->SUPER::new(@_);
64 152         626 $self->{' stream'} = '';
65 152         596 $self->{' poststream'} = '';
66 152         509 $self->{' font'} = undef;
67 152         480 $self->{' fontset'} = 0;
68 152         632 $self->{' fontsize'} = 0;
69 152         435 $self->{' charspace'} = 0;
70 152         431 $self->{' hscale'} = 100;
71 152         403 $self->{' wordspace'} = 0;
72 152         432 $self->{' leading'} = 0;
73 152         568 $self->{' rise'} = 0;
74 152         1106 $self->{' render'} = 0;
75 152         774 $self->{' matrix'} = [1,0,0,1,0,0];
76 152         733 $self->{' textmatrix'} = [1,0,0,1,0,0];
77 152         591 $self->{' textlinematrix'} = [0,0];
78 152         482 $self->{' textlinestart'} = 0;
79 152         632 $self->{' fillcolor'} = [0];
80 152         523 $self->{' strokecolor'} = [0];
81 152         595 $self->{' translate'} = [0,0];
82 152         690 $self->{' scale'} = [1,1];
83 152         580 $self->{' skew'} = [0,0];
84 152         623 $self->{' rotate'} = 0;
85 152         536 $self->{' linewidth'} = 1; # see also gs LW
86 152         502 $self->{' linecap'} = 0; # see also gs LC
87 152         436 $self->{' linejoin'} = 0; # see also gs LJ
88 152         458 $self->{' miterlimit'} = 10; # see also gs ML
89 152         670 $self->{' linedash'} = [[],0]; # see also gs D
90 152         469 $self->{' flatness'} = 1; # see also gs FL
91 152         606 $self->{' apiistext'} = 0;
92 152         460 $self->{' openglyphlist'} = 0;
93             # hold only latest of multiple instances
94 152         448 $self->{' doPending'} = 0; # DISABLE for now
95 152         449 $self->{' Tpending'} = ();
96 152         635 $self->{' Tpending'}{'Tm'} = '';
97 152         498 $self->{' Tpending'}{'Tf'} = '';
98             # Td (and T*) are relative positioning, so don't buffer
99             #$self->{' Tpending'}{'Td'} = '';
100 152         568 $self->{' Tpending'}{'color'} = ''; # rg, g, k, etc.
101 152         569 $self->{' Tpending'}{'Color'} = ''; # RG, G, K, etc.
102 152         489 $self->{' Gpending'} = ();
103 152         513 $self->{' Gpending'}{'color'} = ''; # rg, g, k, etc.
104 152         433 $self->{' Gpending'}{'Color'} = ''; # RG, G, K, etc.
105             # consider line width, dash pattern, linejoin, linecap, etc.
106              
107 152         526 return $self;
108             }
109              
110             # internal helper method
111             sub outobjdeep {
112 143     143 1 452 my $self = shift();
113              
114 143         835 $self->textend();
115             # foreach my $k (qw[ api apipdf apiistext apipage font fontset fontsize
116             # charspace hscale wordspace leading rise render matrix
117             # textmatrix textlinematrix fillcolor strokecolor
118             # translate scale skew rotate ]) {
119             # $self->{" $k"} = undef;
120             # delete($self->{" $k"});
121             # }
122 143 50 66     646 if ($self->{'-docompress'} && $self->{'Filter'}) {
123 6         54 $self->{' stream'} = Compress::Zlib::compress($self->{' stream'});
124 6         2090 $self->{' nofilt'} = 1;
125 6         26 delete $self->{'-docompress'};
126             }
127 143         701 return $self->SUPER::outobjdeep(@_);
128             }
129              
130             =head2 Coordinate Transformations
131              
132             The methods in this section change the coordinate system for the
133             current content object relative to the rest of the document.
134             B<Note:> the changes are relative to the I<original> page coordinates (and
135             thus, absolute), not to the previous position! Thus, C<translate(10, 10);
136             translate(10, 10);> ends up only moving the origin to C<[10, 10]>, rather than
137             to C<[20, 20]>. There is one call, C<transform_rel()>, which makes your changes
138             I<relative> to the previous position.
139              
140             If you call more than one of these methods, the PDF specification
141             recommends calling them in the following order: translate, rotate,
142             scale, skew. Each change builds on the last, and you can get
143             unexpected results when calling them in a different order.
144              
145             B<CAUTION:> a I<text> object ($content) behaves a bit differently. Individual
146             translate, rotate, scale, and skew calls I<cancel out> any previous settings.
147             If you want to combine multiple transformations for text, use the C<transform>
148             call.
149              
150             =head3 translate
151              
152             $content->translate($dx,$dy)
153              
154             =over
155              
156             Moves the origin along the x and y axes to
157             C<$dx> and C<$dy> respectively.
158              
159             =back
160              
161             =cut
162              
163             sub _translate {
164 12     12   32 my ($x,$y) = @_;
165              
166 12         33 return (1,0,0,1, $x,$y);
167             }
168              
169             # transform in turn calls _translate
170             sub translate {
171 2     2 1 9 my ($self, $x,$y) = @_;
172              
173 2         13 $self->transform('translate' => [$x,$y]);
174              
175 2         5 return $self;
176             }
177              
178             =head3 rotate
179              
180             $content->rotate($degrees)
181              
182             =over
183              
184             Rotates the coordinate system counter-clockwise (anti-clockwise) around the
185             current origin. Use a negative argument to rotate clockwise. Note that 360
186             degrees will be treated as 0 degrees.
187              
188             B<Note:> Unless you have already moved (translated) the origin, it is, and will
189             remain, at the lower left corner of the visible sheet. It will I<not>
190             automatically shift to another corner. For example, a rotation of +90 degrees
191             (counter-clockwise) will leave the entire visible sheet in negative Y territory (0 at the left edge, -original_width at the right edge), while X remains in
192             positive territory (0 at bottom, +original_height at the top edge).
193              
194             This C<rotate()> call permits any angle. Do not confuse it with the I<page>
195             rotation C<rotate> call, which only permits increments of 90 degrees (with
196             opposite sign!), but I<does> shift the origin to another corner of the sheet.
197              
198             =back
199              
200             =cut
201              
202             sub _rotate {
203 9     9   20 my ($deg) = @_;
204              
205 9         30 return (cos(deg2rad($deg)), sin(deg2rad($deg)), -sin(deg2rad($deg)), cos(deg2rad($deg)), 0,0);
206             }
207              
208             # transform in turn calls _rotate
209             sub rotate {
210 1     1 1 7 my ($self, $deg) = @_;
211              
212 1         4 $self->transform('rotate' => $deg);
213              
214 1         2 return $self;
215             }
216              
217             =head3 scale
218              
219             $content->scale($sx,$sy)
220              
221             =over
222              
223             Scales (stretches) the coordinate systems along the x and y axes.
224             Separate multipliers are provided for x and y.
225              
226             =back
227              
228             =cut
229              
230             sub _scale {
231 9     9   29 my ($sx,$sy) = @_;
232              
233 9         30 return ($sx,0,0,$sy, 0,0);
234             }
235              
236             # transform in turn calls _scale
237             sub scale {
238 1     1 1 9 my ($self, $sx,$sy) = @_;
239              
240 1         7 $self->transform('scale' => [$sx,$sy]);
241              
242 1         4 return $self;
243             }
244              
245             =head3 skew
246              
247             $content->skew($skx,$sky)
248              
249             =over
250              
251             Skews the coordinate system by C<$skx> degrees
252             (counter-clockwise/anti-clockwise) from
253             the x axis I<and> C<$sky> degrees (clockwise) from the y axis.
254             Note that 360 degrees will be treated the same as 0 degrees.
255              
256             =back
257              
258             =cut
259              
260             sub _skew {
261 9     9   26 my ($skx,$sky) = @_;
262              
263 9         53 return (1, tan(deg2rad($skx)), tan(deg2rad($sky)), 1, 0,0);
264             }
265              
266             # transform in turn calls _skew
267             sub skew {
268 1     1 1 9 my ($self, $skx,$sky) = @_;
269              
270 1         7 $self->transform('skew' => [$skx,$sky]);
271              
272 1         5 return $self;
273             }
274              
275             =head3 transform
276              
277             $content->transform(%opts)
278              
279             =over
280              
281             Use one or more of the given %opts:
282              
283             $content->transform(
284             'translate' => [$dx,$dy],
285             'rotate' => $degrees,
286             'scale' => [$sx,$sy],
287             'skew' => [$skx,$sky],
288             'matrix' => [$a, $b, $c, $d, $e, $f],
289             'point' => [$x,$y]
290             'repeat' => $boolean
291             )
292              
293             A six element list may be given (C<matrix>) for a
294             further transformation matrix:
295              
296             $a = cos(rot) * scale factor for X
297             $b = sin(rot) * tan(skew for X)
298             $c = -sin(rot) * tan(skew for Y)
299             $d = cos(rot) * scale factor for Y
300             $e = translation for X
301             $f = translation for Y
302              
303             Performs multiple coordinate transformations in one call, in the order
304             recommended by the PDF specification (translate, rotate, scale, skew).
305             This is equivalent to making each transformation separately, I<in the
306             indicated order>.
307             A matrix of 6 values may also be given (C<matrix>). The transformation matrix
308             is updated.
309             A C<point> may be given (a point to be multiplied [transformed] by the
310             completed matrix).
311             Omitted options will be unchanged.
312              
313             If C<repeat> is true, and if this is not the first call to a transformation
314             method, the previous transformation will be performed again, modified by any
315             other provided arguments.
316              
317             =back
318              
319             =cut
320              
321             sub _transform {
322 15     15   49 my (%opts) = @_;
323             # user should not be calling this routine directly, but only via transform()
324              
325             # start with "no-op" identity matrix
326 15         167 my $mtx = PDF::Builder::Matrix->new([1,0,0], [0,1,0], [0,0,1]);
327             # note order of operations, compared to PDF spec
328 15         69 foreach my $o (qw( matrix skew scale rotate translate )) {
329 75 100       177 next unless defined $opts{$o};
330              
331 39 100       178 if ($o eq 'translate') {
    100          
    100          
    50          
    0          
332 12         34 my @mx = _translate(@{$opts{$o}});
  12         59  
333 12         56 $mtx = $mtx->multiply(PDF::Builder::Matrix->new(
334             [$mx[0],$mx[1],0],
335             [$mx[2],$mx[3],0],
336             [$mx[4],$mx[5],1]
337             ));
338             } elsif ($o eq 'rotate') {
339 9         32 my @mx = _rotate($opts{$o});
340 9         251 $mtx = $mtx->multiply(PDF::Builder::Matrix->new(
341             [$mx[0],$mx[1],0],
342             [$mx[2],$mx[3],0],
343             [$mx[4],$mx[5],1]
344             ));
345             } elsif ($o eq 'scale') {
346 9         15 my @mx = _scale(@{$opts{$o}});
  9         34  
347 9         45 $mtx = $mtx->multiply(PDF::Builder::Matrix->new(
348             [$mx[0],$mx[1],0],
349             [$mx[2],$mx[3],0],
350             [$mx[4],$mx[5],1]
351             ));
352             } elsif ($o eq 'skew') {
353 9         18 my @mx = _skew(@{$opts{$o}});
  9         36  
354 9         661 $mtx = $mtx->multiply(PDF::Builder::Matrix->new(
355             [$mx[0],$mx[1],0],
356             [$mx[2],$mx[3],0],
357             [$mx[4],$mx[5],1]
358             ));
359             } elsif ($o eq 'matrix') {
360 0         0 my @mx = @{$opts{$o}}; # no check that 6 elements given
  0         0  
361 0         0 $mtx = $mtx->multiply(PDF::Builder::Matrix->new(
362             [$mx[0],$mx[1],0],
363             [$mx[2],$mx[3],0],
364             [$mx[4],$mx[5],1]
365             ));
366             }
367             }
368 15 50       49 if ($opts{'point'}) {
369 0         0 my $mp = PDF::Builder::Matrix->new([$opts{'point'}->[0], $opts{'point'}->[1], 1]);
370 0         0 $mp = $mp->multiply($mtx);
371 0         0 return ($mp->[0][0], $mp->[0][1]);
372             }
373              
374             # if not point
375             return (
376 15         136 $mtx->[0][0],$mtx->[0][1],
377             $mtx->[1][0],$mtx->[1][1],
378             $mtx->[2][0],$mtx->[2][1]
379             );
380             }
381              
382             sub transform {
383 16     16 1 1152 my ($self, %opts) = @_;
384             # copy dashed option names to preferred undashed names
385 16 100 66     80 if ($opts{'-translate'} && !defined $opts{'translate'}) { $opts{'translate'} = delete($opts{'-translate'}); }
  7         214  
386 16 100 66     71 if ($opts{'-rotate'} && !defined $opts{'rotate'}) { $opts{'rotate'} = delete($opts{'-rotate'}); }
  7         20  
387 16 100 66     62 if ($opts{'-scale'} && !defined $opts{'scale'}) { $opts{'scale'} = delete($opts{'-scale'}); }
  7         16  
388 16 100 66     62 if ($opts{'-skew'} && !defined $opts{'skew'}) { $opts{'skew'} = delete($opts{'-skew'}); }
  7         15  
389 16 50 33     57 if ($opts{'-point'} && !defined $opts{'point'}) { $opts{'point'} = delete($opts{'-point'}); }
  0         0  
390 16 50 33     49 if ($opts{'-matrix'} && !defined $opts{'matrix'}) { $opts{'matrix'} = delete($opts{'-matrix'}); }
  0         0  
391 16 50 33     54 if ($opts{'-repeat'} && !defined $opts{'repeat'}) { $opts{'repeat'} = delete($opts{'-repeat'}); }
  0         0  
392              
393             # 'repeat' changes mode to relative
394 16 100       47 return $self->transform_rel(%opts) if $opts{'repeat'};
395              
396             # includes point and matrix operations
397 15         101 $self->matrix(_transform(%opts));
398              
399 15 100       62 if ($opts{'translate'}) {
400 12         20 @{$self->{' translate'}} = @{$opts{'translate'}};
  12         52  
  12         28  
401             } else {
402 3         8 @{$self->{' translate'}} = (0,0);
  3         10  
403             }
404              
405 15 100       41 if ($opts{'rotate'}) {
406 9         28 $self->{' rotate'} = $opts{'rotate'};
407             } else {
408 6         37 $self->{' rotate'} = 0;
409             }
410              
411 15 100       35 if ($opts{'scale'}) {
412 9         18 @{$self->{' scale'}} = @{$opts{'scale'}};
  9         27  
  9         20  
413             } else {
414 6         12 @{$self->{' scale'}} = (1,1);
  6         13  
415             }
416              
417 15 100       44 if ($opts{'skew'}) {
418 9         26 @{$self->{' skew'}} = @{$opts{'skew'}};
  9         43  
  9         19  
419             } else {
420 6         8 @{$self->{' skew'}} = (0,0);
  6         12  
421             }
422              
423 15         60 return $self;
424             }
425              
426             =head3 transform_rel
427              
428             $content->transform_rel(%opts)
429              
430             =over
431              
432             Makes transformations similarly to C<transform>, except that it I<adds>
433             to the previously set values, rather than I<replacing> them (except for
434             I<scale>, which B<multiplies> the new values with the old).
435              
436             Unlike C<transform>, C<matrix> and C<point> are not supported.
437              
438             =back
439              
440             =cut
441              
442             sub transform_rel {
443 2     2 1 30 my ($self, %opts) = @_;
444             # copy dashed option names to preferred undashed names
445 2 100 66     17 if (defined $opts{'-skew'} && !defined $opts{'skew'}) { $opts{'skew'} = delete($opts{'-skew'}); }
  1         3  
446 2 100 66     11 if (defined $opts{'-scale'} && !defined $opts{'scale'}) { $opts{'scale'} = delete($opts{'-scale'}); }
  1         3  
447 2 100 66     15 if (defined $opts{'-rotate'} && !defined $opts{'rotate'}) { $opts{'rotate'} = delete($opts{'-rotate'}); }
  1         4  
448 2 100 66     20 if (defined $opts{'-translate'} && !defined $opts{'translate'}) { $opts{'translate'} = delete($opts{'-translate'}); }
  1         6  
449              
450 2 50       6 my ($sa1,$sb1) = @{$opts{'skew'} ? $opts{'skew'} : [0,0]};
  2         11  
451 2         5 my ($sa0,$sb0) = @{$self->{" skew"}};
  2         6  
452              
453 2 50       20 my ($sx1,$sy1) = @{$opts{'scale'} ? $opts{'scale'} : [1,1]};
  2         11  
454 2         3 my ($sx0,$sy0) = @{$self->{" scale"}};
  2         6  
455              
456 2   50     9 my $rot1 = $opts{'rotate'} || 0;
457 2         5 my $rot0 = $self->{" rotate"};
458              
459 2 50       4 my ($tx1,$ty1) = @{$opts{'translate'} ? $opts{'translate'} : [0,0]};
  2         20  
460 2         5 my ($tx0,$ty0) = @{$self->{" translate"}};
  2         6  
461              
462 2         33 $self->transform(
463             'skew' => [$sa0+$sa1, $sb0+$sb1],
464             'scale' => [$sx0*$sx1, $sy0*$sy1],
465             'rotate' => $rot0+$rot1,
466             'translate' => [$tx0+$tx1, $ty0+$ty1]
467             );
468              
469 2         12 return $self;
470             }
471              
472             =head3 matrix
473              
474             $content->matrix($a, $b, $c, $d, $e, $f)
475              
476             =over
477              
478             I<(Advanced)> Sets the current transformation matrix manually. Unless
479             you have a particular need to enter transformations manually, you
480             should use the C<transform> method instead.
481              
482             $a = cos(rot) * scale factor for X
483             $b = sin(rot) * tan(skew for X)
484             $c = -sin(rot) * tan(skew for Y)
485             $d = cos(rot) * scale factor for Y
486             $e = translation for X
487             $f = translation for Y
488              
489             In text mode, the text matrix is B<returned>.
490             In graphics mode, C<$self> is B<returned>.
491              
492             =back
493              
494             =cut
495              
496             sub _matrix_text {
497 3     3   9 my ($a, $b, $c, $d, $e, $f) = @_;
498              
499             #return (floats($a, $b, $c, $d, $e, $f), 'Tm');
500             return
501 3         11 float($a).' '.float($b).' '.float($c).' '.float($d).' '.
502             float($e).' '.float($f).' Tm';
503             }
504              
505             sub _matrix_gfx {
506 23     23   80 my ($a, $b, $c, $d, $e, $f) = @_;
507              
508 23         169 return (floats($a, $b, $c, $d, $e, $f), 'cm');
509             }
510              
511             # internal helper method
512             sub matrix_update {
513 75     75 0 163 my ($self, $tx,$ty) = @_;
514              
515 75         177 $self->{' textlinematrix'}->[0] += $tx;
516 75         161 $self->{' textlinematrix'}->[1] += $ty;
517 75         119 return $self;
518             }
519              
520             sub matrix {
521 26     26 1 98 my ($self, $a, $b, $c, $d, $e, $f) = @_;
522              
523 26 50       109 if (defined $a) {
524 26 100       97 if ($self->_in_text_object()) {
525             # in text mode, buffer the Tm output
526 3 50       6 if ($self->{' doPending'}) {
527 0         0 $self->{' Tpending'}{'Tm'} = _matrix_text($a, $b, $c, $d, $e, $f);
528             } else {
529 3         12 $self->add(_matrix_text($a, $b, $c, $d, $e, $f));
530             }
531 3         6 @{$self->{' textmatrix'}} = ($a, $b, $c, $d, $e, $f);
  3         12  
532 3         19 @{$self->{' textlinematrix'}} = (0,0);
  3         8  
533             } else {
534             # in graphics mode, directly output cm
535 23         124 $self->add(_matrix_gfx($a, $b, $c, $d, $e, $f));
536             }
537             }
538 26 100       122 if ($self->_in_text_object()) {
539 3         4 return @{$self->{' textmatrix'}};
  3         8  
540             } else {
541 23         58 return $self;
542             }
543             }
544              
545             =head2 Graphics State Parameters
546              
547             The following calls also affect the B<text> state.
548              
549             =head3 linewidth, line_width
550              
551             $content->linewidth($width)
552              
553             =over
554              
555             Sets the width of the stroke (in points). This is the line drawn in graphics
556             mode, or the I<outline> of a character in text mode (with appropriate C<render>
557             mode). If no C<$width> is given, the current setting is B<returned>. If the
558             width is being set, C<$self> is B<returned> so that calls may be chained.
559              
560             B<Alternate name:> C<line_width>
561              
562             This is provided for compatibility with PDF::API2.
563              
564             =back
565              
566             =cut
567              
568             sub _linewidth {
569 89     89   146 my ($linewidth) = @_;
570              
571 89         307 return ($linewidth, 'w');
572             }
573              
574 1     1 1 10 sub line_width { return linewidth(@_); } ## no critic
575              
576             sub linewidth {
577 89     89 1 200 my ($self, $linewidth) = @_;
578              
579 89 50       210 if (!defined $linewidth) {
580 0         0 return $self->{' linewidth'};
581             }
582 89         209 $self->add(_linewidth($linewidth));
583 89         203 $self->{' linewidth'} = $linewidth;
584              
585 89         196 return $self;
586             }
587              
588             =head3 linecap, line_cap
589              
590             $content->linecap($style)
591              
592             =over
593              
594             Sets the style to be used at the end of a stroke. This applies to lines
595             which come to a free-floating end, I<not> to "joins" ("corners") in
596             polylines (see C<linejoin>).
597              
598             B<Alternate name:> C<line_cap>
599              
600             This is provided for compatibility with PDF::API2.
601              
602             =over
603              
604             =item "butt" or "b" or 0 = Butt Cap (default)
605              
606             The stroke ends at the end of the path, with no projection.
607              
608             =item "round" or "r" or 1 = Round Cap
609              
610             A semicircular arc is drawn around the end of the path with a diameter equal to
611             the line width, and is filled in.
612              
613             =item "square" or "s" or 2 = Projecting Square Cap
614              
615             The stroke continues past the end of the path for half the line width.
616              
617             =back
618              
619             If no C<$style> is given, the current setting is B<returned>. If the style is
620             being set, C<$self> is B<returned> so that calls may be chained.
621              
622             Either a number or a string (case-insensitive) may be given.
623              
624             =back
625              
626             =cut
627              
628             sub _linecap {
629 3     3   10 my ($linecap) = @_;
630              
631 3         21 return ($linecap, 'J');
632             }
633              
634 1     1 1 11 sub line_cap { return linecap(@_); } ## no critic
635              
636             sub linecap {
637 3     3 1 19 my ($self, $linecap) = @_;
638              
639 3 50       16 if (!defined $linecap) { # Get
640 0         0 return $self->{' linecap'};
641             }
642              
643             # Set
644 3   50     18 my $style = lc($linecap) // 0; # could be number or string
645 3 50 33     26 $style = 0 if $style eq 'butt' or $style eq 'b';
646 3 50 33     22 $style = 1 if $style eq 'round' or $style eq 'r';
647 3 50 33     22 $style = 2 if $style eq 'square' or $style eq 's';
648 3 50 33     23 unless ($style >= 0 && $style <= 2) {
649 0         0 carp "Unknown line cap style '$linecap', using 0 instead";
650 0         0 $style = 0;
651             }
652              
653 3         13 $self->add(_linecap($style));
654 3         10 $self->{' linecap'} = $style;
655              
656 3         8 return $self;
657             }
658              
659             =head3 linejoin, line_join
660              
661             $content->linejoin($style)
662              
663             =over
664              
665             Sets the style of join to be used at corners of a path
666             (within a multisegment polyline).
667              
668             B<Alternate name:> C<line_join>
669              
670             This is provided for compatibility with PDF::API2.
671              
672             =over
673              
674             =item "miter" or "m" or 0 = Miter Join, default
675              
676             The outer edges of the strokes extend until they meet, up to the limit
677             specified by I<miterlimit>. If the limit would be surpassed, a I<bevel> join
678             is used instead. For a given linewidth, the more acute the angle is (closer
679             to 0 degrees), the higher the ratio of miter length to linewidth will be, and
680             that's what I<miterlimit> controls -- a very "pointy" join is replaced by
681             a bevel.
682              
683             =item "round" or "r" or 1 = Round Join
684              
685             A filled circle with a diameter equal to the I<linewidth> is drawn around the
686             corner point, producing a rounded corner. The arc will meet up with the sides
687             of the line in a smooth tangent.
688              
689             =item "bevel" or "b" or 2 = Bevel Join
690              
691             A filled triangle is drawn to fill in the notch between the two strokes.
692              
693             =back
694              
695             If no C<$style> is given, the current setting is B<returned>. If the style is
696             being set, C<$self> is B<returned> so that calls may be chained.
697              
698             Either a number or a string (case-insensitive) may be given.
699              
700             =back
701              
702             =cut
703              
704             sub _linejoin {
705 3     3   10 my ($style) = @_;
706              
707 3         23 return ($style, 'j');
708             }
709              
710 1     1 1 12 sub line_join { return linejoin(@_); } ## no critic
711              
712             sub linejoin {
713 3     3 1 24 my ($self, $linejoin) = @_;
714              
715 3 50       16 if (!defined $linejoin) { # Get
716 0         0 return $self->{' linejoin'};
717             }
718              
719             # Set
720 3   50     20 my $style = lc($linejoin) // 0; # could be number or string
721 3 50 33     25 $style = 0 if $style eq 'miter' or $style eq 'm';
722 3 50 33     25 $style = 1 if $style eq 'round' or $style eq 'r';
723 3 50 33     24 $style = 2 if $style eq 'bevel' or $style eq 'b';
724 3 50 33     26 unless ($style >= 0 && $style <= 2) {
725 0         0 carp "Unknown line join style '$linejoin', using 0 instead";
726 0         0 $style = 0;
727             }
728              
729 3         13 $self->add(_linejoin($style));
730 3         11 $self->{' linejoin'} = $style;
731              
732 3         11 return $self;
733             }
734              
735             =head3 miterlimit, miter_limit
736              
737             $content->miterlimit($ratio)
738              
739             =over
740              
741             Sets the miter limit when the line join style is a I<miter> join.
742              
743             The ratio is the maximum length of the miter (inner to outer corner) divided
744             by the line width. Any miter above this ratio will be converted to a I<bevel>
745             join. The practical effect is that lines meeting at shallow
746             angles are chopped off instead of producing long pointed corners.
747              
748             The default miter limit is 10.0 (approximately 11.5 degree cutoff angle).
749             The smaller the limit, the larger the cutoff angle.
750              
751             If no C<$ratio> is given, the current setting is B<returned>. If the ratio is
752             being set, C<$self> is B<returned> so that calls may be chained.
753              
754             B<Alternate name:> C<miter_limit>
755              
756             This is provided for compatibility with PDF::API2.
757             Long ago, in a distant galaxy, this method was misnamed I<meterlimit>, but
758             that was removed a while ago. Any code using that name should be updated!
759              
760             =back
761              
762             =cut
763              
764             sub _miterlimit {
765 3     3   8 my ($ratio) = @_;
766              
767 3         26 return ($ratio, 'M');
768             }
769              
770 1     1 1 10 sub miter_limit { return miterlimit(@_); } ## no critic
771              
772             sub miterlimit {
773 3     3 1 25 my ($self, $ratio) = @_;
774              
775 3 50       13 if (!defined $ratio) {
776 0         0 return $self->{' miterlimit'};
777             }
778 3         14 $self->add(_miterlimit($ratio));
779 3         9 $self->{' miterlimit'} = $ratio;
780              
781 3         9 return $self;
782             }
783              
784             # Note: miterlimit was originally named incorrectly to meterlimit, renamed.
785             # is available in PDF::API2
786              
787             =head3 linedash, line_dash_pattern
788              
789             $content->linedash()
790              
791             $content->linedash($length)
792              
793             $content->linedash($dash_length, $gap_length, ...)
794              
795             $content->linedash('pattern' => [$dash_length, $gap_length, ...], 'shift' => $offset)
796              
797             =over
798              
799             Sets the line dash pattern.
800              
801             If called without any arguments, a solid line will be drawn.
802              
803             If called with one argument, the dashes and gaps (strokes and
804             spaces) will have equal lengths.
805              
806             If called with two or more arguments, the arguments represent
807             alternating dash and gap lengths.
808              
809             If called with a hash of arguments, the I<pattern> array may have one or
810             more elements, specifying the dash and gap lengths.
811             A dash phase may be set (I<shift>), which is a B<positive integer>
812             specifying the distance into the pattern at which to start the dashed line.
813             Note that if you wish to give a I<shift> amount, using C<shift>,
814             you need to use C<pattern> instead of one or two elements.
815              
816             If an B<odd> number of dash array elements are given, the list is repeated by
817             the reader software to form an even number of elements (pairs).
818              
819             If a single argument of B<-1> is given, the current setting is B<returned>.
820             This is an array consisting of two elements: an anonymous array containing the
821             dash pattern (default: empty), and the shift (offset) amount (default: 0).
822             It may be used directly in a linedash() call, as linedash will recognize the
823             special pattern [ array, number ].
824              
825             If the dash pattern is being I<set>, C<$self> is B<returned> so that calls may
826             be chained.
827              
828             B<Alternate name:> C<line_dash_pattern>
829              
830             This is provided for compatibility with PDF::API2.
831              
832             =back
833              
834             =cut
835              
836             sub _linedash {
837 11     11   32 my ($self, @pat) = @_;
838              
839 11 100       31 unless (@pat) { # no args
840 7         28 $self->{' linedash'} = [[],0];
841 7         51 return ('[', ']', '0', 'd');
842             } else {
843 4 100 66     35 if ($pat[0] =~ /^\-?pattern/ || $pat[0] =~ /^\-?shift/) {
844 1         5 my %pat = @pat;
845             # copy dashed option names to preferred undashed names
846 1 50 33     10 if (defined $pat{'-pattern'} && !defined $pat{'pattern'}) { $pat{'pattern'} = delete($pat{'-pattern'}); }
  1         4  
847 1 50 33     8 if (defined $pat{'-shift'} && !defined $pat{'shift'}) { $pat{'shift'} = delete($pat{'-shift'}); }
  1         3  
848              
849             # Note: use pattern to replace the old -full and -clear options
850             # which are NOT implemented
851 1   50     2 $self->{' linedash'} = [[@{$pat{'pattern'}}],($pat{'shift'} || 0)];
  1         10  
852 1   50     2 return ('[', floats(@{$pat{'pattern'}}), ']', ($pat{'shift'} || 0), 'd');
  1         7  
853             } else {
854 3         31 $self->{' linedash'} = [[@pat],0];
855 3         20 return ('[', floats(@pat), '] 0 d');
856             }
857             }
858             }
859              
860 1     1 1 11 sub line_dash_pattern { return linedash(@_); } ## no critic
861              
862             sub linedash {
863 11     11 1 60 my ($self, @pat) = @_;
864              
865             # request existing pattern and offset?
866 11 50 66     65 if (scalar @pat == 1 && $pat[0] == -1) {
867 0         0 return @{$self->{' linedash'}};
  0         0  
868             }
869             # request to restore stored pattern and offset?
870 11 50 66     53 if (scalar @pat == 2 && ref($pat[0]) eq 'ARRAY' && ref($pat[1]) eq '') {
      33        
871 0         0 @{$self->{' linedash'}} = @pat;
  0         0  
872 0 0       0 if (@{$pat[0]}) {
  0         0  
873             # not an empty array
874 0         0 return ('[', floats(@{$pat[0]}), '] ', $pat[1], ' d');
  0         0  
875             } else {
876 0         0 return ('[ ] 0 d');
877             }
878             }
879             # anything else, including empty pattern
880 11         60 $self->add($self->_linedash(@pat));
881              
882 11         33 return $self;
883             }
884              
885             =head3 flatness, flatness_tolerance
886              
887             $content->flatness($tolerance)
888              
889             =over
890              
891             I<(Advanced)> Sets the maximum variation in output pixels when drawing
892             curves. The defined range of C<$tolerance> is 0 to 100, with 0 meaning I<use
893             the device default flatness>. According to the PDF specification, you should
894             not try to force visible line segments (the curve's approximation); results
895             will be unpredictable. Usually, results for different flatness settings will be
896             indistinguishable to the eye.
897              
898             The C<$tolerance> value is silently clamped to be between 0 and 100.
899              
900             If no C<$tolerance> is given, the current setting is B<returned>. If the
901             tolerance is being set, C<$self> is B<returned> so that calls may be chained.
902              
903             B<Alternate name:> C<flatness_tolerance>
904              
905             This is provided for compatibility with PDF::API2.
906              
907             =back
908              
909             =cut
910              
911             sub _flatness {
912 3     3   11 my ($tolerance) = @_;
913              
914 3 50       11 if ($tolerance < 0 ) { $tolerance = 0; }
  0         0  
915 3 50       9 if ($tolerance > 100) { $tolerance = 100; }
  0         0  
916 3         38 return ($tolerance, 'i');
917             }
918              
919 1     1 1 10 sub flatness_tolerance { return flatness(@_); } ## no critic
920              
921             sub flatness {
922 3     3 1 23 my ($self, $tolerance) = @_;
923              
924 3 50       10 if (!defined $tolerance) {
925 0         0 return $self->{' flatness'};
926             }
927 3         15 $self->add(_flatness($tolerance));
928 3         9 $self->{' flatness'} = $tolerance;
929              
930 3         8 return $self;
931             }
932              
933             =head3 egstate
934              
935             $content->egstate($object)
936              
937             =over
938              
939             I<(Advanced)> Adds an Extended Graphic State B<object> containing additional
940             state parameters.
941              
942             =back
943              
944             =cut
945              
946             sub egstate {
947 0     0 1 0 my ($self, $egs) = @_;
948              
949 0         0 $self->add('/' . $egs->name(), 'gs');
950 0         0 $self->resource('ExtGState', $egs->name(), $egs);
951              
952 0         0 return $self;
953             }
954              
955             =head2 Path Construction (Drawing)
956              
957             =head3 move
958              
959             $content->move($x,$y)
960              
961             =over
962              
963             Starts a new path at the specified coordinates.
964             Note that multiple x,y pairs I<can> be given, although this isn't that useful
965             (only the last pair would have an effect).
966              
967             =back
968              
969             =cut
970              
971             sub _move {
972 0     0   0 my ($x,$y) = @_;
973              
974 0         0 return (floats($x,$y), 'm');
975             }
976              
977             sub move {
978 134     134 1 490 my ($self) = shift;
979              
980 134         521 $self->_Gpending();
981 134         250 my ($x,$y);
982 134         385 while (scalar @_ >= 2) {
983 134         234 $x = shift;
984 134         244 $y = shift;
985 134         328 $self->{' mx'} = $x;
986 134         380 $self->{' my'} = $y;
987 134 50       407 if ($self->_in_text_object()) {
988 0         0 $self->add_post(floats($x,$y), 'm');
989             } else {
990 134         636 $self->add(floats($x,$y), 'm');
991             }
992 134         359 $self->{' x'} = $x; # set new current position
993 134         504 $self->{' y'} = $y;
994             }
995             #if (@_) { # normal practice is to discard unused values
996             # warn "extra coordinate(s) ignored in move\n";
997             #}
998              
999 134         352 return $self;
1000             }
1001              
1002             =head3 close
1003              
1004             $content->close()
1005              
1006             =over
1007              
1008             Closes and ends the current path by extending a line from the current
1009             position to the starting position.
1010              
1011             =back
1012              
1013             =cut
1014              
1015             sub close {
1016 14     14 1 93 my ($self) = shift;
1017              
1018 14         56 $self->add('h');
1019 14         48 $self->{' x'} = $self->{' mx'};
1020 14         39 $self->{' y'} = $self->{' my'};
1021              
1022 14         35 return $self;
1023             }
1024              
1025             =head3 Straight line constructs
1026              
1027             B<Note:> None of these will actually be I<visible> until you call C<stroke>,
1028             C<fill>, or C<fillstroke>. They are merely setting up the path to draw.
1029              
1030             =head4 line
1031              
1032             $content->line($x,$y)
1033              
1034             $content->line($x,$y, $x2,$y2,...)
1035              
1036             =over
1037              
1038             Extends the path in a line from the I<current> coordinates to the
1039             specified coordinates, and updates the current position to be the new
1040             coordinates.
1041              
1042             Multiple additional C<[$x,$y]> pairs are permitted, to draw joined multiple
1043             line segments. Note that this is B<not> equivalent to a polyline (see C<poly>),
1044             because the first C<[$x,$y]> pair in a polyline is a I<move> operation.
1045             Also, the C<linecap> setting will be used rather than the C<linejoin>
1046             setting for treating the ends of segments.
1047              
1048             =back
1049              
1050             =cut
1051              
1052             sub _line {
1053 0     0   0 my ($x,$y) = @_;
1054              
1055 0         0 return (floats($x,$y), 'l');
1056             }
1057              
1058             sub line {
1059 99     99 1 212 my ($self) = shift;
1060              
1061 99         274 $self->_Gpending();
1062 99         165 my ($x,$y);
1063 99         227 while (scalar @_ >= 2) {
1064 101         156 $x = shift;
1065 101         142 $y = shift;
1066 101 50       241 if ($self->_in_text_object()) {
1067 0         0 $self->add_post(floats($x,$y), 'l');
1068             } else {
1069 101         335 $self->add(floats($x,$y), 'l');
1070             }
1071 101         229 $self->{' x'} = $x; # new current point
1072 101         311 $self->{' y'} = $y;
1073             }
1074             #if (@_) { leftovers ignored, as is usual practice
1075             # warn "line() has leftover coordinate (ignored).";
1076             #}
1077              
1078 99         222 return $self;
1079             }
1080              
1081             =head4 hline, vline
1082              
1083             $content->hline($x)
1084              
1085             $content->vline($y)
1086              
1087             =over
1088              
1089             Shortcuts for drawing horizontal and vertical lines from the current
1090             position. They are like C<line()>, but to the new x and current y (C<hline>),
1091             or to the the current x and new y (C<vline>).
1092              
1093             =back
1094              
1095             =cut
1096              
1097             sub hline {
1098 2     2 1 16 my ($self, $x) = @_;
1099              
1100 2         11 $self->_Gpending();
1101 2 50       6 if ($self->_in_text_object()) {
1102 0         0 $self->add_post(floats($x, $self->{' y'}), 'l');
1103             } else {
1104 2         10 $self->add(floats($x, $self->{' y'}), 'l');
1105             }
1106             # extraneous inputs discarded
1107 2         10 $self->{' x'} = $x; # update current position
1108              
1109 2         6 return $self;
1110             }
1111              
1112             sub vline {
1113 1     1 1 8 my ($self, $y) = @_;
1114              
1115 1         5 $self->_Gpending();
1116 1 50       3 if ($self->_in_text_object()) {
1117 0         0 $self->add_post(floats($self->{' x'}, $y), 'l');
1118             } else {
1119 1         5 $self->add(floats($self->{' x'}, $y), 'l');
1120             }
1121             # extraneous inputs discarded
1122 1         4 $self->{' y'} = $y; # update current position
1123              
1124 1         4 return $self;
1125             }
1126              
1127             =head4 polyline
1128              
1129             $content->polyline($x1,$y1, ..., $xn,$yn)
1130              
1131             =over
1132              
1133             This is a shortcut for creating a polyline path from the current position. It
1134             extends the path in line segments along the specified coordinates.
1135             The current position is changed to the last C<[$x,$y]> pair given.
1136              
1137             A critical distinction between the C<polyline> method and the C<poly> method
1138             is that in this (C<polyline>), the first pair of coordinates are treated as a
1139             I<draw> order (unlike the I<move> order in C<poly>).
1140              
1141             Thus, while this is provided for compatibility with PDF::API2, it is I<not>
1142             really an alias or alternate name for C<poly>!
1143              
1144             =back
1145              
1146             =cut
1147              
1148             # TBD document line_join vs line_cap? (see poly()). perhaps demo in Content.pl?
1149             sub polyline {
1150 2     2 1 13 my $self = shift();
1151 2 50       10 unless (@_ % 2 == 0) {
1152 0         0 croak 'polyline requires pairs of coordinates';
1153             }
1154              
1155 2         8 $self->_Gpending();
1156 2         51 while (@_) {
1157 4         10 my $x = shift();
1158 4         8 my $y = shift();
1159 4         14 $self->line($x, $y);
1160             }
1161              
1162 2         7 return $self;
1163             }
1164              
1165             =head4 poly
1166              
1167             $content->poly($x1,$y1, ..., $xn,$yn)
1168              
1169             =over
1170              
1171             This is a shortcut for creating a polyline path. It moves to C<[$x1,$y1]>, and
1172             then extends the path in line segments along the specified coordinates.
1173             The current position is changed to the last C<[$x,$y]> pair given.
1174              
1175             The difference between a polyline and a C<line> with multiple C<[$x,$y]>
1176             pairs is that the first pair in a polyline are a I<move>, while in a line
1177             they are a I<draw>.
1178             Also, C<line_join> instead of C<line_cap> is used to control the appearance
1179             of the ends of line segments.
1180              
1181             A critical distinction between the C<polyline> method and the C<poly> method
1182             is that in this (C<poly>), the first pair of coordinates are treated as a
1183             I<move> order.
1184              
1185             =back
1186              
1187             =cut
1188              
1189             sub poly {
1190             # not implemented as self,x,y = @_, as @_ must be shifted
1191 4     4 1 53 my ($self) = shift;
1192 4         11 my $x = shift;
1193 4         8 my $y = shift;
1194              
1195 4         27 $self->_Gpending();
1196 4         20 $self->move($x,$y);
1197 4         25 $self->line(@_);
1198              
1199 4         27 return $self;
1200             }
1201              
1202             =head4 rectangle
1203              
1204             $content = $content->rectangle($x1, $y1, $x2, $y2)
1205              
1206             =over
1207              
1208             Creates a new rectangle-shaped path, between the two corner points C<[$x1, $y1]>
1209             and C<[$x2, $y2]>. The corner points are swapped if necessary, to make
1210             "1" the lower left and "2" the upper right (x2 > x1 and y2 > y1).
1211             The object (here, C<$content>) is returned, to permit chaining.
1212              
1213             B<Note> that this is I<not> an alias or alternate name for C<rect>. It handles
1214             only one rectangle, and takes corner coordinates for corner "2", rather than
1215             the width and height.
1216              
1217             =back
1218              
1219             =cut
1220              
1221             sub rectangle {
1222 2     2 1 13 my ($self, $x1, $y1, $x2, $y2) = @_;
1223              
1224             # Ensure that x1,y1 is lower-left and x2,y2 is upper-right
1225             # swap corners if necessary
1226 2 100       8 if ($x2 < $x1) {
1227 1         3 my $x = $x1;
1228 1         3 $x1 = $x2;
1229 1         2 $x2 = $x;
1230             }
1231 2 50       7 if ($y2 < $y1) {
1232 0         0 my $y = $y1;
1233 0         0 $y1 = $y2;
1234 0         0 $y2 = $y;
1235             }
1236              
1237 2         9 $self->_Gpending();
1238 2         11 $self->add(floats($x1, $y1, ($x2 - $x1), ($y2 - $y1)), 're');
1239 2         7 $self->{' x'} = $x1;
1240 2         32 $self->{' y'} = $y1;
1241              
1242 2         5 return $self;
1243             }
1244              
1245             =head4 rect
1246              
1247             $content = $content->rect($x,$y, $w,$h)
1248              
1249             $content = $content->rect($x1,$y1, $w1,$h1, ..., $xn,$yn, $wn,$hn)
1250              
1251             =over
1252              
1253             This creates paths for one or more rectangles, with their lower left points
1254             at C<[$x,$y]> and specified widths (+x direction) and heights (+y direction).
1255             Negative widths and heights are permitted, which draw to the left (-x) and
1256             below (-y) the given corner point, respectively.
1257             The current position is changed to the C<[$x,$y]> of the last rectangle given.
1258             Note that this is the I<starting> point of the rectangle, not the end point.
1259             The object (here, C<$content>) is returned, to permit chaining.
1260              
1261             B<Note> that this differs from the C<rectangle> method in that multiple
1262             rectangles may be drawn in one call, and the second pair for each rectangle
1263             are the width and height, not the opposite corner coordinates.
1264              
1265             =back
1266              
1267             =cut
1268              
1269             sub rect {
1270 10     10 1 42 my $self = shift;
1271              
1272 10         19 my ($x,$y, $w,$h);
1273 10         42 $self->_Gpending();
1274 10         28 while (scalar @_ >= 4) {
1275 12         19 $x = shift;
1276 12         19 $y = shift;
1277 12         19 $w = shift;
1278 12         18 $h = shift;
1279 12         52 $self->add(floats($x,$y, $w,$h), 're');
1280             }
1281             #if (@_) { # usual practice is to ignore extras
1282             # warn "rect() extra coordinates discarded.\n";
1283             #}
1284 10         24 $self->{' x'} = $x; # set new current position
1285 10         21 $self->{' y'} = $y;
1286              
1287 10         47 return $self;
1288             }
1289              
1290             =head4 rectxy
1291              
1292             $content->rectxy($x1,$y1, $x2,$y2)
1293              
1294             =over
1295              
1296             This creates a rectangular path, with C<[$x1,$y1]> and C<[$x2,$y2]>
1297             specifying I<opposite> corners. They can be Lower Left and Upper Right,
1298             I<or> Upper Left and Lower Right, in either order, so long as they are
1299             diagonally opposite each other.
1300             The current position is changed to the C<[$x1,$y1]> (first) pair.
1301              
1302             This is not I<quite> an alias or alternate name for C<rectangle>, as it
1303             permits the corner points to be specified in any order.
1304              
1305             =back
1306              
1307             =cut
1308              
1309             # TBD allow multiple rectangles, as in rect()
1310              
1311             sub rectxy {
1312 4     4 1 34 my ($self, $x,$y, $x2,$y2) = @_;
1313              
1314             #$self->_Gpending(); unnecessary, handled by rect()
1315 4         23 $self->rect($x,$y, ($x2-$x),($y2-$y));
1316              
1317 4         10 return $self;
1318             }
1319              
1320             =head3 Curved line constructs
1321              
1322             B<Note:> None of these will actually be I<visible> until you call C<stroke>,
1323             C<fill>, or C<fillstroke>. They are merely setting up the path to draw.
1324              
1325             =head4 circle
1326              
1327             $content->circle($xc,$yc, $radius)
1328              
1329             =over
1330              
1331             This creates a circular path centered on C<[$xc,$yc]> with the specified
1332             radius. It does B<not> change the current position.
1333              
1334             =back
1335              
1336             =cut
1337              
1338             sub circle {
1339 1     1 1 10 my ($self, $xc,$yc, $r) = @_;
1340              
1341 1         7 $self->_Gpending();
1342 1         6 $self->arc($xc,$yc, $r,$r, 0,360, 1);
1343 1         7 $self->close();
1344              
1345 1         4 return $self;
1346             }
1347              
1348             =head4 ellipse
1349              
1350             $content->ellipse($xc,$yc, $rx,$ry)
1351              
1352             =over
1353              
1354             This creates a closed elliptical path centered on C<[$xc,$yc]>, with axis radii
1355             (semidiameters) specified by C<$rx> (x axis) and C<$ry> (y axis), respectively.
1356             It does not change the current position.
1357              
1358             =back
1359              
1360             =cut
1361              
1362             sub ellipse {
1363 1     1 1 9 my ($self, $xc,$yc, $rx,$ry) = @_;
1364              
1365 1         7 $self->_Gpending();
1366 1         5 $self->arc($xc,$yc, $rx,$ry, 0,360, 1);
1367 1         5 $self->close();
1368              
1369 1         3 return $self;
1370             }
1371              
1372             # input: x and y axis radii
1373             # sweep start and end angles
1374             # sweep direction (0=CCW (default), or 1=CW)
1375             # output: two endpoints and two control points for
1376             # the Bezier curve describing the arc
1377             # maximum 30 degrees of sweep: is broken up into smaller
1378             # arc segments if necessary
1379             # if crosses 0 degree angle in either sweep direction, split there at 0
1380             # if alpha=beta (0 degree sweep) or either radius <= 0, fatal error
1381             sub _arctocurve {
1382 228     228   494 my ($rx,$ry, $alpha,$beta, $dir) = @_;
1383              
1384 228 50       480 if (!defined $dir) { $dir = 0; } # default is CCW sweep
  0         0  
1385             # check for non-positive radius
1386 228 50 33     830 if ($rx <= 0 || $ry <= 0) {
1387 0         0 die "curve request with radius not > 0 ($rx, $ry)";
1388             }
1389             # check for zero degrees of sweep
1390 228 50       506 if ($alpha == $beta) {
1391 0         0 die "curve request with zero degrees of sweep ($alpha to $beta)";
1392             }
1393              
1394             # constrain alpha and beta to 0..360 range so 0 crossing check works
1395 228         483 while ($alpha < 0.0) { $alpha += 360.0; }
  0         0  
1396 228         531 while ( $beta < 0.0) { $beta += 360.0; }
  2         37  
1397 228         493 while ($alpha > 360.0) { $alpha -= 360.0; }
  0         0  
1398 228         462 while ( $beta > 360.0) { $beta -= 360.0; }
  0         0  
1399              
1400             # Note that there is a problem with the original code, when the 0 degree
1401             # angle is crossed. It especially shows up in arc() and pie(). Therefore,
1402             # split the original sweep at 0 degrees, if it crosses that angle.
1403 228 50 66     599 if (!$dir && $alpha > $beta) { # CCW pass over 0 degrees
1404 0 0 0     0 if ($alpha == 360.0 && $beta == 0.0) { # oddball case
    0          
    0          
1405 0         0 return (_arctocurve($rx,$ry, 0.0,360.0, 0));
1406             } elsif ($alpha == 360.0) { # alpha to 360 would be null
1407 0         0 return (_arctocurve($rx,$ry, 0.0,$beta, 0));
1408             } elsif ($beta == 0.0) { # 0 to beta would be null
1409 0         0 return (_arctocurve($rx,$ry, $alpha,360.0, 0));
1410             } else {
1411             return (
1412 0         0 _arctocurve($rx,$ry, $alpha,360.0, 0),
1413             _arctocurve($rx,$ry, 0.0,$beta, 0)
1414             );
1415             }
1416             }
1417 228 100 100     646 if ($dir && $alpha < $beta) { # CW pass over 0 degrees
1418 2 50 33     30 if ($alpha == 0.0 && $beta == 360.0) { # oddball case
    50          
    0          
1419 0         0 return (_arctocurve($rx,$ry, 360.0,0.0, 1));
1420             } elsif ($alpha == 0.0) { # alpha to 0 would be null
1421 2         10 return (_arctocurve($rx,$ry, 360.0,$beta, 1));
1422             } elsif ($beta == 360.0) { # 360 to beta would be null
1423 0         0 return (_arctocurve($rx,$ry, $alpha,0.0, 1));
1424             } else {
1425             return (
1426 0         0 _arctocurve($rx,$ry, $alpha,0.0, 1),
1427             _arctocurve($rx,$ry, 360.0,$beta, 1)
1428             );
1429             }
1430             }
1431              
1432             # limit arc length to 30 degrees, for reasonable smoothness
1433             # none of the long arcs or short resulting arcs cross 0 degrees
1434 226 100       536 if (abs($beta-$alpha) > 30) {
1435             return (
1436 106         380 _arctocurve($rx,$ry, $alpha,($beta+$alpha)/2, $dir),
1437             _arctocurve($rx,$ry, ($beta+$alpha)/2,$beta, $dir)
1438             );
1439             } else {
1440             # Note that we can't use deg2rad(), because closed arcs (circle() and
1441             # ellipse()) are 0-360 degrees, which deg2rad treats as 0-0 radians!
1442 120         219 $alpha = ($alpha * pi / 180);
1443 120         209 $beta = ($beta * pi / 180);
1444              
1445 120         336 my $bcp = (4.0/3 * (1 - cos(($beta - $alpha)/2)) / sin(($beta - $alpha)/2));
1446 120         207 my $sin_alpha = sin($alpha);
1447 120         222 my $sin_beta = sin($beta);
1448 120         241 my $cos_alpha = cos($alpha);
1449 120         200 my $cos_beta = cos($beta);
1450              
1451 120         233 my $p0_x = $rx * $cos_alpha;
1452 120         202 my $p0_y = $ry * $sin_alpha;
1453 120         194 my $p1_x = $rx * ($cos_alpha - $bcp * $sin_alpha);
1454 120         216 my $p1_y = $ry * ($sin_alpha + $bcp * $cos_alpha);
1455 120         213 my $p2_x = $rx * ($cos_beta + $bcp * $sin_beta);
1456 120         221 my $p2_y = $ry * ($sin_beta - $bcp * $cos_beta);
1457 120         188 my $p3_x = $rx * $cos_beta;
1458 120         184 my $p3_y = $ry * $sin_beta;
1459              
1460 120         644 return ($p0_x,$p0_y, $p1_x,$p1_y, $p2_x,$p2_y, $p3_x,$p3_y);
1461             }
1462             }
1463              
1464             =head4 arc
1465              
1466             $content->arc($xc,$yc, $rx,$ry, $alpha,$beta, $move, $dir)
1467              
1468             $content->arc($xc,$yc, $rx,$ry, $alpha,$beta, $move)
1469              
1470             =over
1471              
1472             This extends the path along an arc of an ellipse centered at C<[$xc,$yc]>.
1473             The semidiameters of the elliptical curve are C<$rx> (x axis) and C<$ry>
1474             (y axis), respectively, and the arc sweeps from C<$alpha> degrees to C<$beta>
1475             degrees. The current position is then set to the endpoint of the arc.
1476              
1477             Set C<$move> to a I<true> value if this arc is the beginning of a new
1478             path instead of the continuation of an existing path. Either way, the
1479             current position will be updated to the end of the arc.
1480             Use C<$rx == $ry> for a circular arc.
1481              
1482             The optional C<$dir> arc sweep direction defaults to 0 (I<false>), for a
1483             counter-clockwise/anti-clockwise sweep. Set to 1 (I<true>) for a clockwise
1484             sweep.
1485              
1486             =back
1487              
1488             =cut
1489              
1490             sub arc {
1491 5     5 1 31 my ($self, $xc,$yc, $rx,$ry, $alpha,$beta, $move, $dir) = @_;
1492              
1493 5 100       36 if (!defined $dir) { $dir = 0; }
  4         9  
1494 5         20 my @points = _arctocurve($rx,$ry, $alpha,$beta, $dir);
1495 5         16 my ($p0_x,$p0_y, $p1_x,$p1_y, $p2_x,$p2_y, $p3_x,$p3_y);
1496              
1497 5         22 $self->_Gpending();
1498 5         14 $p0_x = $xc + shift @points;
1499 5         13 $p0_y = $yc + shift @points;
1500              
1501 5 100       26 $self->move($p0_x,$p0_y) if $move;
1502              
1503 5         19 while (scalar @points >= 6) {
1504 44         83 $p1_x = $xc + shift @points;
1505 44         75 $p1_y = $yc + shift @points;
1506 44         98 $p2_x = $xc + shift @points;
1507 44         77 $p2_y = $yc + shift @points;
1508 44         85 $p3_x = $xc + shift @points;
1509 44         76 $p3_y = $yc + shift @points;
1510 44         160 $self->curve($p1_x,$p1_y, $p2_x,$p2_y, $p3_x,$p3_y);
1511 44         105 shift @points;
1512 44         86 shift @points;
1513 44         86 $self->{' x'} = $p3_x; # set new current position
1514 44         125 $self->{' y'} = $p3_y;
1515             }
1516             # should we worry about anything left over in @points?
1517             # supposed to be blocks of 8 (4 points)
1518              
1519 5         15 return $self;
1520             }
1521              
1522             =head4 pie
1523              
1524             $content->pie($xc,$yc, $rx,$ry, $alpha,$beta, $dir)
1525              
1526             $content->pie($xc,$yc, $rx,$ry, $alpha,$beta)
1527              
1528             =over
1529              
1530             Creates a pie-shaped path from an ellipse centered on C<[$xc,$yc]>.
1531             The x-axis and y-axis semidiameters of the ellipse are C<$rx> and C<$ry>,
1532             respectively, and the arc sweeps from C<$alpha> degrees to C<$beta>
1533             degrees.
1534             It does not change the current position.
1535             Depending on the sweep angles and direction, this can draw either the
1536             pie "slice" or the remaining pie (with slice removed).
1537             Use C<$rx == $ry> for a circular pie.
1538             Use a different C<[$xc,$yc]> for the slice, to offset it from the remaining pie.
1539              
1540             The optional C<$dir> arc sweep direction defaults to 0 (I<false>), for a
1541             counter-clockwise/anti-clockwise sweep. Set to 1 (I<true>) for a clockwise
1542             sweep.
1543              
1544             This is a shortcut to draw a section of elliptical (or circular) arc and
1545             connect it to the center of the ellipse or circle, to form a pie shape.
1546              
1547             =back
1548              
1549             =cut
1550              
1551             sub pie {
1552 1     1 1 11 my ($self, $xc,$yc, $rx,$ry, $alpha,$beta, $dir) = @_;
1553              
1554 1 50       4 if (!defined $dir) { $dir = 0; }
  1         3  
1555 1         6 my ($p0_x,$p0_y) = _arctocurve($rx,$ry, $alpha,$beta, $dir);
1556             #$self->_Gpending(); move() will take care of this
1557 1         8 $self->move($xc,$yc);
1558 1         8 $self->line($p0_x+$xc, $p0_y+$yc);
1559 1         7 $self->arc($xc,$yc, $rx,$ry, $alpha,$beta, 0, $dir);
1560 1         6 $self->close();
1561              
1562 1         3 return $self;
1563             }
1564              
1565             =head4 curve
1566              
1567             $content->curve($cx1,$cy1, $cx2,$cy2, $x,$y)
1568              
1569             =over
1570              
1571             This extends the path in a curve from the current point to C<[$x,$y]>,
1572             using the two specified I<control> points to create a B<cubic Bezier curve>, and
1573             updates the current position to be the new point (C<[$x,$y]>).
1574              
1575             Within a B<text> object, the text's baseline follows the Bezier curve.
1576              
1577             Note that while multiple sets of three C<[x,y]> pairs are permitted, these
1578             are treated as I<independent> cubic Bezier curves. There is no attempt made to
1579             smoothly blend one curve into the next!
1580              
1581             =back
1582              
1583             =cut
1584              
1585             sub curve {
1586 125     125 1 267 my ($self) = shift;
1587              
1588 125         250 my ($cx1,$cy1, $cx2,$cy2, $x,$y);
1589 125         391 $self->_Gpending();
1590 125         280 while (scalar @_ >= 6) {
1591 125         205 $cx1 = shift;
1592 125         228 $cy1 = shift;
1593 125         214 $cx2 = shift;
1594 125         199 $cy2 = shift;
1595 125         186 $x = shift;
1596 125         201 $y = shift;
1597 125 50       331 if ($self->_in_text_object()) {
1598 0         0 $self->add_post(floats($cx1,$cy1, $cx2,$cy2, $x,$y), 'c');
1599             } else {
1600 125         449 $self->add(floats($cx1,$cy1, $cx2,$cy2, $x,$y), 'c');
1601             }
1602 125         399 $self->{' x'} = $x; # set new current position
1603 125         423 $self->{' y'} = $y;
1604             }
1605              
1606 125         247 return $self;
1607             }
1608              
1609             =head4 qbspline, spline
1610              
1611             $content->qbspline($cx1,$cy1, $x,$y)
1612              
1613             =over
1614              
1615             This extends the path in a curve from the current point to C<[$x,$y]>,
1616             using the two specified points to create a quadratic Bezier curve, and updates
1617             the current position to be the new point.
1618              
1619             Internally, these splines are one or more cubic Bezier curves (see C<curve>)
1620             with the two control points synthesized from the two given points (a control
1621             point and the end point of a I<quadratic> Bezier curve).
1622              
1623             Note that while multiple sets of two C<[x,y]> pairs are permitted, these
1624             are treated as I<independent> quadratic Bezier curves. There is no attempt
1625             made to smoothly blend one curve into the next!
1626              
1627             Further note that this "spline" does not match the common definition of
1628             a spline being a I<continuous> curve passing I<through> B<all> the given
1629             points! It is a piecewise non-continuous cubic Bezier curve. Use with care, and
1630             do not make assumptions about splines for you or your readers. You may wish
1631             to use the C<bspline> call to have a continuously smooth spline to pass through
1632             all given points.
1633              
1634             Pairs of points (control point and end point) are consumed in a loop. If one
1635             point or coordinate is left over at the end, it is discarded (as usual practice
1636             for excess data to a routine). There is no check for duplicate points or other
1637             degeneracies.
1638              
1639             B<Alternate name:> C<spline>
1640              
1641             This method is still named C<spline> in PDF::API2, so for compatibility, that
1642             name is usable here. Since there are both quadratic and cubic splines available
1643             in PDF, it is preferred to use more descriptive names such as C<qbspline> and
1644             C<cbspline> to minimize confusion.
1645              
1646             =back
1647              
1648             =cut
1649              
1650 0     0 1 0 sub spline { return qbspline(@_); } ## no critic
1651              
1652             sub qbspline {
1653 1     1 1 8 my ($self) = shift;
1654              
1655             #$self->_Gpending(); curve() will take care of this
1656 1         6 while (scalar @_ >= 4) {
1657 1         3 my $cx = shift; # single Control Point
1658 1         2 my $cy = shift;
1659 1         2 my $x = shift; # new end point
1660 1         3 my $y = shift;
1661             # synthesize 2 cubic Bezier control points from two given points
1662 1         5 my $c1x = (2*$cx + $self->{' x'})/3;
1663 1         3 my $c1y = (2*$cy + $self->{' y'})/3;
1664 1         3 my $c2x = (2*$cx + $x)/3;
1665 1         4 my $c2y = (2*$cy + $y)/3;
1666 1         5 $self->curve($c1x,$c1y, $c2x,$c2y, $x,$y);
1667             }
1668             ## one left over point? straight line (silent error recovery)
1669             #if (scalar @_ >= 2) {
1670             # my $x = shift; # new end point
1671             # my $y = shift;
1672             # $self->line($x,$y);
1673             #}
1674             #if (@_) { leftovers ignored, as is usual practice
1675             # warn "qbspline() has leftover coordinate (ignored).";
1676             #}
1677              
1678 1         3 return $self;
1679             }
1680              
1681             =head4 bspline, cbspline
1682              
1683             $content->bspline($ptsRef, %opts)
1684              
1685             =over
1686              
1687             This extends the path in a curve from the current point to the end of a list
1688             of coordinate pairs in the array referenced by C<$ptsRef>. Smoothly continuous
1689             cubic Bezier splines are used to create a curve that passes through I<all>
1690             the given points. Multiple control points are synthesized; they are not
1691             supplied in the call. The current position is updated to the last point.
1692              
1693             Internally, these splines are one cubic Bezier curve (see C<curve>) per pair
1694             of input points, with the two control points synthesized from the tangent
1695             through each point as set by the polyline that would connect each point to its
1696             neighbors. The intent is that the resulting curve should follow reasonably
1697             closely a polyline that would connect the points, and should avoid any major
1698             excursions. See the discussions below for the handling of the control points
1699             at the endpoints (current point and last input point). The point at the end
1700             of the last line or curve drawn becomes the new current point.
1701              
1702             Options %opts:
1703              
1704             =back
1705              
1706             =over
1707              
1708             =item 'firstseg' => 'I<mode>'
1709              
1710             where I<mode> is
1711              
1712             =over
1713              
1714             =item curve
1715              
1716             This is the B<default> behavior.
1717             This forces the first segment (from the current point to the first given point)
1718             to be drawn as a cubic Bezier curve. This means that the direction of the curve
1719             coming off the current point is unconstrained (it will end up being a reflection
1720             of the tangent at the first given point).
1721              
1722             =item line1
1723              
1724             This forces the first segment (from the current point to the first given point)
1725             to be drawn as a curve, with the tangent at the current point to be constrained
1726             as parallel to the polyline segment.
1727              
1728             =item line2
1729              
1730             This forces the first segment (from the current point to the first given point)
1731             to be drawn as a line segment. This also sets the tangent through the first
1732             given point as a continuation of the line, as well as constraining the direction
1733             of the line at the current point.
1734              
1735             =item constraint1
1736              
1737             This forces the first segment (from the current point to the first given point)
1738             to B<not> be drawn, but to be an invisible curve (like mode=line1) to leave
1739             the tangent at the first given point unconstrained. A I<move> will be made to
1740             the first given point, and the current point is otherwise ignored.
1741              
1742             =item constraint2
1743              
1744             This forces the first segment (from the current point to the first given point)
1745             to B<not> be drawn, but to be an invisible line (like mode=line2) to constrain
1746             the tangent at the first given point. A I<move> will be made to the first given
1747             point, and the current point is otherwise ignored.
1748              
1749             =back
1750              
1751             =item 'lastseg' => 'I<mode>'
1752              
1753             where I<mode> is
1754              
1755             =over
1756              
1757             =item curve
1758              
1759             This is the B<default> behavior.
1760             This forces the last segment (to the last given input point)
1761             to be drawn as a cubic Bezier curve. This means that the direction of the curve
1762             going to the last point is unconstrained (it will end up being a reflection
1763             of the tangent at the next-to-last given point).
1764              
1765             =item line1
1766              
1767             This forces the last segment (to the last given input point) to be drawn as a
1768             curve with the the tangent through the last given point parallel to the
1769             polyline segment, thus constraining the direction of the line at the last
1770             point.
1771              
1772             =item line2
1773              
1774             This forces the last segment (to the last given input point)
1775             to be drawn as a line segment. This also sets the tangent through the
1776             next-to-last given point as a back continuation of the line, as well as
1777             constraining the direction of the line at the last point.
1778              
1779             =item constraint1
1780              
1781             This forces the last segment (to the last given input point)
1782             to B<not> be drawn, but to be an invisible curve (like mode=line1) to leave
1783             the tangent at the next-to-last given point unconstrained. The last given
1784             input point is ignored, and next-to-last point becomes the new current point.
1785              
1786             =item constraint2
1787              
1788             This forces the last segment (to the last given input point)
1789             to B<not> be drawn, but to be an invisible line (like mode=line2) to constrain
1790             the tangent at the next-to-last given point. The last given input point is
1791             ignored, and next-to-last point becomes the new current point.
1792              
1793             =back
1794              
1795             =item 'ratio' => I<n>
1796              
1797             I<n> is the ratio of the length from a point to a control point to the length
1798             of the polyline segment on that side of the given point. It must be greater
1799             than 0.1, and the default is 0.3333 (1/3).
1800              
1801             =item 'colinear' => 'I<mode>'
1802              
1803             This describes how to handle the middle segment when there are four or more
1804             colinear points in the input set. A I<mode> of 'line' specifies that a line
1805             segment will be drawn between each of the interior colinear points. A I<mode>
1806             of 'curve' (this is the default) will draw a Bezier curve between each of those
1807             points.
1808              
1809             C<colinear> applies only to interior runs of colinear points, between curves.
1810             It does not apply to runs at the beginning or end of the point list, which are
1811             drawn as line segments or linear constraints regardless of I<firstseg> and
1812             I<lastseg> settings.
1813              
1814             =item 'debug' => I<N>
1815              
1816             If I<N> is 0 (the default), only the spline is returned. If it is greater than
1817             0, a number of additional items will be drawn: (N>0) the points, (N>1) a green
1818             solid polyline connecting them, (N>2) blue original tangent lines at each
1819             interior point, and (N>3) red dashed lines and hollow points representing the
1820             Bezier control points.
1821              
1822             =back
1823              
1824             =over
1825              
1826             B<Special cases>
1827              
1828             Adjacent points which are duplicates are consolidated.
1829             An extra coordinate at the end of the input point list (not a full
1830             C<[x,y]> pair) will, as usual, be ignored.
1831              
1832             =back
1833              
1834             =over
1835              
1836             =item 0 given points (after duplicate consolidation)
1837              
1838             This leaves only the current point (unchanged), so it is a no-op.
1839              
1840             =item 1 given point (after duplicate consolidation)
1841              
1842             This leaves the current point and one point, so it is rendered as a line,
1843             regardless of %opt flags.
1844              
1845             =item 2 given points (after duplicate consolidation)
1846              
1847             This leaves the current point, an intermediate point, and the end point. If
1848             the three points are colinear, two line segments will be drawn. Otherwise, both
1849             segments are curves (through the tangent at the intermediate point). If either
1850             end segment mode is requested to be a line or constraint, it is treated as a
1851             B<line1> mode request instead.
1852              
1853             =item I<N> colinear points at beginning or end
1854              
1855             I<N> colinear points at beginning or end of the point set causes I<N-1> line
1856             segments (C<line2> or C<constraint2>, regardless of the settings of
1857             C<firstseg>, C<lastseg>, and C<colinear>.
1858              
1859             =back
1860              
1861             =over
1862              
1863             B<Alternate name:> C<cbspline>
1864              
1865             This is to emphasize that it is a I<cubic> Bezier spline, as opposed to a
1866             I<quadratic> Bezier spline (see C<qbspline> above).
1867              
1868             =back
1869              
1870             =cut
1871              
1872 0     0 1 0 sub cbspline { return bspline(@_); } ## no critic
1873              
1874             sub bspline {
1875 1     1 1 8 my ($self, $ptsRef, %opts) = @_;
1876             # copy dashed option names to preferred undashed names
1877 1 50 33     7 if (defined $opts{'-firstseg'} && !defined $opts{'firstseg'}) { $opts{'firstseg'} = delete($opts{'-firstseg'}); }
  0         0  
1878 1 50 33     4 if (defined $opts{'-lastseg'} && !defined $opts{'lastseg'}) { $opts{'lastseg'} = delete($opts{'-lastseg'}); }
  0         0  
1879 1 50 33     5 if (defined $opts{'-ratio'} && !defined $opts{'ratio'}) { $opts{'ratio'} = delete($opts{'-ratio'}); }
  0         0  
1880 1 50 33     5 if (defined $opts{'-colinear'} && !defined $opts{'colinear'}) { $opts{'colinear'} = delete($opts{'-colinear'}); }
  0         0  
1881 1 50 33     5 if (defined $opts{'-debug'} && !defined $opts{'debug'}) { $opts{'debug'} = delete($opts{'-debug'}); }
  0         0  
1882              
1883 1         5 my @inputPts = @$ptsRef;
1884 1         4 my ($firstseg, $lastseg, $ratio, $colinear, $debug);
1885 1         0 my (@oldColor, @oldFill, $oldWidth, @oldDash);
1886             # specific treatment of the first and last segments of the spline
1887             # code will be checking for line[12] and constraint[12], and assume it's
1888             # 'curve' if nothing else matches (silent error)
1889 1 50       4 if (defined $opts{'firstseg'}) {
1890 0         0 $firstseg = $opts{'firstseg'};
1891             } else {
1892 1         2 $firstseg = 'curve';
1893             }
1894 1 50       5 if (defined $opts{'lastseg'}) {
1895 0         0 $lastseg = $opts{'lastseg'};
1896             } else {
1897 1         3 $lastseg = 'curve';
1898             }
1899             # ratio of the length of a Bezier control point line to the distance
1900             # between the points
1901 1 50       5 if (defined $opts{'ratio'}) {
1902 0         0 $ratio = $opts{'ratio'};
1903             # clamp it (silent error) to be >0.1. probably no need to limit high end
1904 0 0       0 if ($ratio <= 0.1) { $ratio = 0.1; }
  0         0  
1905             } else {
1906 1         2 $ratio = 0.3333; # default
1907             }
1908             # colinear points (4 or more) draw a line instead of a curve
1909 1 50       5 if (defined $opts{'colinear'}) {
1910 0         0 $colinear = $opts{'colinear'}; # 'line' or 'curve'
1911             } else {
1912 1         3 $colinear = 'curve'; # default
1913             }
1914             # debug options to draw out intermediate stages
1915 1 50       22 if (defined $opts{'debug'}) {
1916 0         0 $debug = $opts{'debug'};
1917             } else {
1918 1         3 $debug = 0; # default
1919             }
1920              
1921 1         4 $self->_Gpending();
1922             # copy input point list pairs, checking for duplicates
1923 1         3 my (@inputs, $x,$y);
1924 1         5 @inputs = ([$self->{' x'}, $self->{' y'}]); # initialize to current point
1925 1         12 while (scalar(@inputPts) >= 2) {
1926 7         13 $x = shift @inputPts;
1927 7         11 $y = shift @inputPts;
1928 7         15 push @inputs, [$x, $y];
1929             # eliminate duplicate point just added
1930 7 50 66     26 if ($inputs[-2][0] == $inputs[-1][0] &&
1931             $inputs[-2][1] == $inputs[-1][1]) {
1932             # duplicate
1933 0         0 pop @inputs;
1934             }
1935             }
1936             #if (@inputPts) { leftovers ignored, as is usual practice
1937             # warn "bspline() has leftover coordinate (ignored).";
1938             #}
1939              
1940             # handle special cases of 1, 2, or 3 points in @inputs
1941 1 50       8 if (scalar @inputs == 1) {
    50          
    50          
1942             # only current point in list: no-op
1943 0         0 return $self;
1944             } elsif (scalar @inputs == 2) {
1945             # just two points: draw a line
1946 0         0 $self->line($inputs[1][0],$inputs[1][1]);
1947 0         0 return $self;
1948             } elsif (scalar @inputs == 3) {
1949             # just 3 points: adjust flags
1950 0 0       0 if ($firstseg ne 'curve') { $firstseg = 'line1'; }
  0         0  
1951 0 0       0 if ($lastseg ne 'curve') { $lastseg = 'line1'; }
  0         0  
1952             # note that if colinear, will become line2 for both
1953             }
1954              
1955             # save existing settings if debug draws anything
1956 1 50       4 if ($debug > 0) {
1957 0         0 @oldColor = $self->strokecolor();
1958 0         0 @oldFill = $self->fillcolor();
1959 0         0 $oldWidth = $self->linewidth();
1960 0         0 @oldDash = $self->linedash(-1);
1961             }
1962             # initialize working arrays
1963             # dx,dy are unit vector (sum of squares is 1)
1964             # polyline [n][0] = dx, [n][1] = dy, [n][2] = length for segment between
1965             # points n and n+1
1966             # colinpt [n] = 0 if not, 1 if it is interior colinear point
1967             # type [n] = 0 it's a Bezier curve, 1 it's a line between pts n, n+1
1968             # 2 it's a curve constraint (not drawn), 3 line constraint ND
1969             # tangent [n][0] = dx, [n][1] = dy for tangent line direction (forward)
1970             # at point n
1971             # cp [n][0][0,1] = dx,dy direction to control point "before" point n
1972             # [2] = distance from point n to this control point
1973             # [1] likewise for control point "after" point n
1974             # n=0 doesn't use "before" and n=last doesn't use "after"
1975             #
1976             # every time a tangent is set, also set the cp unit vectors, so nothing
1977             # is overlooked, even if a tangent may be changed later
1978 1         4 my ($i,$j,$k, $l, $dx,$dy, @polyline, @colinpt, @type, @tangent, @cp);
1979 1         3 my $last = $#inputs; # index number of last point (first is 0)
1980              
1981 1         5 for ($i=0; $i<=$last; $i++) { # through all points
1982 8         30 $polyline[$i] = [0,0,0];
1983 8 100       19 if ($i < $last) { # polyline[i] is line point i to i+1
1984 7         15 $dx = $inputs[$i+1][0] - $inputs[$i][0];
1985 7         15 $dy = $inputs[$i+1][1] - $inputs[$i][1];
1986 7         17 $polyline[$i][2] = $l = sqrt($dx*$dx + $dy*$dy);
1987 7         15 $polyline[$i][0] = $dx/$l;
1988 7         13 $polyline[$i][1] = $dy/$l;
1989             }
1990              
1991 8         14 $colinpt[$i] = 0; # default: not colinear at this point i
1992 8         15 $type[$i] = 0; # default: using a curve at this point i to i+1
1993             # N/A if i=last, will ignore
1994 8 100 100     29 if ($i > 0 && $i < $last) { # colinpt... look at polyline unit vectors
1995             # of lines coming into and out of point i
1996 6 50 33     19 if ($polyline[$i-1][0] == $polyline[$i][0] &&
1997             $polyline[$i-1][1] == $polyline[$i][1]) {
1998 0         0 $colinpt[$i] = 1; # same unit vector at prev point
1999             # so point is colinear (inside run)
2000             # set type[i] even if may change later
2001 0 0       0 if ($i == 1) {
2002             # point 1 is colinear? force line2 or constraint2
2003 0 0       0 if ($firstseg =~ m#^constraint#) {
2004 0         0 $firstseg = 'constraint2';
2005 0         0 $type[0] = 3;
2006             } else {
2007 0         0 $firstseg = 'line2';
2008 0         0 $type[0] = 1;
2009             }
2010 0         0 $colinpt[0] = 1; # if 1 is colinear, so is 0
2011 0         0 $type[1] = 1;
2012             }
2013 0 0       0 if ($i == $last-1) {
2014             # point last-1 is colinear? force line2 or constraint2
2015 0 0       0 if ($lastseg =~ m#^constraint#) {
2016 0         0 $lastseg = 'constraint2';
2017 0         0 $type[$i] = 3;
2018             } else {
2019 0         0 $lastseg = 'line2';
2020 0         0 $type[$i] = 1;
2021             }
2022 0         0 $colinpt[$last] = 1; # if last-1 is colinear, so is last
2023 0         0 $type[$last-2] = 1;
2024             }
2025             } # it is colinear
2026             } # looking for colinear interior points
2027             # if 3 or more colinear points at beginning or end, handle later
2028              
2029 8         43 $tangent[$i] = [0,0]; # set tangent at each point
2030             # endpoints & interior colinear points just use the polyline they're on
2031             #
2032             # at point $i, [0 1] "before" for previous curve and "after"
2033             # each [dx, dy, len] from this point to control point
2034 8         35 $cp[$i] = [[0,0,0], [0,0,0]];
2035             # at least can set the lengths here. uvecs will be set to tangents,
2036             # even though some may be changed later
2037            
2038 8 100       37 if ($i > 0) { # do 'before' cp length
2039 7         15 $cp[$i][0][2] = $polyline[$i-1][2] * $ratio;
2040             }
2041 8 100       23 if ($i < $last) { # do 'after' cp length
2042 7         41 $cp[$i][1][2] = $polyline[$i][2] * $ratio;
2043             }
2044              
2045 8 100 66     43 if ($i == 0 || $i < $last && $colinpt[$i]) {
    100 66        
2046 1         3 $cp[$i][1][0] = $tangent[$i][0] = $polyline[$i][0];
2047 1         3 $cp[$i][1][1] = $tangent[$i][1] = $polyline[$i][1];
2048 1 50       5 if ($i > 0) {
2049 0         0 $cp[$i][0][0] = -$cp[$i][1][0];
2050 0         0 $cp[$i][0][1] = -$cp[$i][1][1];
2051             }
2052             } elsif ($i == $last) {
2053 1         4 $tangent[$i][0] = $polyline[$i-1][0];
2054 1         3 $tangent[$i][1] = $polyline[$i-1][1];
2055 1         3 $cp[$i][0][0] = -$tangent[$i][0];
2056 1         4 $cp[$i][0][1] = -$tangent[$i][1];
2057             } else {
2058             # for other points, add the incoming and outgoing polylines
2059             # and normalize to unit length
2060 6         14 $dx = $polyline[$i-1][0] + $polyline[$i][0];
2061 6         10 $dy = $polyline[$i-1][1] + $polyline[$i][1];
2062 6         13 $l = sqrt($dx*$dx + $dy*$dy);
2063             # degenerate sequence A-B-A would give a length of 0, so avoid /0
2064             # TBD: look at entry and exit curves to instead have assigned
2065             # tangent go left instead of right, to avoid in some cases a
2066             # twist in the loop
2067 6 50       12 if ($l == 0) {
2068             # still no direction to it. assign 90 deg right turn
2069             # on outbound A-B (at point B)
2070 0         0 my $theta = atan2($polyline[$i-1][1], $polyline[$i-1][0]) - Math::Trig::pip2;
2071 0         0 $cp[$i][1][0] = $tangent[$i][0] = cos($theta);
2072 0         0 $cp[$i][1][1] = $tangent[$i][1] = sin($theta);
2073             } else {
2074 6         14 $cp[$i][1][0] = $tangent[$i][0] = $dx/$l;
2075 6         13 $cp[$i][1][1] = $tangent[$i][1] = $dy/$l;
2076             }
2077 6         13 $cp[$i][0][0] = -$cp[$i][1][0];
2078 6         16 $cp[$i][0][1] = -$cp[$i][1][1];
2079             }
2080             } # for loop to initialize all arrays
2081              
2082             # debug: show points, polyline, and original tangents
2083 1 50       4 if ($debug > 0) {
2084 0         0 $self->linedash(); # solid
2085 0         0 $self->linewidth(2);
2086 0         0 $self->strokecolor('green');
2087 0         0 $self->fillcolor('green');
2088              
2089             # points (debug = 1+)
2090 0         0 for ($i=0; $i<=$last; $i++) {
2091 0         0 $self->circle($inputs[$i][0],$inputs[$i][1], 2);
2092             }
2093 0         0 $self->fillstroke();
2094             # polyline (@inputs not in correct format for poly() call)
2095 0 0       0 if ($debug > 1) {
2096 0         0 $self->move($inputs[0][0], $inputs[0][1]);
2097 0         0 for ($i=1; $i<=$last; $i++) {
2098 0         0 $self->line($inputs[$i][0], $inputs[$i][1]);
2099             }
2100 0         0 $self->stroke();
2101 0         0 $self->fillcolor(@oldFill);
2102             }
2103              
2104             # original tangents (before adjustment)
2105 0 0       0 if ($debug > 2) {
2106 0         0 $self->linewidth(1);
2107 0         0 $self->strokecolor('blue');
2108 0         0 for ($i=0; $i<=$last; $i++) {
2109 0         0 $self->move($inputs[$i][0], $inputs[$i][1]);
2110 0         0 $self->line($inputs[$i][0] + 20*$tangent[$i][0],
2111             $inputs[$i][1] + 20*$tangent[$i][1]);
2112             }
2113 0         0 $self->stroke();
2114             }
2115              
2116             # prepare for control points and dashed lines
2117 0 0       0 if ($debug > 3) {
2118 0         0 $self->linedash(2); # repeating 2 on 2 off (solid for points)
2119 0         0 $self->linewidth(2); # 1 for points (circles)
2120 0         0 $self->strokecolor('red');
2121             }
2122             } # debug dump of intermediate results
2123             # at this point, @tangent unit vectors need to be adjusted for several
2124             # reasons, and @cp unit vectors need to await final tangent vectors.
2125             # @type is "displayed curve" (0) for all segments ex possibly first and last
2126              
2127             # follow colinear segments at beginning and end (not interior).
2128             # follow colinear segments from 1 to $last-1, and same $last-1 to 1,
2129             # setting type to 1 (line segment). once type set to non-zero, will
2130             # not revisit it. we should have at least 3 points ($last >= 2), and points
2131             # 0, 1, last-1, and last should already have been set. tangents already set.
2132 1         4 for ($i=1; $i<$last-1; $i++) {
2133 1 50       4 if ($colinpt[$i]) {
2134 0         0 $type[$i] = 1;
2135 0         0 $cp[$i+1][1][0] = $tangent[$i+1][0] = $polyline[$i][0];
2136 0         0 $cp[$i+1][1][1] = $tangent[$i+1][1] = $polyline[$i][1];
2137 0         0 $cp[$i+1][0][0] = -$tangent[$i+1][0];
2138 0         0 $cp[$i+1][0][1] = -$tangent[$i+1][1];
2139             } else {
2140 1         3 last;
2141             }
2142             }
2143 1         5 for ($i=$last-1; $i>1; $i--) {
2144 1 50       3 if ($colinpt[$i]) {
2145 0         0 $type[$i-1] = 1;
2146 0         0 $cp[$i-1][1][0] = $tangent[$i-1][0] = $polyline[$i-1][0];
2147 0         0 $cp[$i-1][1][1] = $tangent[$i-1][1] = $polyline[$i-1][1];
2148 0         0 $cp[$i-1][0][0] = -$tangent[$i-1][0];
2149 0         0 $cp[$i-1][0][1] = -$tangent[$i-1][1];
2150             } else {
2151 1         2 last;
2152             }
2153             }
2154              
2155             # now the major work of deciding whether line segment or Bezier curve
2156             # at each polyline segment, and placing the control points for the curves
2157             #
2158             # handle first and last segments first, as they affect tangents.
2159             # then go through, setting colinear sections to lines if requested,
2160             # or setting tangents if curves. calculate all control points from final
2161             # tangents, and draw them if debug.
2162 1         3 my ($ptheta, $ttheta, $dtheta);
2163             # special treatments for first segment
2164 1 50       18 if ($firstseg eq 'line1') {
    50          
    50          
    50          
2165             # Bezier curve from point 0 to 1, constrained to polyline at point 0
2166             # but no constraint on tangent at point 1.
2167             # should already be type 0 between points 0 and 1
2168             # point 0 tangent should already be on polyline segment
2169             } elsif ($firstseg eq 'line2') {
2170             # line drawn from point 0 to 1, constraining the tangent at point 1
2171 0         0 $type[0] = 1; # set to type 1 between points 0 and 1
2172             # no need to set tangent at point 0, or set control points
2173 0         0 $cp[1][1][0] = $tangent[1][0] = $polyline[0][0];
2174 0         0 $cp[1][1][1] = $tangent[1][1] = $polyline[0][1];
2175 0         0 $cp[1][0][0] = -$tangent[1][0];
2176 0         0 $cp[1][0][1] = -$tangent[1][1];
2177             } elsif ($firstseg eq 'constraint1') {
2178             # Bezier curve from point 0 to 1, constrained to polyline at point 0
2179             # (not drawn, allows unconstrained tangent at point 1)
2180 0         0 $type[0] = 2;
2181             # no need to set after and before, as is not drawn
2182             } elsif ($firstseg eq 'constraint2') {
2183             # line from point 0 to 1 (not drawn, only sets tangent at point 1)
2184 0         0 $type[0] = 3;
2185             # no need to set before, as is not drawn and is line anyway
2186 0         0 $cp[1][1][0] = $tangent[1][0] = $polyline[0][0];
2187 0         0 $cp[1][1][1] = $tangent[1][1] = $polyline[0][1];
2188             } else { # 'curve'
2189             # Bezier curve from point 0 to 1. both ends unconstrained, at point 0
2190             # it is just a reflection of the tangent at point 1
2191             #$type[0] = 0; # should already be 0
2192 1         21 $ptheta = atan2($polyline[0][1], $polyline[0][0]);
2193 1         5 $ttheta = atan2(-$tangent[1][1], -$tangent[1][0]);
2194 1         6 $dtheta = _leftright($ptheta, $ttheta);
2195 1         4 $ptheta = atan2(-$polyline[0][1], -$polyline[0][0]);
2196 1         4 $ttheta = _sweep($ptheta, $dtheta);
2197 1         5 $cp[0][1][0] = $tangent[0][0] = cos($ttheta); # also 'after' uvec at 0
2198 1         3 $cp[0][1][1] = $tangent[0][1] = sin($ttheta);
2199             }
2200             # special treatments for last segment
2201 1 50       20 if ($lastseg eq 'line1') {
    50          
    50          
    50          
2202             # Bezier curve from point last-1 to last, constrained to polyline at
2203             # point last but no constraint on tangent at point last-1
2204             # should already be type 0 at last-1
2205             # point last tangent should already be on polyline segment
2206             } elsif ($lastseg eq 'line2') {
2207             # line drawn from point last-1 to last, constraining the tangent at point last-1
2208 0         0 $type[$last-1] = 1;
2209             # no need to set tangent at point last, or set control points at last
2210 0         0 $cp[$last-1][1][0] = $tangent[$last-1][0] = $polyline[$last-1][0];
2211 0         0 $cp[$last-1][1][1] = $tangent[$last-1][1] = $polyline[$last-1][1];
2212 0         0 $cp[$last-1][0][0] = -$tangent[$last-1][0];
2213 0         0 $cp[$last-1][0][1] = -$tangent[$last-1][1];
2214             } elsif ($lastseg eq 'constraint1') {
2215             # Bezier curve from point last-1 to last, constrained to polyline at point last
2216             # (not drawn, allows unconstrained tangent at point last-1)
2217 0         0 $type[$last-1] = 2;
2218             } elsif ($lastseg eq 'constraint2') {
2219             # line from point last-1 to last (not drawn, only sets tangent at point last-1)
2220 0         0 $type[$last-1] = 3;
2221             # no need to set after, as is not drawn and is line anyway
2222 0         0 $tangent[$last-1][0] = $polyline[$last-1][0];
2223 0         0 $tangent[$last-1][1] = $polyline[$last-1][1];
2224 0         0 $cp[$last-1][0][0] = -$tangent[$last-1][0];
2225 0         0 $cp[$last-1][0][1] = -$tangent[$last-1][1];
2226             } else { # 'curve'
2227             # Bezier curve from point last-1 to last. both ends unconstrained, at point last
2228             # it is just a reflection of the tangent at point last-1
2229             #$type[$last-1] = 0; # should already be 0
2230 1         6 $ptheta = atan2($polyline[$last-1][1], $polyline[$last-1][0]);
2231 1         11 $ttheta = atan2($tangent[$last-1][1], $tangent[$last-1][0]);
2232 1         3 $dtheta = _leftright($ptheta, $ttheta);
2233 1         5 $ptheta = atan2(-$polyline[$last-1][1], -$polyline[$last-1][0]);
2234 1         3 $ttheta = _sweep($ptheta, $dtheta);
2235 1         4 $tangent[$last][0] = -cos($ttheta);
2236 1         3 $tangent[$last][1] = -sin($ttheta);
2237 1         3 $cp[$last][0][0] = -$tangent[$last][0]; # set 'before' unit vector at point 1
2238 1         3 $cp[$last][0][1] = -$tangent[$last][1];
2239             }
2240              
2241             # go through interior points (2..last-2) and set tangents if colinear
2242             # (and not forcing lines). by default are curves.
2243 1         15 for ($i=2; $i<$last-1; $i++) {
2244 4 50       18 if ($colinpt[$i]) {
2245             # this is a colinear point (1 or more in a row with endpoints of
2246             # run). first, find run
2247 0         0 for ($j=$i+1; $j<$last-1; $j++) {
2248 0 0       0 if (!$colinpt[$j]) { last; }
  0         0  
2249             }
2250 0         0 $j--; # back up one
2251             # here with $i = first of a run of colinear points, and $j = last
2252             # of the run. $i may equal $j (no lines to force)
2253 0 0 0     0 if ($colinear eq 'line' && $j>$i) {
2254 0         0 for ($k=$i; $k<$j; $k++) {
2255 0         0 $type[$k] = 1; # force a drawn line, ignore tangents/cps
2256             }
2257             } else {
2258             # colinear, will draw curve
2259 0         0 my ($pthetap, $tthetap, $dthetap, $count, $odd, $kk,
2260             $center, $tthetax, $same);
2261             # odd number of points or even?
2262 0         0 $count = $j - $i + 1; # only interior colinear points (>= 1)
2263 0         0 $odd = $count % 2; # odd = 1 if odd count, 0 if even
2264              
2265             # need to figure tangents for each colinear point (draw curves)
2266             # first get d-theta for entry angle, d-theta' for exit angle
2267             # for which side of polyline the entry, exit control points are
2268 0         0 $ptheta = atan2($polyline[$i-1][1], $polyline[$i-1][0]);
2269 0         0 $ttheta = atan2($tangent[$i-1][1], $tangent[$i-1][0]);
2270 0         0 $dtheta = _leftright($ptheta, $ttheta); # >=0 CCW left side
2271             # <0 CW right side
2272 0         0 $pthetap = atan2(-$polyline[$j][1], -$polyline[$j][0]);
2273 0         0 $tthetap = atan2(-$tangent[$j+1][1], -$tangent[$j+1][0]);
2274 0         0 $dthetap = _leftright($pthetap, $tthetap); # >=0 CCW right side
2275             # <0 CW left side
2276              
2277             # both dtheta and dtheta' are modified below, so preserve here
2278 0 0 0     0 if ($dtheta >= 0 && $dthetap < 0 ||
      0        
      0        
2279             $dtheta < 0 && $dthetap >= 0) {
2280             # non-colinear end tangents are on same side
2281 0         0 $same = 1;
2282             } else {
2283             # non-colinear end tangents are on opposite sides
2284 0         0 $same = 0;
2285             }
2286             # $kk is how many points on each side to set tangent at,
2287             # including $i and $j (but excluding $center)
2288 0 0       0 if ($odd) {
2289             # center (i + (count-1)/2) stays flat tangent,
2290 0         0 $kk = ($count-1)/2; # ignore if 0
2291 0         0 $center = $i + $kk;
2292             } else {
2293             # center falls between i+count/2 and i+count/2+1
2294 0         0 $kk = $count/2; # minimum 1
2295 0         0 $center = -1; # not used
2296             }
2297              
2298             # dtheta[p]/2,3,4... towards center alternating
2299             # direction from initial dtheta[p]
2300             # from left, i, i+1, i+2,...,i+kk-1, (center)
2301             # from right, j, j-1, j-2,...,j-kk+1, (center)
2302 0         0 for ($k=0; $k<$kk; $k++) {
2303             # handle i+k and j-k points
2304 0         0 $dtheta = -$dtheta;
2305 0         0 $tthetax = _sweep($ptheta, -$dtheta/($k+2));
2306 0         0 $cp[$i+$k][1][0] = $tangent[$i+$k][0] = cos($tthetax);
2307 0         0 $cp[$i+$k][1][1] = $tangent[$i+$k][1] = sin($tthetax);
2308 0         0 $cp[$i+$k][0][0] = -$tangent[$i+$k][0];
2309 0         0 $cp[$i+$k][0][1] = -$tangent[$i+$k][1];
2310              
2311 0         0 $dthetap = -$dthetap;
2312 0         0 $tthetax = _sweep($pthetap, -$dthetap/($k+2));
2313 0         0 $cp[$j-$k][1][0] = $tangent[$j-$k][0] = -cos($tthetax);
2314 0         0 $cp[$j-$k][1][1] = $tangent[$j-$k][1] = -sin($tthetax);
2315 0         0 $cp[$j-$k][0][0] = -$tangent[$j-$k][0];
2316 0         0 $cp[$j-$k][0][1] = -$tangent[$j-$k][1];
2317             }
2318              
2319             # if odd (there is a center point), either flat or averaged
2320 0 0       0 if ($odd) {
2321 0 0       0 if ($same) {
2322             # non-colinear tangents are on same side,
2323             # so tangent is flat (in line with polyline)
2324             # tangent[center] should already be set to polyline
2325             } else {
2326             # non-colinear tangents are on opposite sides
2327             # so tangent is average of both neighbors dtheta's
2328             # and is opposite sign of the left neighbor
2329 0         0 $dtheta = -($dtheta + $dthetap)/2/($kk+2);
2330 0         0 $tthetax = _sweep($ptheta, -$dtheta);
2331 0         0 $tangent[$center][0] = cos($tthetax);
2332 0         0 $tangent[$center][1] = sin($tthetax);
2333             }
2334             # finally, the cps for the center. redundant for flat
2335 0         0 $cp[$center][0][0] = -$tangent[$center][0];
2336 0         0 $cp[$center][0][1] = -$tangent[$center][1];
2337 0         0 $cp[$center][1][0] = $tangent[$center][0];
2338 0         0 $cp[$center][1][1] = $tangent[$center][1];
2339             } # odd length of run
2340             } # it IS a colinear point
2341              
2342             # done dealing with run of colinear points
2343 0         0 $i = $j; # jump ahead over the run
2344 0         0 next;
2345             # end of handling colinear points
2346             } else {
2347             # non-colinear. just set cp before and after uvecs (lengths should
2348             # already be set)
2349             }
2350             } # end of for loop through interior points
2351              
2352             # all cp entries should be set, and all type entries should be set. if
2353             # debug flag, output control points (hollow red circles) with dashed 2-2
2354             # red lines from their points
2355 1 50       4 if ($debug > 3) {
2356 0         0 for ($i=0; $i<$last; $i++) {
2357             # if a line or constraint line, no cp/line to draw
2358             # don't forget, for i=last-1 and type=0 or 2, need to draw at last
2359 0 0 0     0 if ($i < $last && ($type[$i] == 1 || $type[$i] == 3)) { next; }
  0   0     0  
2360              
2361             # have point i that is end of curve, so draw dashed line to
2362             # control point, change to narrow solid line, draw open circle,
2363             # change back to heavy dashed line for next
2364 0         0 for ($j=0; $j<2; $j++) {
2365             # j=0 'after' control point for point $i
2366             # j=1 'before' control point for point $i+1
2367              
2368             # dashed red line
2369 0         0 $self->move($inputs[$i+$j][0], $inputs[$i+$j][1]);
2370 0         0 $self->line($inputs[$i+$j][0] + $cp[$i+$j][1-$j][0]*$cp[$i+$j][1-$j][2],
2371             $inputs[$i+$j][1] + $cp[$i+$j][1-$j][1]*$cp[$i+$j][1-$j][2]);
2372 0         0 $self->stroke();
2373             # red circle
2374 0         0 $self->linewidth(1);
2375 0         0 $self->linedash();
2376 0         0 $self->circle($inputs[$i+$j][0] + $cp[$i+$j][1-$j][0]*$cp[$i+$j][1-$j][2],
2377             $inputs[$i+$j][1] + $cp[$i+$j][1-$j][1]*$cp[$i+$j][1-$j][2],
2378             2);
2379 0         0 $self->stroke();
2380             # prepare for next line
2381 0         0 $self->linewidth(2);
2382 0         0 $self->linedash(2);
2383             }
2384             } # loop through all points
2385             } # debug == 3
2386              
2387             # restore old settings
2388 1 50       19 if ($debug > 0) {
2389 0         0 $self->fillstroke();
2390 0         0 $self->strokecolor(@oldColor);
2391 0         0 $self->linewidth($oldWidth);
2392 0         0 $self->linedash(@oldDash);
2393             }
2394              
2395             # the final act: go through each segment and draw either a line or a
2396             # curve
2397 1 50       4 if ($type[0] < 2) { # start drawing at 0 or 1?
2398 1         6 $self->move($inputs[0][0], $inputs[0][1]);
2399             } else {
2400 0         0 $self->move($inputs[1][0], $inputs[1][1]);
2401             }
2402 1         5 for ($i=0; $i<$last; $i++) {
2403 7 50       19 if ($type[$i] > 1) { next; } # 2, 3 constraints, not drawn
  0         0  
2404 7 50       15 if ($type[$i] == 0) {
2405             # Bezier curve, use $cp[$i][1] and $cp[$i+1][0] to generate
2406             # points for curve call
2407 7         84 $self->curve($inputs[$i][0] + $cp[$i][1][0]*$cp[$i][1][2],
2408             $inputs[$i][1] + $cp[$i][1][1]*$cp[$i][1][2],
2409             $inputs[$i+1][0] + $cp[$i+1][0][0]*$cp[$i+1][0][2],
2410             $inputs[$i+1][1] + $cp[$i+1][0][1]*$cp[$i+1][0][2],
2411             $inputs[$i+1][0],
2412             $inputs[$i+1][1]);
2413             } else {
2414             # line to next point
2415 0         0 $self->line($inputs[$i+1][0], $inputs[$i+1][1]);
2416             }
2417             }
2418            
2419 1         17 return $self;
2420             }
2421             # helper function for bspline()
2422             # given two unit vectors (direction in radians), return the delta change in
2423             # direction (radians) of the first vector to the second. left is positive.
2424             sub _leftright {
2425 2     2   7 my ($ptheta, $ttheta) = @_;
2426             # ptheta is the angle (radians) of the polyline vector from one
2427             # point to the next, and ttheta is the tangent vector at the point
2428 2         11 my ($dtheta, $antip);
2429              
2430 2 100 33     18 if ($ptheta >= 0 && $ttheta >= 0 || # both in top half (QI, QII)
      66        
      66        
2431             $ptheta < 0 && $ttheta < 0) { # both in bottom half (QIII, QIV)
2432 1         3 $dtheta = $ttheta - $ptheta;
2433             } else { # p in top half (QI, QII), t,antip in bottom half (QIII, QIV)
2434             # or p in bottom half, t,antip in top half
2435 1 50       4 if ($ttheta < 0) {
2436 0         0 $antip = $ptheta - pi;
2437             } else {
2438 1         3 $antip = $ptheta + pi;
2439             }
2440 1 50       3 if ($ttheta <= $antip) {
2441 0         0 $dtheta = pi - $antip + $ttheta; # pi - (antip - ttheta)
2442             } else {
2443 1         3 $dtheta = $ttheta - $antip - pi; # (ttheta - antip) - pi
2444             }
2445             }
2446              
2447 2         6 return $dtheta;
2448             }
2449             # helper function. given a unit direction ptheta, swing +dtheta radians right,
2450             # return normalized result
2451             sub _sweep {
2452 2     2   6 my ($ptheta, $dtheta) = @_;
2453 2         4 my ($max, $result);
2454              
2455 2 50       30 if ($ptheta >= 0) { # p in QI or QII
2456 2 50       7 if ($dtheta >= 0) { # delta CW radians
2457 0         0 $result = $ptheta - $dtheta; # OK to go into bottom quadrants
2458             } else { # delta CCW radians
2459 2         4 $max = pi - $ptheta; # max delta (>0) to stay in top quadrants
2460 2 50       6 if ($max >= -$dtheta) { # end up still in top quadrants
2461 2         4 $result = $ptheta - $dtheta;
2462             } else { # into bottom quadrants
2463 0         0 $dtheta += $max; # remaining CCW amount from -pi
2464 0         0 $result = -1*pi - $dtheta; # -pi caused some problems
2465             }
2466             }
2467             } else { # p in QIII or QIV
2468 0 0       0 if ($dtheta >= 0) { # delta CW radians
2469 0         0 $max = pi + $ptheta; # max delta (>0) to stay in bottom quadrants
2470 0 0       0 if ($max >= $dtheta) { # end up still in bottom quadrants
2471 0         0 $result = $ptheta - $dtheta;
2472             } else { # into top quadrants
2473 0         0 $dtheta -= $max; # remaining CCW amount from +pi
2474 0         0 $result = pi - $dtheta;
2475             }
2476             } else { # delta CCW radians
2477 0         0 $result = $ptheta - $dtheta; # OK to go into top quadrants
2478             }
2479             }
2480              
2481 2         5 return $result;
2482             }
2483              
2484             =head4 bogen
2485              
2486             $content->bogen($x1,$y1, $x2,$y2, $radius, $move, $larger, $reverse)
2487              
2488             $content->bogen($x1,$y1, $x2,$y2, $radius, $move, $larger)
2489              
2490             $content->bogen($x1,$y1, $x2,$y2, $radius, $move)
2491              
2492             $content->bogen($x1,$y1, $x2,$y2, $radius)
2493              
2494             =over
2495              
2496             (I<bogen> is German for I<bow>, as in a segment (arc) of a circle. This is a
2497             segment of a circle defined by the intersection of two circles of a given
2498             radius, with the two intersection points as inputs. There are B<four> possible
2499             resulting arcs, which can be selected with C<$larger> and C<$reverse>.)
2500              
2501             This extends the path along an arc of a circle of the specified radius
2502             between C<[$x1,$y1]> to C<[$x2,$y2]>. The current position is then set
2503             to the endpoint of the arc (C<[$x2,$y2]>).
2504              
2505             Set C<$move> to a I<true> value if this arc is the beginning of a new
2506             path instead of the continuation of an existing path. Note that the default
2507             (C<$move> = I<false>) is
2508             I<not> a straight line to I<P1> and then the arc, but a blending into the curve
2509             from the current point. It will often I<not> pass through I<P1>!
2510              
2511             Set C<$larger> to a I<true> value to draw the larger ("outer") arc between the
2512             two points, instead of the smaller one. Both arcs are drawn I<clockwise> from
2513             I<P1> to I<P2>. The default value of I<false> draws the smaller arc.
2514             Note that the "other" circle's larger arc is used (the center point is
2515             "flipped" across the line between I<P1> and I<P2>), rather than using the
2516             "remainder" of the smaller arc's circle (which would necessitate reversing the
2517             direction of travel along the arc -- see C<$reverse>).
2518              
2519             Set C<$reverse> to a I<true> value to draw the mirror image of the
2520             specified arc (flip it over, so that its center point is on the other
2521             side of the line connecting the two points). Both arcs are drawn
2522             I<counter-clockwise> from I<P1> to I<P2>. The default (I<false>) draws
2523             clockwise arcs. An arc is B<always> drawn from I<P1> to I<P2>; the direction
2524             (clockwise or counter-clockwise) may be chosen.
2525              
2526             The C<$radius> value cannot be smaller than B<half> the distance from
2527             C<[$x1,$y1]> to C<[$x2,$y2]>. If it is too small, the radius will be set to
2528             half the distance between the points (resulting in an arc that is a
2529             semicircle). This is a silent error, as even if the points are correct, due
2530             to rounding etc. they may not fall I<exactly> on the two circles.
2531              
2532             You can think of "looking" from I<P1> to I<P2>. In the degenerate case, where
2533             the radius is exactly half the distance between the points, there is no
2534             difference between "small" and "large" arcs, and both circles will coincide
2535             with their center half way between I<P1> and I<P2>. Only the direction matters.
2536             Once the radius is any larger, the two circles become distinct. The primary
2537             circle is centered to your right, whose small arc is CW on your left; the
2538             secondary circle is centered to your left, whose small arc is CCW on your
2539             right. The "large" arcs are the arcs using the remainder of the circles: CW
2540             large is part of the left (secondary) circle, and CCW large is part of the
2541             right (primary) circle.
2542              
2543             =back
2544              
2545             =cut
2546              
2547             sub bogen {
2548 8     8 1 96 my ($self, $x1,$y1, $x2,$y2, $r, $move, $larc, $dir) = @_;
2549             # in POD description, dir is "reverse" flag
2550              
2551 8         47 my ($p0_x,$p0_y, $p1_x,$p1_y, $p2_x,$p2_y, $p3_x,$p3_y);
2552 8         0 my ($dx,$dy, $x,$y, $alpha,$beta, $alpha_rad, $d,$z, @points);
2553              
2554 8 50 33     44 if ($x1 == $x2 && $y1 == $y2) {
2555 0         0 die "bogen requires two distinct points";
2556             # SVG def of (arc) merely leaves it as a point
2557             }
2558 8 50       36 if ($r <= 0.0) {
2559 0         0 die "bogen requires a positive radius";
2560             # SVG def of (arc) merely takes absolute value
2561             }
2562 8 50       29 $move = 0 if !defined $move;
2563 8 100       29 $larc = 0 if !defined $larc;
2564 8 100       28 $dir = 0 if !defined $dir;
2565              
2566 8         40 $self->_Gpending();
2567 8         17 $dx = $x2 - $x1;
2568 8         20 $dy = $y2 - $y1;
2569 8         33 $z = sqrt($dx**2 + $dy**2);
2570 8         68 $alpha_rad = asin($dy/$z); # |dy/z| guaranteed <= 1.0
2571 8 50       156 $alpha_rad = pi - $alpha_rad if $dx < 0;
2572              
2573             # alpha is direction of vector P1 to P2
2574 8         63 $alpha = rad2deg($alpha_rad);
2575             # use the complementary angle for flipped arc (arc center on other side)
2576             # effectively clockwise draw from P2 to P1
2577 8 100       152 $alpha -= 180 if $dir;
2578              
2579 8         19 $d = 2*$r;
2580             # z/d must be no greater than 1.0 (arcsine arg)
2581 8 50       30 if ($z > $d) {
2582 0         0 $d = $z; # SILENT error and fixup
2583 0         0 $r = $d/2;
2584             }
2585              
2586 8         32 $beta = rad2deg(2*asin($z/$d));
2587             # beta is the sweep P1 to P2: ~0 (r very large) to 180 degrees (min r)
2588 8 100       117 $beta = 360-$beta if $larc; # large arc is remainder of small arc
2589             # for large arc, beta could approach 360 degrees if r is very large
2590              
2591             # always draw CW (dir=1)
2592             # note that start and end could be well out of +/-360 degree range
2593 8         63 @points = _arctocurve($r,$r, 90+$alpha+$beta/2,90+$alpha-$beta/2, 1);
2594              
2595 8 100       36 if ($dir) { # flip order of points for reverse arc
2596 2         12 my @pts = @points;
2597 2         7 @points = ();
2598 2         11 while (@pts) {
2599 32         50 $y = pop @pts;
2600 32         52 $x = pop @pts;
2601 32         85 push(@points, $x,$y);
2602             }
2603             }
2604              
2605 8         22 $p0_x = shift @points;
2606 8         21 $p0_y = shift @points;
2607 8         23 $x = $x1 - $p0_x;
2608 8         17 $y = $y1 - $p0_y;
2609              
2610 8 100       41 $self->move($x1,$y1) if $move;
2611              
2612 8         142 while (scalar @points > 0) {
2613 72         136 $p1_x = $x + shift @points;
2614 72         151 $p1_y = $y + shift @points;
2615 72         145 $p2_x = $x + shift @points;
2616 72         118 $p2_y = $y + shift @points;
2617             # if we run out of data points, use the end point instead
2618 72 50       156 if (scalar @points == 0) {
2619 0         0 $p3_x = $x2;
2620 0         0 $p3_y = $y2;
2621             } else {
2622 72         127 $p3_x = $x + shift @points;
2623 72         152 $p3_y = $y + shift @points;
2624             }
2625 72         241 $self->curve($p1_x,$p1_y, $p2_x,$p2_y, $p3_x,$p3_y);
2626 72         137 shift @points;
2627 72         185 shift @points;
2628             }
2629              
2630 8         34 return $self;
2631             }
2632              
2633             =head2 Path Painting (Drawing)
2634              
2635             =head3 stroke
2636              
2637             $content->stroke()
2638              
2639             =over
2640              
2641             Strokes the current path. That is, it is drawing solid or dashed I<lines>, but
2642             B<not> filling areas.
2643              
2644             =back
2645              
2646             =cut
2647              
2648             sub _stroke {
2649 134     134   467 return 'S';
2650             }
2651              
2652             sub stroke {
2653 134     134 1 504 my ($self) = shift;
2654              
2655 134         464 $self->_Gpending(); # flush buffered commands
2656 134         383 $self->add(_stroke());
2657              
2658 134         322 return $self;
2659             }
2660              
2661             =head3 fill
2662              
2663             $content->fill($use_even_odd_fill)
2664              
2665             $content->fill('rule' => $rule)
2666              
2667             $content->fill() # use default nonzero rule
2668              
2669             =over
2670              
2671             Fill the current path's enclosed I<area>.
2672             It does I<not> stroke the enclosing path around the area.
2673              
2674             =over
2675              
2676             =item $user_even_odd_fill = 0 or I<false> (B<default>)
2677              
2678             =item $rule = 'nonzero'
2679              
2680             If the path intersects with itself, the I<nonzero> winding rule will be
2681             used to determine which part of the path is filled in. This basically
2682             fills in I<everything> inside the path, except in some situations depending
2683             on the direction of the path.
2684              
2685             =item $user_even_odd_fill = 1 (non-zero value) or I<true>
2686              
2687             =item $rule = 'even-odd'
2688              
2689             If the path intersects with itself, the I<even-odd> winding rule will be
2690             used to determine which part of the path is filled in. In most cases, this
2691             means that the filling state alternates each time the path is intersected.
2692             This basically will fill alternating closed sub-areas.
2693              
2694             =back
2695              
2696             See the PDF Specification, section 8.5.3.3 (in version 1.7),
2697             for more details on filling.
2698              
2699             The "rule" parameter is added for PDF::API2 compatibility.
2700              
2701             =back
2702              
2703             =cut
2704              
2705             sub fill {
2706 3     3 1 25 my ($self) = shift;
2707              
2708 3         19 $self->_Gpending(); # flush buffered commands
2709 3         7 my $even_odd = 0; # default (use non-zero rule)
2710 3 50       12 if (@_ == 2) { # hash list (one element) given
2711 0         0 my %opts = @_;
2712 0 0 0     0 if (defined $opts{'-rule'} && !defined $opts{'rule'}) { $opts{'rule'} = delete($opts{'-rule'}); }
  0         0  
2713 0 0 0     0 if (($opts{'rule'} // 'nonzero') eq 'even-odd') {
2714 0         0 $even_odd = 1;
2715             }
2716             } else { # single value (boolean)
2717 3         9 $even_odd = shift();
2718             }
2719              
2720 3 100       24 $self->add($even_odd ? 'f*' : 'f');
2721              
2722 3         11 return $self;
2723             }
2724              
2725             =head3 fillstroke, paint, fill_stroke
2726              
2727             $content->fillstroke($use_even_odd_fill)
2728              
2729             $content->fillstroke('rule' => $rule)
2730              
2731             $content->fillstroke() # use default nonzero rule
2732              
2733             =over
2734              
2735             B<Fill> the current path's enclosed I<area> and then B<stroke> the enclosing
2736             path around the area (possibly with a different color).
2737              
2738             =over
2739              
2740             =item $user_even_odd_fill = 0 or I<false> (B<default>)
2741              
2742             =item $rule = 'nonzero'
2743              
2744             If the path intersects with itself, the I<nonzero> winding rule will be
2745             used to determine which part of the path is filled in. This basically
2746             fills in I<everything> inside the path, except in some situations depending
2747             on the direction of the path.
2748              
2749             =item $user_even_odd_fill = 1 (non-zero value) or I<true>
2750              
2751             =item $rule = 'even-odd'
2752              
2753             If the path intersects with itself, the I<even-odd> winding rule will be
2754             used to determine which part of the path is filled in. In most cases, this
2755             means that the filling state alternates each time the path is intersected.
2756             This basically will fill alternating closed sub-areas.
2757              
2758             =back
2759              
2760             See the PDF Specification, section 8.5.3.3 (in version 1.7),
2761             for more details on filling.
2762              
2763             The "rule" parameter is added for PDF::API2 compatibility.
2764              
2765             B<Alternate names:> C<paint> and C<fill_stroke>
2766              
2767             C<paint> is for compatibility with PDF::API2, while C<fill_stroke> is added
2768             for compatibility with many other PDF::API2-related renamed methods.
2769              
2770             =back
2771              
2772             =cut
2773              
2774 0     0 1 0 sub paint { return fillstroke(@_); } ## no critic
2775              
2776 0     0 1 0 sub fill_stroke { return fillstroke(@_); } ## no critic
2777              
2778             sub fillstroke {
2779 4     4 1 38 my ($self) = shift;
2780              
2781 4         12 my $even_odd = 0; # default (use non-zero rule)
2782 4 50       16 if (@_ == 2) { # hash list (one element) given
2783 0         0 my %opts = @_;
2784 0 0 0     0 if (defined $opts{'-rule'} && !defined $opts{'rule'}) { $opts{'rule'} = delete($opts{'-rule'}); }
  0         0  
2785 0 0 0     0 if (($opts{'rule'} // 'nonzero') eq 'even-odd') {
2786 0         0 $even_odd = 1;
2787             }
2788             } else { # single value (boolean)
2789 4         13 $even_odd = shift();
2790             }
2791              
2792 4 100       34 $self->add($even_odd ? 'B*' : 'B');
2793              
2794 4         37 return $self;
2795             }
2796              
2797             =head3 clip
2798              
2799             $content->clip($use_even_odd_fill)
2800              
2801             $content->clip('rule' => $rule)
2802              
2803             $content->clip() # use default nonzero rule
2804              
2805             =over
2806              
2807             Modifies the current clipping path by intersecting it with the current
2808             path. Initially (a fresh page), the clipping path is the entire media. Each
2809             definition of a path, and a C<clip()> call, intersects the new path with the
2810             existing clip path, so the resulting clip path is no larger than the new path,
2811             and may even be empty if the intersection is null.
2812              
2813             =over
2814              
2815             =item $user_even_odd_fill = 0 or I<false> (B<default>)
2816              
2817             =item $rule = 'nonzero'
2818              
2819             If the path intersects with itself, the I<nonzero> winding rule will be
2820             used to determine which part of the path is included (clipped in or out).
2821             This basically includes I<everything> inside the path, except in some
2822             situations depending on the direction of the path.
2823              
2824             =item $user_even_odd_fill = 1 (non-zero value) or I<true>
2825              
2826             =item $rule = 'even-odd'
2827              
2828             If the path intersects with itself, the I<even-odd> winding rule will be
2829             used to determine which part of the path is included. In most cases, this
2830             means that the inclusion state alternates each time the path is intersected.
2831             This basically will include alternating closed sub-areas.
2832              
2833             =back
2834              
2835             It is common usage to make the
2836             C<endpath()> call (B<n>) after the C<clip()> call, to clear the path (unless
2837             you want to reuse that path, such as to fill and/or stroke it to show the clip
2838             path). If you want to clip text glyphs, it gets rather complicated, as a clip
2839             port cannot be created within a text object (that will have an effect on text).
2840             See the object discussion in L<PDF::Builder::Docs/Rendering Order>.
2841              
2842             my $grfxC1 = $page->gfx();
2843             my $textC = $page->text();
2844             my $grfxC2 = $page->gfx();
2845             ...
2846             $grfxC1->save();
2847             $grfxC1->endpath();
2848             $grfxC1->rect(...);
2849             $grfxC1->clip();
2850             $grfxC1->endpath();
2851             ...
2852             $textC-> output text to be clipped
2853             ...
2854             $grfxC2->restore();
2855              
2856             The "rule" parameter is added for PDF::API2 compatibility.
2857              
2858             =back
2859              
2860             =cut
2861              
2862             sub clip {
2863 3     3 1 30 my ($self) = shift;
2864              
2865 3         11 my $even_odd = 0; # default (use non-zero rule)
2866 3 50       15 if (@_ == 2) { # hash list (one element) given
2867 0         0 my %opts = @_;
2868 0 0 0     0 if (defined $opts{'-rule'} && !defined $opts{'rule'}) { $opts{'rule'} = delete($opts{'-rule'}); }
  0         0  
2869 0 0 0     0 if (($opts{'rule'} // 'nonzero') eq 'even-odd') {
2870 0         0 $even_odd = 1;
2871             }
2872             } else { # single value (boolean)
2873 3         7 $even_odd = shift();
2874             }
2875              
2876 3 100       43 $self->add($even_odd ? 'W*' : 'W');
2877              
2878 3         10 return $self;
2879             }
2880              
2881             =head3 endpath, end
2882              
2883             $content->endpath()
2884              
2885             =over
2886              
2887             Ends the current path without explicitly enclosing it.
2888             That is, unlike C<close>, there is B<no> line segment
2889             drawn back to the starting position.
2890             This is often used to end the current path without filling or
2891             stroking, for the side effect of changing the current clipping path.
2892              
2893             B<Alternate name:> C<end>
2894              
2895             This is provided for compatibility with PDF::API2. Do not confuse it with
2896             the C<$pdf-E<gt>end()> method!
2897              
2898             =back
2899              
2900             =cut
2901              
2902 0     0 1 0 sub end { return endpath(@_); } ## no critic
2903              
2904             sub endpath {
2905 2     2 1 17 my ($self) = shift;
2906              
2907 2         9 $self->add('n');
2908              
2909 2         5 return $self;
2910             }
2911              
2912             =head3 shade
2913              
2914             $content->shade($shade, @coord)
2915              
2916             =over
2917              
2918             Sets the shading matrix.
2919              
2920             =over
2921              
2922             =item $shade
2923              
2924             A hash reference that includes a C<name()> method for the shade name.
2925              
2926             =item @coord
2927              
2928             An array of 4 items: X-translation, Y-translation,
2929             X-scaled and translated, Y-scaled and translated.
2930              
2931             =back
2932              
2933             =back
2934              
2935             =cut
2936              
2937             sub shade {
2938 0     0 1 0 my ($self, $shade, @coord) = @_;
2939              
2940 0         0 my @tm = (
2941             $coord[2]-$coord[0] , 0,
2942             0 , $coord[3]-$coord[1],
2943             $coord[0] , $coord[1]
2944             );
2945 0         0 $self->save();
2946 0         0 $self->matrix(@tm);
2947 0         0 $self->add('/'.$shade->name(), 'sh');
2948              
2949 0         0 $self->resource('Shading', $shade->name(), $shade);
2950 0         0 $self->restore();
2951              
2952 0         0 return $self;
2953             }
2954              
2955             =head2 Colors
2956              
2957             =head3 fillcolor, fill_color, strokecolor, stroke_color
2958              
2959             $content->fillcolor($color)
2960              
2961             $content->strokecolor($color)
2962              
2963             =over
2964              
2965             Sets the fill (enclosed area) or stroke (path) color. The interior of text
2966             characters are I<filled>, and (I<if> ordered by C<render>) the outline is
2967             I<stroked>.
2968              
2969             # Use a named color
2970             # -> RGB color model
2971             # there are many hundreds of named colors defined in
2972             # PDF::Builder::Resource::Colors
2973             $content->fillcolor('blue');
2974              
2975             # Use an RGB color (# followed by 3, 6, 9, or 12 hex digits)
2976             # -> RGB color model
2977             # This maps to 0-1.0 values for red, green, and blue
2978             $content->fillcolor('#FF0000'); # red
2979              
2980             # Use a CMYK color (% followed by 4, 8, 12, or 16 hex digits)
2981             # -> CMYK color model
2982             # This maps to 0-1.0 values for cyan, magenta, yellow, and black
2983             $content->fillcolor('%FF000000'); # cyan
2984             # Note: you might wish to make use of packages such as
2985             # HashData::Color::PantoneToCMYK to map "Pantone" color names/codes to a
2986             # set of CMYK values
2987              
2988             # Use an HSV color (! followed by 3, 6, 9, or 12 hex digits)
2989             # -> RGB color model
2990             # This maps to 0-360 degrees for the hue, and 0-1.0 values for
2991             # saturation and value
2992             $content->fillcolor('!FF0000');
2993              
2994             # Use an HSL color (& followed by 3, 6, 9, or 12 hex digits)
2995             # -> L*a*b color model
2996             # This maps to 0-360 degrees for the hue, and 0-1.0 values for
2997             # saturation and lightness. Note that 360 degrees = 0 degrees (wraps)
2998             $content->fillcolor('&FF0000');
2999              
3000             # Use an L*a*b color ($ followed by 3, 6, 9, or 12 hex digits)
3001             # -> L*a*b color model
3002             # This maps to 0-100 for L, -100 to 100 for a and b
3003             $content->fillcolor('$FF0000');
3004              
3005             In all cases, if too few digits are given, the given digits
3006             are silently right-padded with 0's (zeros). If an incorrect number
3007             of digits are given, the next lowest number of expected
3008             digits are used, and the remaining digits are silently ignored.
3009              
3010             # A single number between 0.0 (black) and 1.0 (white) is an alternate way
3011             # of specifying a gray scale.
3012             $content->fillcolor(0.5);
3013              
3014             # Three array elements between 0.0 and 1.0 is an alternate way of specifying
3015             # an RGB color.
3016             $content->fillcolor(0.3, 0.59, 0.11);
3017              
3018             # Four array elements between 0.0 and 1.0 is an alternate way of specifying
3019             # a CMYK color.
3020             $content->fillcolor(0.1, 0.9, 0.3, 1.0);
3021              
3022             In all cases, if a number is less than 0, it is silently turned into a 0. If
3023             a number is greater than 1, it is silently turned into a 1. This "clamps" all
3024             values to the range 0.0-1.0.
3025              
3026             # A single reference is treated as a pattern or shading space.
3027              
3028             # Two or more entries with the first element a Perl reference, is treated
3029             # as either an indexed colorspace reference plus color-index(es), or
3030             # as a custom colorspace reference plus parameter(s).
3031              
3032             If no value was passed in, the current fill color (or stroke color) I<array>
3033             is B<returned>, otherwise C<$self> is B<returned>.
3034              
3035             B<Alternate names:> C<fill_color> and C<stroke_color>.
3036              
3037             These are provided for PDF::API2 compatibility.
3038              
3039             =back
3040              
3041             =cut
3042              
3043             # TBD document in POD (examples) and add t tests for (pattern/shading space,
3044             # indexed colorspace + color-index, or custom colorspace + parameter)
3045             # for both fillcolor() and strokecolor(). t/cs-webcolor.t does test
3046             # cs + index
3047              
3048             # note that namecolor* routines all handle #, %, !, &, and named
3049             # colors, even though _makecolor only sends each type to proper
3050             # routine. reserved for different output color models?
3051              
3052             # I would have preferred to move _makecolor and _clamp over to Util.pm, but
3053             # some subtle errors were showing up. Maybe in the future...
3054             sub _makecolor {
3055 36     36   195 my ($self, $sf, @clr) = @_;
3056              
3057             # $sf is the stroke/fill flag (0/1)
3058             # note that a scalar argument is turned into a single element array
3059             # there will be at least one element, guaranteed
3060              
3061 36 100       190 if (scalar @clr == 1) { # a single @clr element
    50          
3062 31 50       242 if (ref($clr[0])) {
    100          
    100          
    100          
3063             # pattern or shading space
3064 0 0       0 return '/Pattern', ($sf? 'cs': 'CS'), '/'.($clr[0]->name()), ($sf? 'scn': 'SCN');
    0          
3065            
3066             } elsif ($clr[0] =~ m/^[a-z#!]/i) {
3067             # colorname (alpha) or # (RGB) or ! (HSV) specifier and 3/6/9/12 digits
3068             # with rgb target colorspace
3069             # namecolor always returns an RGB
3070             #return namecolor($clr[0]), ($sf? 'rg': 'RG');
3071 25 100       144 return join(' ',namecolor($clr[0])).' '.($sf? 'rg': 'RG');
3072            
3073             } elsif ($clr[0] =~ m/^%/) {
3074             # % (CMYK) specifier and 4/8/12/16 digits
3075             # with cmyk target colorspace
3076             #return namecolor_cmyk($clr[0]), ($sf? 'k': 'K');
3077 2 100       16 return join(' ',namecolor_cmyk($clr[0])).' '.($sf? 'k': 'K');
3078              
3079             } elsif ($clr[0] =~ m/^[\$\&]/) {
3080             # & (HSL) or $ (L*a*b) specifier
3081             # with L*a*b target colorspace
3082 2 50       10 if (!defined $self->resource('ColorSpace', 'LabS')) {
3083 2         7 my $dc = PDFDict();
3084 2         7 my $cs = PDFArray(PDFName('Lab'), $dc);
3085 2         7 $dc->{'WhitePoint'} = PDFArray(map { PDFNum($_) } qw(1 1 1));
  6         17  
3086 2         7 $dc->{'Range'} = PDFArray(map { PDFNum($_) } qw(-128 127 -128 127));
  8         29  
3087 2         7 $dc->{'Gamma'} = PDFArray(map { PDFNum($_) } qw(2.2 2.2 2.2));
  6         14  
3088 2         8 $self->resource('ColorSpace', 'LabS', $cs);
3089             }
3090             #return '/LabS', ($sf? 'cs': 'CS'), namecolor_lab($clr[0]), ($sf? 'sc': 'SC');
3091 2 100       16 return '/LabS '.($sf? 'cs': 'CS').' '.join(' ',namecolor_lab($clr[0])).' '.($sf? 'sc': 'SC');
    100          
3092              
3093             } else { # should be a float number... add a test and else failure?
3094             # grey color spec.
3095 2         12 $clr[0] = _clamp($clr[0], 0, 0, 1);
3096             #return $clr[0], ($sf? 'g': 'G');
3097 2 100       16 return $clr[0].' '.($sf? 'g': 'G');
3098              
3099             #} else {
3100             # die 'invalid color specification.';
3101             } # @clr 1 element
3102              
3103             } elsif (scalar @clr > 1) { # 2 or more @clr elements
3104 5 100       28 if (ref($clr[0])) {
    100          
    50          
3105             # indexed colorspace plus color-index(es)
3106             # or custom colorspace plus param(s)
3107 1         2 my $cs = shift @clr;
3108             #return '/'.($cs->name()).' '.($sf? 'cs': 'CS').' '.($cs->param(@clr)).' '.($sf? 'sc': 'SC');
3109 1         3 my $out = '/'.($cs->name());
3110 1 50       2 $out .= ' '.($sf? 'cs': 'CS');
3111 1         4 $out .= " @clr";
3112 1 50       2 $out .= ' '.($sf? 'sc': 'SC');
3113 1         4 return $out;
3114              
3115             # What exactly is the difference between the following case and the
3116             # previous case? The previous allows multiple indices or parameters and
3117             # this one doesn't. Also, this one would try to process a bad call like
3118             # fillcolor('blue', 'gray').
3119             #} elsif (scalar @clr == 2) {
3120             # # indexed colorspace plus color-index
3121             # # or custom colorspace plus param
3122             # return '/'.$clr[0]->name(), ($sf? 'cs': 'CS'), $clr[0]->param($clr[1]), ($sf? 'sc': 'SC');
3123              
3124             } elsif (scalar @clr == 3) {
3125             # legacy rgb color-spec (0 <= x <= 1)
3126 2         12 $clr[0] = _clamp($clr[0], 0, 0, 1);
3127 2         7 $clr[1] = _clamp($clr[1], 0, 0, 1);
3128 2         7 $clr[2] = _clamp($clr[2], 0, 0, 1);
3129             #return floats($clr[0], $clr[1], $clr[2]), ($sf? 'rg': 'RG');
3130 2 100       13 return join(' ',floats($clr[0], $clr[1], $clr[2])).' '.($sf? 'rg': 'RG');
3131              
3132             } elsif (scalar @clr == 4) {
3133             # legacy cmyk color-spec (0 <= x <= 1)
3134 2         12 $clr[0] = _clamp($clr[0], 0, 0, 1);
3135 2         8 $clr[1] = _clamp($clr[1], 0, 0, 1);
3136 2         6 $clr[2] = _clamp($clr[2], 0, 0, 1);
3137 2         7 $clr[3] = _clamp($clr[3], 0, 0, 1);
3138             #return floats($clr[0], $clr[1], $clr[2], $clr[3]), ($sf? 'k': 'K');
3139 2 100       15 return join(' ',floats($clr[0], $clr[1], $clr[2], $clr[3])).' '.($sf? 'k': 'K');
3140              
3141             } else {
3142 0         0 die 'invalid color specification.';
3143             } # @clr with 2 or more elements
3144              
3145             } else { # @clr with 0 elements. presumably won't see...
3146 0         0 die 'invalid color specification.';
3147             }
3148             }
3149              
3150             # silent error if non-numeric value (assign default),
3151             # or outside of min..max limits (clamp to closer limit).
3152             sub _clamp {
3153 16     16   39 my ($val, $default, $min, $max) = @_;
3154              
3155 16 50       64 if (!Scalar::Util::looks_like_number($val)) { $val = $default; }
  0         0  
3156 16 100       58 if ($val < $min) {
    100          
3157 1         3 $val = $min;
3158             } elsif ($val > $max) {
3159 2         6 $val = $max;
3160             }
3161              
3162 16         40 return $val;
3163             }
3164              
3165             sub _fillcolor {
3166 20     20   65 my ($self, @clrs) = @_;
3167              
3168 20 50       122 if (ref($clrs[0]) =~ m|^PDF::Builder::Resource::ColorSpace|) {
    50          
3169 0         0 $self->resource('ColorSpace', $clrs[0]->name(), $clrs[0]);
3170             } elsif (ref($clrs[0]) =~ m|^PDF::Builder::Resource::Pattern|) {
3171 0         0 $self->resource('Pattern', $clrs[0]->name(), $clrs[0]);
3172             }
3173              
3174 20         128 return $self->_makecolor(1, @clrs);
3175             }
3176              
3177 1     1 1 12 sub fill_color { return fillcolor(@_); } ## no critic
3178              
3179             sub fillcolor {
3180 20     20 1 140 my $self = shift;
3181              
3182 20 50       67 if (@_) {
3183 20         47 @{$self->{' fillcolor'}} = @_;
  20         253  
3184 20         118 my $string = $self->_fillcolor(@_);
3185 20 50       199 if ($self->_in_text_object()) {
3186 0 0       0 if ($self->{' doPending'}) {
3187 0         0 $self->{' Tpending'}{'color'} = $string;
3188             } else {
3189 0         0 $self->add($string);
3190             }
3191             } else {
3192 20 50       57 if ($self->{' doPending'}) {
3193 0         0 $self->{' Gpending'}{'color'} = $string;
3194             } else {
3195 20         93 $self->add($string);
3196             }
3197             }
3198              
3199 20         65 return $self;
3200             } else {
3201              
3202 0         0 return @{$self->{' fillcolor'}};
  0         0  
3203             }
3204             }
3205              
3206             sub _strokecolor {
3207 16     16   44 my ($self, @clrs) = @_;
3208              
3209 16 100       91 if (ref($clrs[0]) =~ m|^PDF::Builder::Resource::ColorSpace|) {
    50          
3210 1         5 $self->resource('ColorSpace', $clrs[0]->name(), $clrs[0]);
3211             } elsif (ref($clrs[0]) =~ m|^PDF::Builder::Resource::Pattern|) {
3212 0         0 $self->resource('Pattern', $clrs[0]->name(), $clrs[0]);
3213             }
3214              
3215 16         74 return $self->_makecolor(0, @clrs);
3216             }
3217              
3218 1     1 1 16 sub stroke_color { return strokecolor(@_); } ## no critic
3219              
3220             sub strokecolor {
3221 16     16 1 96 my $self = shift;
3222              
3223 16 50       51 if (@_) {
3224 16         36 @{$self->{' strokecolor'}} = @_;
  16         51  
3225 16         121 my $string = $self->_strokecolor(@_);
3226 16 50       76 if ($self->_in_text_object()) {
3227 0 0       0 if ($self->{' doPending'}) {
3228 0         0 $self->{' Tpending'}{'Color'} = $string;
3229             } else {
3230 0         0 $self->add($string);
3231             }
3232             } else {
3233 16 50       51 if ($self->{' doPending'}) {
3234 0         0 $self->{' Gpending'}{'Color'} = $string;
3235             } else {
3236 16         67 $self->add($string);
3237             }
3238             }
3239              
3240 16         52 return $self;
3241             } else {
3242              
3243 0         0 return @{$self->{' strokecolor'}};
  0         0  
3244             }
3245             }
3246              
3247             =head2 External Objects
3248              
3249             =head3 image
3250              
3251             $content->image($image_object, $x,$y, $width,$height)
3252              
3253             $content->image($image_object, $x,$y, $scale)
3254              
3255             $content->image($image_object, $x,$y)
3256              
3257             $content->image($image_object)
3258              
3259             # Example
3260             my $image_object = $pdf->image_jpeg($my_image_file);
3261             $content->image($image_object, 100, 200);
3262              
3263             =over
3264              
3265             Places an image on the page in the specified location (specifies the lower
3266             left corner of the image). The default location is C<[0,0]>.
3267              
3268             If coordinate transformations have been made (see I<Coordinate
3269             Transformations> above), the position and scale will be relative to the
3270             updated coordinates. Otherwise, C<[0,0]> will represent the bottom left
3271             corner of the page, and C<$width> and C<$height> will be measured at
3272             72dpi.
3273              
3274             For example, if you have a 600x600 image that you would like to be
3275             shown at 600dpi (i.e., one inch square), set the width and height to 72.
3276             (72 Big Points is one inch)
3277              
3278             If passed the output of C<image_svg()>, C<image()> will simply pass it on to
3279             the C<object()> method, with adjusted parameters. Note that this usage
3280             requires that the C<width> and C<height> are replaced by C<scale_x> and
3281             C<scale_y> values (optionally).
3282              
3283             =back
3284              
3285             =cut
3286              
3287             # deprecated in PDF::API2 -- suggests use of object() instead
3288             sub image {
3289 8     8 1 78 my ($self, $img, $x,$y, $w,$h) = @_;
3290              
3291 8 50       71 if (!defined $y) { $y = 0; }
  0         0  
3292 8 50       28 if (!defined $x) { $x = 0; }
  0         0  
3293              
3294             # is this a processed SVG (array of hashes)? throw over the wall to object
3295 8 50       31 if (ref($img) eq 'ARRAY') {
3296             # note that w and h are NOT the sizes, but are the SCALING factors
3297             # (default: 1). discussed in image_svg() call.
3298 0         0 $self->object($img, $x,$y, $w,$h);
3299 0         0 return $self;
3300             }
3301              
3302 8 50       26 if (defined $img->{'Metadata'}) {
3303 0         0 $self->_metaStart('PPAM:PlacedImage', $img->{'Metadata'});
3304             }
3305 8         42 $self->save();
3306 8 50       41 if (!defined $w) {
    50          
3307 0         0 $h = $img->height();
3308 0         0 $w = $img->width();
3309             } elsif (!defined $h) {
3310 0         0 $h = $img->height()*$w;
3311 0         0 $w = $img->width()*$w;
3312             }
3313 8         57 $self->matrix($w,0,0,$h, $x,$y);
3314 8         46 $self->add("/".$img->name(), 'Do');
3315 8         40 $self->restore();
3316 8         22 $self->{' x'} = $x;
3317 8         20 $self->{' y'} = $y;
3318 8         49 $self->resource('XObject', $img->name(), $img);
3319 8 50       34 if (defined $img->{'Metadata'}) {
3320 0         0 $self->_metaEnd();
3321             }
3322              
3323 8         23 return $self;
3324             }
3325              
3326             =head3 formimage
3327              
3328             $content->formimage($form_object, $x,$y, $scaleX, $scaleY)
3329              
3330             $content->formimage($form_object, $x,$y, $scale)
3331              
3332             $content->formimage($form_object, $x,$y)
3333              
3334             $content->formimage($form_object)
3335              
3336             =over
3337              
3338             Places an XObject on the page in the specified location (giving the lower
3339             left corner of the image) and scale (applied to the image's native height
3340             and width). If no scale is given, use 1 for both X and Y. If one scale is
3341             given, use for both X and Y. If two scales given, they are for (separately)
3342             X and Y. In general, you should not greatly distort an image by using greatly
3343             different scaling factors in X and Y, although it is now possible for when
3344             that effect is desirable. The C<$x,$y> default is C<[0,0]>.
3345              
3346             B<Note> that while this method is named form I<image>, it is also used for the
3347             pseudoimages created by the barcode routines. Images are naturally dimensionless
3348             (1 point square) and need at some point to be scaled up to the desired point
3349             size. Barcodes are naturally sized in points, and should be scaled at
3350             approximately I<1>. Therefore, it would greatly overscale barcodes to multiply
3351             by image width and height I<within> C<formimage>, and require scaling of
3352             1/width and 1/height in the call. So, we leave scaling alone within
3353             C<formimage> and have the user manually scale I<images> by the image width and
3354             height (in pixels) in the call to C<formimage>.
3355              
3356             =back
3357              
3358             =cut
3359              
3360             sub formimage {
3361 2     2 1 12 my ($self, $img, $x,$y, $sx,$sy) = @_;
3362              
3363 2 50       8 if (!defined $y) { $y = 0; }
  0         0  
3364 2 50       5 if (!defined $x) { $x = 0; }
  0         0  
3365              
3366             # if one scale given, use for both
3367             # if no scale given, use 1 for both
3368 2 50       6 if (!defined $sx) { $sx = 1; }
  0         0  
3369 2 50       20 if (!defined $sy) { $sy = $sx; }
  2         4  
3370              
3371             ## convert to desired height and width in pixels
3372             #$sx *= $img->width();
3373             #$sy *= $img->height();
3374              
3375 2         12 $self->save();
3376              
3377 2         11 $self->matrix($sx,0,0,$sy, $x,$y);
3378 2         9 $self->add('/' . $img->name(), 'Do');
3379 2         11 $self->restore();
3380 2         9 $self->resource('XObject', $img->name(), $img);
3381              
3382 2         8 return $self;
3383             }
3384              
3385             =head3 object
3386              
3387             $content->object($object, $x,$y, $scale_x,$scale_y)
3388              
3389             $content->object($object, $x,$y, $scale)
3390              
3391             $content->object($object, $x,$y)
3392              
3393             $content->object($object)
3394              
3395             =over
3396              
3397             Places an image or other external object (a.k.a. XObject) on the page in the
3398             specified location (giving the upper left corner of the object). Note that this
3399             positioning is I<different> from C<image()> and C<formimage()>, which give the
3400             I<lower left> corner!
3401              
3402             Up to four optional arguments may be given, with their defaults as described
3403             below.
3404              
3405             C<$x> and C<$y> are the I<upper left> corner of the object. If they are
3406             omitted, the object will be placed with its I<lower left> corner at C<[0, 0]>.
3407             B<Note> that if the object's bounding box has the fourth value (maximum
3408             ascender) greater than 0, you may need to subtract that value from C<y> to get
3409             the desired vertical position! A typical application will have a bounding box
3410             of C<[0, -height, width, 0]>, and no correction is needed. If the bounding box
3411             is C<[0, -max_descender, width, max_ascender]>, you may need to add the
3412             correction.
3413              
3414             For images, C<$scale_x> and C<$scale_y> represent the width and height of the
3415             image on the page, in points. If C<$scale_x> is omitted, it will default to 72
3416             pixels per inch. If C<$scale_y> is omitted, the image will be scaled
3417             proportionally, based on the image dimensions.
3418              
3419             For other external objects, including B<SVG images>, the scale is a
3420             multiplier, where 1 (the default) represents 100% (i.e., no change).
3421              
3422             If coordinate transformations have been made (see Coordinate Transformations
3423             above), the position and scale will be relative to the updated coordinates.
3424              
3425             If no coordinate transformations are needed, this method can be called directly
3426             from the L<PDF::Builder::Page> object instead.
3427              
3428             If an SVG XObject array (output from C<image_svg()>) is passed in, only the
3429             first [0th] element will be displayed. Any others will be ignored.
3430              
3431             =back
3432              
3433             =cut
3434              
3435             # Behavior based on argument count. xo, UL x,y, scale_x/w,scale_y/h
3436             # 1: Place at 0, 0, 100% (lower left)
3437             # 2: Place at x, 0, 100%
3438             # 3: Place at X, Y, 100%
3439             # 4: Place at X, Y, scaled
3440             # 5: Place at X, Y, scale_w, scale_h
3441             # TBD: size=>'points' or 'scale' to override Image usage. can do by finding
3442             # an element 'size' (string) and inserting undef's before it to fill
3443             # out @_ to 7+ in length.
3444              
3445             sub object {
3446 0     0 1 0 my ($self, $object, $x, $y, $scale_x, $scale_y) = @_;
3447 0   0     0 $x //= 0;
3448 0   0     0 $y //= 0;
3449 0   0     0 $scale_x //= 1;
3450 0   0     0 $scale_y //= $scale_x;
3451              
3452 0         0 my $name;
3453 0 0       0 if (UNIVERSAL::isa($object,'PDF::Builder::Resource::XObject::Image')) {
    0          
3454 0         0 $scale_x = $object->width();
3455 0         0 $scale_y = $object->height() * $scale_x / $object->width();
3456 0         0 $name = $object->name();
3457              
3458             } elsif (ref($object) eq 'ARRAY') {
3459             # output from image_svg()
3460 0 0       0 if (defined $object->[0]) {
3461              
3462             # simply ignore anything after the first element (first <svg>)
3463 0         0 my $xo = $object->[0]->{'xo'}; # hash of content
3464 0         0 my $width = $object->[0]->{'width'}; # viewBox width
3465 0         0 my $height = $object->[0]->{'height'}; # viewBox height
3466 0         0 my $vwidth = $object->[0]->{'vwidth'}; # desired (design) width
3467 0         0 my $vheight = $object->[0]->{'vheight'}; # desired (design) height
3468 0         0 my @vb = @{$object->[0]->{'vbox'}}; # viewBox
  0         0  
3469 0         0 my @bb = $xo->bbox(); # bounding box
3470              
3471             # scale factors to get viewBox dimensions to design dimensions
3472 0         0 my $flag = 1; # h and v scale will be defined
3473 0 0 0     0 if (!defined $width || !defined $vwidth ||
      0        
      0        
3474             !defined $height || !defined $vheight) {
3475 0         0 $flag = 0;
3476             }
3477              
3478             # bbox: y=0 is baseline, [1] is max descender, [3] is max ascender
3479             # [0] min x (usually 0), [2] max x (usually width).
3480             # if no "baseline", [3] is usually 0 (and [1] is -height)
3481 0         0 my $h = $bb[3] - $bb[1];
3482 0         0 my ($hscale, $vscale);
3483 0 0       0 if ($flag) {
3484 0         0 $hscale = $vwidth / $width;
3485 0         0 $vscale = $vheight / $height;
3486             } else {
3487 0         0 $hscale = $vscale = 1;
3488             }
3489 0         0 $scale_x *= $hscale;
3490 0         0 $scale_y *= $vscale;
3491              
3492             # if x,y = 0,0, assume want that to be the LOWER left corner,
3493             # and rejigger y to be UPPER left
3494 0 0 0     0 if ($x == 0 && $y == 0) {
3495 0         0 $y = $h;
3496             }
3497              
3498             # store away in $object where the image bounds are UL to LR,
3499             # baseline y. only for SVG images, used by higher level apps.
3500 0 0       0 if ($bb[3] > 0) {
3501             # baseline for equation is above bottom of viewbox
3502 0         0 $object->[0]->{'imageVB'} = [
3503             $x, $y,
3504             $x+($bb[2]-$bb[0])*$scale_x, $y-$h*$scale_y,
3505             $y-($h+$bb[1])*$scale_y
3506             ];
3507             } else {
3508             # no separate baseline (give as LRy)
3509 0         0 $object->[0]->{'imageVB'} = [
3510             $x, $y,
3511             $x+($bb[2]-$bb[0])*$scale_x, $y-$h*$scale_y,
3512             $y-$h*$scale_y
3513             ];
3514             }
3515              
3516             # make up a name
3517 0         0 $name = 'Sv' . pdfkey();
3518              
3519 0         0 $self->save();
3520             # baseline for equation is above bottom of viewbox
3521             # also adjust y position, otherwise MathJax eqn
3522             # itself is too high on page
3523 0         0 $self->matrix($scale_x, 0, 0, $scale_y, $x, $y-$bb[3]*$scale_y);
3524 0         0 $self->add('/' . $name, 'Do');
3525 0         0 $self->restore();
3526              
3527 0         0 $self->resource('XObject', $name, $xo);
3528 0         0 return $self;
3529              
3530             } else {
3531             # don't have at least one <svg>
3532 0         0 carp "attempt to display SVG object with no content.";
3533 0         0 return $self;
3534             }
3535             } else {
3536             # scale_x/y already set
3537 0         0 $name = $object->name();
3538             }
3539              
3540 0         0 $self->save();
3541 0         0 $self->matrix($scale_x, 0, 0, $scale_y, $x, $y);
3542 0         0 $self->add('/' . $name, 'Do');
3543 0         0 $self->restore();
3544              
3545 0         0 $self->resource('XObject', $name, $object);
3546              
3547 0         0 return $self;
3548             }
3549              
3550             =head2 Text
3551              
3552             =head3 Text State Parameters
3553              
3554             All of the following parameters that take a size are applied before
3555             any scaling takes place, so you don't need to adjust values to
3556             counteract scaling.
3557              
3558             =head4 charspace, character_spacing, char_space
3559              
3560             $spacing = $content->charspace($spacing)
3561              
3562             =over
3563              
3564             Sets additional B<horizontal> spacing between B<characters> in a line. Vertical
3565             writing systems are not supported. This is in I<points> and is initially zero.
3566             It may be positive to give an I<expanded> effect to words, or
3567             it may be negative to give a I<condensed> effect to words.
3568             If C<$spacing> is given, the current setting is replaced by that value and
3569             C<$self> is B<returned> (to permit chaining).
3570             If C<$spacing> is not given, the current setting is B<returned>.
3571              
3572             One use for character spacing is to adjust I<tracking> in a line of text.
3573             It is common to adjust inter-word spacing (e.g., TeX "glue" length) to justify
3574             a line (see C<wordspace>), but in cases where the result is words too close
3575             together (or too far apart), you may want to adjust tracking in order to force
3576             spaces back to a more "reasonable" standard size. For example, if you have a
3577             fairly "loose" line, with wide spaces between words, you could add a little
3578             character spacing between the letters of words, and shrink the spaces down to a
3579             more reasonable size. Don't overdo it, and make the words themselves difficult
3580             to read! You also would want to take care to "drive" the resulting spaces
3581             towards a consistent width throughout a document (or at least, a paragraph).
3582              
3583             You may also choose to use character spacing for special effects, such as a
3584             high-level heading expanded with extra space. This is a decorative effect, and
3585             should be used with restraint.
3586              
3587             Note that interword spaces (x20) I<also> receive additional character space,
3588             in addition to any additional word space (C<wordspace>) defined!
3589              
3590             B<CAUTION:> be careful about using C<charspace> if you are using a connected
3591             ("script") font. This might include Arabic, Devanagari, Latin cursive
3592             handwriting, and so on. You don't want to leave gaps between characters, or
3593             cause overlaps. For such fonts and typefaces, you I<may> need to explicitly set
3594             the C<charspace> spacing to 0, if you have set it to non-zero elsewhere.
3595             PDF::Builder may not be able to determine that a given font is a connected
3596             script font, and automatically suppress non-zero character spacing.
3597              
3598             B<Alternate names:> C<character_spacing> and C<char_space>
3599              
3600             I<character_spacing> is provided for compatibility with PDF::API2, while
3601             I<char_space> is provided to be consistent with many other method name
3602             changes in PDF::API2.
3603              
3604             =back
3605              
3606             =cut
3607              
3608             sub _charspace {
3609 14     14   34 my ($space) = @_;
3610              
3611 14         52 return float($space, 6) . ' Tc';
3612             }
3613              
3614 1     1 1 9 sub character_spacing { return charspace(@_); } ## no critic
3615              
3616 1     1 1 10 sub char_space { return charspace(@_); } ## no critic
3617              
3618             sub charspace {
3619 19     19 1 1030 my ($self, $space) = @_;
3620              
3621 19 100       58 if (defined $space) {
3622 14         34 $self->{' charspace'} = $space;
3623 14         53 $self->add(_charspace($space));
3624              
3625 14         41 return $self;
3626             } else {
3627 5         15 return $self->{' charspace'};
3628             }
3629             }
3630              
3631             =head4 wordspace, word_spacing, word_space
3632              
3633             $spacing = $content->wordspace($spacing)
3634              
3635             =over
3636              
3637             Sets additional B<horizontal> spacing between B<words> in a line. Vertical
3638             writing systems are not supported. This is in I<points> and is initially zero
3639             (i.e., just the width of the space, without anything extra). It may be negative
3640             to close up sentences a bit.
3641             If C<$spacing> is given, the current setting is replaced by that value and
3642             C<$self> is B<returned> (to permit chaining).
3643             If C<$spacing> is not given, the current setting is B<returned>.
3644              
3645             See the note in C<charspace> in regards to I<tracking> adjustment, and its
3646             effect on C<wordspace>. The two calls may often be used together for optimal
3647             results (although resulting in a somewhat increased PDF file size).
3648              
3649             Note that it is a limitation of the PDF specification (as of version 1.7,
3650             section 9.3.3) that only spacing with an ASCII space (x20) is adjusted. Neither
3651             required blanks (xA0) nor any multiple-byte spaces (including thin and wide
3652             spaces) are currently adjusted. B<However,> multiple I<spaces> between words
3653             I<each> are expanded. E.g., if you have a double x20 space between words, it
3654             will receive I<twice> the expansion of a single space! Furthermore, character
3655             spacing (Tc) is also added to each space, in I<addition> to word spacing (Tw).
3656              
3657             B<alternate names:> C<word_spacing> and C<word_space>
3658              
3659             I<word_spacing> is provided for compatibility with PDF::API2, while
3660             I<word_space> is provided to be consistent with many other method name
3661             changes in PDF::API2.
3662              
3663             =back
3664              
3665             =cut
3666              
3667             sub _wordspace {
3668 17     17   36 my ($space) = @_;
3669              
3670 17         62 return float($space, 6) . ' Tw';
3671             }
3672              
3673 1     1 1 24 sub word_spacing { return wordspace(@_); } ## no critic
3674              
3675 1     1 1 10 sub word_space { return wordspace(@_); } ## no critic
3676              
3677             sub wordspace {
3678 22     22 1 1438 my ($self, $space) = @_;
3679              
3680 22 100       67 if (defined $space) {
3681 17         46 $self->{' wordspace'} = $space;
3682 17         53 $self->add(_wordspace($space));
3683              
3684 17         45 return $self;
3685             } else {
3686 5         21 return $self->{' wordspace'};
3687             }
3688             }
3689              
3690             =head4 hscale
3691              
3692             $scale = $content->hscale($scale)
3693              
3694             =over
3695              
3696             Sets the percentage of horizontal text scaling (relative sizing, I<not>
3697             spacing). This is initially 100 (percent, i.e., no scaling). A scale of greater
3698             than 100 will stretch the text, while less than 100 will compress it.
3699             If C<$scale> is given, the current setting is replaced by that value and
3700             C<$self> is B<returned> (to permit chaining).
3701             If C<$scale> is not given, the current setting is B<returned>.
3702              
3703             Note that scaling affects all of the character widths, interletter spacing, and
3704             interword spacing. It is inadvisable to stretch or compress text by a large
3705             amount, as it will quickly make the text unreadable. If your objective is to
3706             justify text, you will usually be better off using C<charspace> and C<wordspace>
3707             to expand (or slightly condense) a line to fill a desired width. Also see
3708             the C<text_justify()> calls for this purpose.
3709              
3710             =back
3711              
3712             =cut
3713              
3714             sub _hscale {
3715 9     9   22 my ($scale) = @_;
3716              
3717 9         37 return float($scale, 6) . ' Tz';
3718             }
3719              
3720             sub hscale {
3721 25     25 1 61 my ($self, $scale) = @_;
3722              
3723 25 100       90 if (defined $scale) {
3724 9         22 $self->{' hscale'} = $scale;
3725 9         25 $self->add(_hscale($scale));
3726              
3727 9         30 return $self;
3728             } else {
3729 16         80 return $self->{' hscale'};
3730             }
3731             }
3732              
3733             # Note: hscale was originally named incorrectly as hspace, renamed
3734             # note that the private class data ' hspace' is no longer supported
3735             # PDF::API2 still provides 'hspace' and '_hspace'
3736              
3737             # lead() and the associated lead variable have been replaced by leading()
3738              
3739             =head4 leading
3740              
3741             $leading = $content->leading($leading)
3742              
3743             $leading = $content->leading()
3744              
3745             =over
3746              
3747             Sets the text leading, which is the distance between baselines. This
3748             is initially B<zero> (i.e., the lines will be printed on top of each
3749             other). The unit of leading is points.
3750             If C<$leading> is given, the current setting is replaced by that value and
3751             C<$self> is B<returned> (to permit chaining).
3752             If C<$leading> is not given, the current setting is B<returned>.
3753              
3754             Note that C<leading> here is defined as used in electronic typesetting and
3755             the PDF specification, which is the full interline spacing (text baseline to
3756             text baseline distance, in points). In cold metal typesetting, I<leading> was
3757             usually the I<extra> spacing between lines beyond the font height itself,
3758             created by inserting lead (type alloy) shims.
3759              
3760             =back
3761              
3762             =cut
3763              
3764             sub _leading {
3765 10     10   31 my ($leading) = @_;
3766              
3767 10         58 return float($leading) . ' TL';
3768             }
3769              
3770             sub leading {
3771 48     48 1 159 my ($self, $leading) = @_;
3772              
3773 48 100       141 if (defined $leading) {
3774 10         33 $self->{' leading'} = $leading;
3775 10         44 $self->add(_leading($leading));
3776              
3777 10         25 return $self;
3778             } else {
3779 38         154 return $self->{' leading'};
3780             }
3781             }
3782              
3783             =head4 render
3784              
3785             $mode = $content->render($mode)
3786              
3787             =over
3788              
3789             Sets the text rendering mode.
3790              
3791             =over
3792              
3793             =item 0 = Fill text
3794              
3795             =item 1 = Stroke text (outline)
3796              
3797             =item 2 = Fill, then stroke text
3798              
3799             =item 3 = Neither fill nor stroke text (invisible)
3800              
3801             =item 4 = Fill text and add to path for clipping
3802              
3803             =item 5 = Stroke text and add to path for clipping
3804              
3805             =item 6 = Fill, then stroke text and add to path for clipping
3806              
3807             =item 7 = Add text to path for clipping
3808              
3809             =back
3810              
3811             If C<$mode> is given, the current setting is replaced by that value and
3812             C<$self> is B<returned> (to permit chaining).
3813             If C<$mode> is not given, the current setting is B<returned>.
3814              
3815             =back
3816              
3817             =cut
3818              
3819             sub _render {
3820 1     1   16 my ($mode) = @_;
3821              
3822 1         4 return intg($mode) . ' Tr';
3823             }
3824              
3825             sub render {
3826 1     1 1 6 my ($self, $mode) = @_;
3827              
3828 1 50       3 if (defined $mode) {
3829 1         6 $mode = max(0, min(7, int($mode))); # restrict to integer range 0..7
3830 1         2 $self->{' render'} = $mode;
3831 1         3 $self->add(_render($mode));
3832              
3833 1         1 return $self;
3834             } else {
3835 0         0 return $self->{' render'};
3836             }
3837             }
3838              
3839             =head4 rise
3840              
3841             $dist = $content->rise($dist)
3842              
3843             =over
3844              
3845             Adjusts the baseline up or down from its current location. This is
3846             initially zero. A C<$dist> greater than 0 moves the baseline B<up> the page
3847             (y increases).
3848              
3849             Use this for creating superscripts or subscripts (usually along with an
3850             adjustment to the font size).
3851             If C<$dist> is given, the current setting is replaced by that value and
3852             C<$self> is B<returned> (to permit chaining).
3853             If C<$dist> is not given, the current setting is B<returned>.
3854              
3855             =back
3856              
3857             =cut
3858              
3859             sub _rise {
3860 1     1   2 my ($dist) = @_;
3861              
3862 1         4 return float($dist) . ' Ts';
3863             }
3864              
3865             sub rise {
3866 1     1 1 7 my ($self, $dist) = @_;
3867              
3868 1 50       3 if (defined $dist) {
3869 1         2 $self->{' rise'} = $dist;
3870 1         3 $self->add(_rise($dist));
3871              
3872 1         2 return $self;
3873             } else {
3874 0         0 return $self->{' rise'};
3875             }
3876             }
3877              
3878             =head4 textstate
3879              
3880             %state = $content->textstate('charspace'=>$value, 'wordspace'=>$value, ...)
3881              
3882             =over
3883              
3884             This is a shortcut for setting multiple text state parameters at once.
3885             If any parameters are set, an I<empty> hash is B<returned>.
3886             This can also be used without arguments to retrieve the current text
3887             state settings (a hash of the state is B<returned>).
3888              
3889             B<Note:> This does not work with the C<save> and C<restore> commands.
3890              
3891             =back
3892              
3893             =cut
3894              
3895             sub textstate {
3896 0     0 1 0 my ($self) = shift;
3897              
3898 0         0 my %state;
3899 0 0       0 if (@_) {
3900 0         0 %state = @_;
3901 0         0 foreach my $k (qw( charspace hscale wordspace leading rise render )) {
3902 0 0       0 next unless $state{$k};
3903 0         0 $self->can($k)->($self, $state{$k});
3904             }
3905 0 0 0     0 if ($state{'font'} && $state{'fontsize'}) {
3906 0         0 $self->font($state{'font'}, $state{'fontsize'});
3907             }
3908 0 0       0 if ($state{'textmatrix'}) {
3909 0         0 $self->matrix(@{$state{'textmatrix'}});
  0         0  
3910 0         0 @{$self->{' translate'}} = @{$state{'translate'}};
  0         0  
  0         0  
3911 0         0 $self->{' rotate'} = $state{'rotate'};
3912 0         0 @{$self->{' scale'}} = @{$state{'scale'}};
  0         0  
  0         0  
3913 0         0 @{$self->{' skew'}} = @{$state{'skew'}};
  0         0  
  0         0  
3914             }
3915 0 0       0 if ($state{'fillcolor'}) {
3916 0         0 $self->fillcolor(@{$state{'fillcolor'}});
  0         0  
3917             }
3918 0 0       0 if ($state{'strokecolor'}) {
3919 0         0 $self->strokecolor(@{$state{'strokecolor'}});
  0         0  
3920             }
3921 0         0 %state = ();
3922             } else {
3923 0         0 foreach my $k (qw( font fontsize charspace hscale wordspace leading rise render )) {
3924 0         0 $state{$k}=$self->{" $k"};
3925             }
3926 0         0 $state{'matrix'} = [@{$self->{" matrix"}}];
  0         0  
3927 0         0 $state{'textmatrix'} = [@{$self->{" textmatrix"}}];
  0         0  
3928 0         0 $state{'textlinematrix'} = [@{$self->{" textlinematrix"}}];
  0         0  
3929 0         0 $state{'rotate'} = $self->{" rotate"};
3930 0         0 $state{'scale'} = [@{$self->{" scale"}}];
  0         0  
3931 0         0 $state{'skew'} = [@{$self->{" skew"}}];
  0         0  
3932 0         0 $state{'translate'} = [@{$self->{" translate"}}];
  0         0  
3933 0         0 $state{'fillcolor'} = [@{$self->{" fillcolor"}}];
  0         0  
3934 0         0 $state{'strokecolor'} = [@{$self->{" strokecolor"}}];
  0         0  
3935             }
3936              
3937 0         0 return %state;
3938             }
3939              
3940             =head4 font
3941              
3942             $content->font($font_object, $size) # Set
3943              
3944             ($font_object, $size) = $content->font() # Get
3945              
3946             =over
3947              
3948             Sets or gets the font and font size. C<$font> is an object created by calling
3949             L<PDF::Builder/"font"> to add the font to the document.
3950              
3951             # Example (12 point Helvetica)
3952             my $pdf = PDF::Builder->new();
3953              
3954             my $font = $pdf->font('Helvetica');
3955             $text->font($font, 24);
3956             $text->position(72, 720);
3957             $text->text('Hello, World!');
3958              
3959             $pdf->save('sample.pdf');
3960              
3961             Or, get the current font object and size setting:
3962              
3963             my ($font, $size) = $text->font();
3964              
3965             Results ($font and $size) are indeterminate if font() was not previously called
3966             using them.
3967              
3968             =back
3969              
3970             =cut
3971              
3972             sub _font {
3973 17     17   75 my ($font, $size) = @_;
3974              
3975 17 100       76 if ($font->isvirtual()) {
3976 1         4 return '/'.$font->fontlist()->[0]->name().' '.float($size).' Tf';
3977             } else {
3978 16         62 return '/'.$font->name().' '.float($size).' Tf';
3979             }
3980             }
3981              
3982             sub font {
3983 18     18 1 1493 my ($self, $font, $size) = @_;
3984              
3985 18 50       107 if (!defined $font) { # Get
3986 0         0 $font = $self->{' font'};
3987 0         0 $size = $self->{' fontsize'};
3988 0         0 return ($font, $size);
3989             }
3990              
3991             # otherwise Set
3992 18 100       74 unless ($size) {
3993 1         194 croak q{A font size is required};
3994             }
3995 17         122 $self->_fontset($font, $size);
3996             # buffer the Tf command
3997 17 50       73 if ($self->{' doPending'}) {
3998 0         0 $self->{' Tpending'}{'Tf'} = _font($font, $size);
3999             } else {
4000 17         94 $self->add(_font($font, $size));
4001             }
4002 17         48 $self->{' fontset'} = 1;
4003              
4004 17         57 return $self;
4005             }
4006              
4007             sub _fontset {
4008 17     17   47 my ($self, $font, $size) = @_;
4009              
4010 17         59 $self->{' font'} = $font;
4011 17         46 $self->{' fontsize'} = $size;
4012 17         49 $self->{' fontset'} = 0;
4013              
4014 17 100       92 if ($font->isvirtual()) {
4015 1         2 foreach my $f (@{$font->fontlist()}) {
  1         3  
4016 2         8 $self->resource('Font', $f->name(), $f);
4017             }
4018             } else {
4019 16         104 $self->resource('Font', $font->name(), $font);
4020             }
4021              
4022 17         41 return $self;
4023             }
4024              
4025             =head3 Positioning Text
4026              
4027             =head4 position
4028              
4029             $content = $content->position($x, $y) # Set (also returns object, for ease of chaining)
4030              
4031             ($x, $y) = $content->position() # Get
4032              
4033             =over
4034              
4035             If called I<with> arguments (Set), moves to the start of the current line of
4036             text, offset by C<$x> and C<$y> (right and up for positive values).
4037              
4038             If called I<without> arguments (Get), returns the current position of the
4039             cursor (before the effects of any coordinate transformation methods).
4040              
4041             Note that this is very similar in function to C<distance()>, added recently
4042             to PDF::API2 and added here for compatibility.
4043              
4044             =back
4045              
4046             =cut
4047              
4048             sub position {
4049 0     0 1 0 my ($self, $x, $y) = @_;
4050              
4051 0 0 0     0 if (defined $x and not defined $y) {
4052 0         0 croak 'position() requires either 0 or 2 arguments';
4053             }
4054              
4055 0 0       0 if (defined $x) { # Set
4056 0         0 $self->_Tpending();
4057             # if ($self->{' doPending'}) {
4058             # $self->{' Tpending'}{'Td'} = float($x).' '.float($y).' Td';
4059             # } else {
4060 0         0 $self->add(float($x), float($y), 'Td');
4061             # }
4062 0         0 $self->matrix_update($x, $y);
4063 0         0 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'} + $x;
4064 0         0 $self->{' textlinestart'} = $self->{' textlinematrix'}->[0];
4065 0         0 return $self;
4066             }
4067              
4068             # Get
4069 0         0 return @{$self->{' textlinematrix'}};
  0         0  
4070             }
4071              
4072             =head4 textpos, (see also) position
4073              
4074             ($tx,$ty) = $content->textpos()
4075              
4076             =over
4077              
4078             B<Returns> the current text position on the page (where next write will happen)
4079             as an array.
4080              
4081             B<Note:> This does not affect the PDF in any way. It only tells you where the
4082             the next write will occur.
4083              
4084             B<Alternate name:> C<position> (added for compatibility with PDF::API2)
4085              
4086             =back
4087              
4088             =cut
4089              
4090             sub _textpos {
4091 0     0   0 my ($self, @xy) = @_;
4092              
4093 0         0 my ($x,$y) = (0,0);
4094 0         0 while (scalar @xy > 0) {
4095 0         0 $x += shift @xy;
4096 0         0 $y += shift @xy;
4097             }
4098             my @m = _transform(
4099 0         0 'matrix' => $self->{" textmatrix"},
4100             'point' => [$x,$y]
4101             );
4102 0         0 return ($m[0],$m[1]);
4103             }
4104              
4105             sub _textpos2 {
4106 60     60   125 my ($self) = shift;
4107              
4108 60         80 return @{$self->{" textlinematrix"}};
  60         250  
4109             }
4110              
4111             sub textpos {
4112 0     0 1 0 my ($self) = shift;
4113              
4114 0         0 return $self->_textpos(@{$self->{" textlinematrix"}});
  0         0  
4115             }
4116              
4117             =head4 distance
4118              
4119             $content->distance($dx,$dy)
4120              
4121             =over
4122              
4123             This moves to the start of the previously-written line, plus an offset by the
4124             given amounts, which are both required. C<[0,0]> would overwrite the previous
4125             line, while C<[0,36]> would place the new line 36pt I<above> the old line
4126             (higher y). The C<$dx> moves to the right, if positive.
4127              
4128             C<distance> is analogous to graphic's C<move>, except that it is relative to
4129             the beginning of the previous text write, not to the coordinate origin.
4130             B<Note> that subsequent text writes will be relative to this new starting
4131             (left) point and Y position! E.g., if you give a non-zero C<$dx>, subsequent
4132             lines will be indented by that amount.
4133              
4134             =back
4135              
4136             =cut
4137              
4138             sub distance {
4139 2     2 1 16 my ($self, $dx,$dy) = @_;
4140              
4141 2         12 $self->_Tpending();
4142             # if ($self->{' doPending'}) {
4143             # $self->{' Tpending'}{'Td'} = float($dx).' '.float($dy).' Td';
4144             # } else {
4145 2         9 $self->add(float($dx), float($dy), 'Td');
4146             # }
4147 2         10 $self->matrix_update($dx,$dy);
4148 2         4 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'} + $dx;
4149 2         5 $self->{' textlinestart'} = $self->{' textlinematrix'}->[0];
4150              
4151 2         5 return $self;
4152             }
4153              
4154             =head4 cr
4155              
4156             $content->cr()
4157              
4158             $content->cr($vertical_offset)
4159              
4160             $content->cr(0)
4161              
4162             =over
4163              
4164             If passed without an argument, moves (down) to the start of the I<next> line
4165             (distance set by C<leading>). This is similar to C<nl()>.
4166              
4167             If passed I<with> an argument, the C<leading> distance is ignored and the next
4168             line starts that far I<up> the page (positive value) or I<down> the page
4169             (negative value) from the current line. "Y" increases upward, so a negative
4170             value would normally be used to get to the next line down.
4171              
4172             An argument of I<0> would
4173             simply return to the start of the present line, overprinting it with new text.
4174             That is, it acts as a simple carriage return, without a linefeed.
4175              
4176             Note that any setting for C<leading> is ignored. If you wish to account for
4177             the C<leading> setting, you may wish to use the C<crlf> method instead.
4178              
4179             =back
4180              
4181             =cut
4182              
4183             sub cr {
4184 7     7 1 32 my ($self, $offset) = @_;
4185              
4186 7         22 $self->_Tpending();
4187 7 100       42 if (defined $offset) {
4188             # if ($self->{' doPending'}) {
4189             # $self->{' Tpending'}{'Td'} = '0 '.float($offset).' Td';
4190             # } else {
4191 5         19 $self->add(0, float($offset), 'Td');
4192             # }
4193 5         11 $self->matrix_update(0, $offset);
4194             } else {
4195 2         20 $self->add('T*');
4196 2         17 $self->matrix_update(0, $self->leading() * -1);
4197             }
4198 7         14 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
4199              
4200 7         16 return $self;
4201             }
4202              
4203             =head4 nl
4204              
4205             $content->nl()
4206              
4207             $content->nl($indent)
4208              
4209             $content->nl(0)
4210              
4211             =over
4212              
4213             Moves to the start of the next line (see C<leading>). If C<$indent> is not given,
4214             or is 0, there is no indentation. Otherwise, indent by that amount (I<out>dent
4215             if a negative value). The unit of measure is hundredths of a "unit of text
4216             space", or roughly 88 per em.
4217              
4218             Note that any setting for C<leading> is ignored. If you wish to account for
4219             the C<leading> setting, you may wish to use the C<crlf> method instead.
4220              
4221             =back
4222              
4223             =cut
4224              
4225             sub nl {
4226 24     24 1 88 my ($self, $indent) = @_;
4227              
4228 24         79 $self->_Tpending();
4229              
4230             # can't use Td, because it permanently changes the line start by $indent
4231             # same problem using the distance() call
4232 24         75 $self->add('T*'); # go to start of next line
4233 24         84 $self->matrix_update(0, $self->leading() * -1);
4234 24         84 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
4235              
4236 24 100 100     82 if (defined($indent) && $indent != 0) {
4237             # move right or left by $indent
4238 1         4 $self->add('[' . (-10 * $indent) . '] TJ');
4239             }
4240              
4241 24         67 return $self;
4242             }
4243              
4244             =head4 crlf
4245              
4246             $content = $content->crlf()
4247              
4248             =over
4249              
4250             Moves to the start of the next line, based on the L</"leading"> setting. It
4251             returns its own object, for ease of chaining.
4252              
4253             If leading isn't set, a default distance of 120% of the font size will be used.
4254              
4255             Added for compatibility with PDF::API2 changes; may be used to replace both
4256             C<cr> and C<nl> methods.
4257              
4258             =back
4259              
4260             =cut
4261              
4262             sub crlf {
4263 0     0 1 0 my $self = shift();
4264 0         0 $self->_Tpending();
4265 0         0 my $leading = $self->leading();
4266 0 0 0     0 if ($leading or not $self->{' fontsize'}) {
4267 0         0 $self->add('T*');
4268             }
4269             else {
4270 0         0 $leading = $self->{' fontsize'} * 1.2;
4271             # if ($self->{' doPending'}) {
4272             # $self->{' Tpending'}{'Td'} = '0 '.float($leading * -1).' Td';
4273             # } else {
4274 0         0 $self->add(0, float($leading * -1), 'Td');
4275             # }
4276             }
4277              
4278 0         0 $self->matrix_update(0, $leading * -1);
4279 0         0 $self->{' textlinematrix'}->[0] = $self->{' textlinestart'};
4280 0         0 return $self;
4281             }
4282              
4283             =head4 advancewidth, text_width
4284              
4285             $width = $content->advancewidth($string, %opts)
4286              
4287             =over
4288              
4289             Returns the number of points that will be used (horizontally) by the input
4290             string. This assumes all on one line (no line breaking).
4291              
4292             Options %opts:
4293              
4294             =over
4295              
4296             =item 'font' => $f3_TimesRoman
4297              
4298             Change the font used, overriding $self->{' font'}. The font must have been
4299             previously created (i.e., is not the name). Example: use Times-Roman.
4300              
4301             =item 'fontsize' => 12
4302              
4303             Change the font size, overriding $self->{' fontsize'}. Example: 12 pt font.
4304              
4305             =item 'wordspace' => 0.8
4306              
4307             Change the additional word spacing, overriding $self->wordspace().
4308             Example: add 0.8 pt between words.
4309              
4310             =item 'charspace' => -2.1
4311              
4312             Change the additional character spacing, overriding $self->charspace().
4313             Example: subtract 2.1 pt between letters, to condense the text.
4314              
4315             =item 'hscale' => 125
4316              
4317             Change the horizontal scaling factor, overriding $self->hscale().
4318             Example: stretch text to 125% of its natural width.
4319              
4320             =back
4321              
4322             B<Returns> the B<width of the $string> (when set as a line of type), based
4323             on all currently set text-state
4324             attributes. These can optionally be overridden with %opts. I<Note that these
4325             values temporarily B<replace> the existing values, B<not> scaling them up or
4326             down.> For example, if the existing charspace is 2, and you give in options
4327             a value of 3, the value used is 3, not 5.
4328              
4329             B<Note:> This does not affect the PDF in any way. It only tells you how much
4330             horizontal space a text string will take up.
4331              
4332             B<Alternate name:> C<text_width>
4333              
4334             This is provided for compatibility with PDF::API2.
4335              
4336             =back
4337              
4338             =cut
4339              
4340 0     0 1 0 sub text_width { return advancewidth(@_); } ## no critic
4341              
4342             sub advancewidth {
4343 181     181 1 3703 my ($self, $text, %opts) = @_;
4344              
4345 181         306 my ($glyph_width, $num_space, $num_char, $word_spaces,
4346             $char_spaces, $advance);
4347              
4348 181 50 33     700 return 0 unless defined($text) and length($text);
4349             # fill %opts from current settings unless explicitly given
4350 181         371 foreach my $k (qw[ font fontsize wordspace charspace hscale]) {
4351 905 100       2780 $opts{$k} = $self->{" $k"} unless defined $opts{$k};
4352             }
4353             # any other options given are ignored
4354            
4355             # $opts{'font'} (not ' font'}) needs to be defined. fail if not.
4356             # other code should first fatal error in text() call, this is a fallback
4357 181 50       397 return 0 if !defined $opts{'font'};
4358              
4359             # leading, trailing, extra spaces are counted (not squeezed out)
4360             # width of text without adjusting char and word spacing
4361 181         609 $glyph_width = $opts{'font'}->width($text)*$opts{'fontsize'};
4362             # how many ASCII spaces x20. TBD: account for other size spaces, maybe tabs
4363 181         389 $num_space = $text =~ y/\x20/\x20/;
4364             # how many characters in all, including spaces
4365 181         282 $num_char = length($text);
4366             # how many points to add to width due to spaces. note that doubled
4367             # interword spaces count as two (or more) word spaces, not just one
4368 181         342 $word_spaces = $opts{'wordspace'}*$num_space;
4369             # intercharacter additional spacing (note that interword spaces count
4370             # as normal characters here. TBD: check PDF spec if that is correct).
4371             # want extra space after EACH character, including the one on the end, not
4372             # just between each character WITHIN the text string.
4373 181         301 $char_spaces = $opts{'charspace'}*$num_char;
4374 181         520 $advance = ($glyph_width+$word_spaces+$char_spaces)*$opts{'hscale'}/100;
4375              
4376 181         647 return $advance;
4377             }
4378              
4379             =head3 Rendering Text
4380              
4381             =head4 Single Lines
4382              
4383             =head4 text
4384              
4385             $width = $content->text($text, %opts)
4386              
4387             =over
4388              
4389             Adds text to the page (left justified by default).
4390             The width used (in points) is B<returned>.
4391              
4392             Options:
4393              
4394             =over
4395              
4396             =item 'align' => position
4397              
4398             Align the text, assuming left-to-right writing direction (RTL/bidirectional is
4399             not currently supported).
4400              
4401             =over
4402              
4403             =item 'l' or 'left' (case insensitive).
4404              
4405             B<default.> Text I<begins> at the current text position.
4406              
4407             =item 'c' or 'center' (case insensitive).
4408              
4409             Text is I<centered> at the current text position.
4410              
4411             =item 'r' or 'right' (case insensitive).
4412              
4413             Text I<ends> (is right justified to) at the current text position.
4414              
4415             =back
4416              
4417             In all cases, the ending text position is at the (right) end of the text.
4418             If mixing various alignments, you should explicitly place the current text
4419             position so as to not overwrite earlier text.
4420              
4421             =item 'indent' => $distance
4422              
4423             Indents the text by the number of points (A value less than 0 gives an
4424             I<outdent>).
4425             The indentation amount moves the text left (negative indentation) or right
4426             (positive indentation), regardless of alignment. This allows desired alignment
4427             effects (for centered and right) that aren't exactly aligned on the current
4428             position. For example, consider a column of decimal numbers centered on a
4429             desired I<x> position, but aligned on their decimal points. The C<indent>
4430             would be on a per-line basis, adjusted by the length of the number and the
4431             decimal position.
4432              
4433             =item 'underline' => 'none'
4434              
4435             =item 'underline' => 'auto'
4436              
4437             =item 'underline' => $distance
4438              
4439             =item 'underline' => [$distance, $thickness, ...]
4440              
4441             Underlines the text. C<$distance> is the number of units beneath the
4442             baseline, and C<$thickness> is the width of the line.
4443             Multiple underlines can be made by passing several distances and
4444             thicknesses.
4445             A value of 'none' means no underlining (is the default).
4446              
4447             Example:
4448            
4449             # 3 underlines:
4450             # distance 4, thickness 1, color red
4451             # distance 7, thickness 1.5, color yellow
4452             # distance 11, thickness 2, color (strokecolor default)
4453             'underline' => [4,[1,'red'],7,[1.5,'yellow'],11,2],
4454              
4455             =item 'strikethru' => 'none'
4456              
4457             =item 'strikethru' => 'auto'
4458              
4459             =item 'strikethru' => $distance
4460              
4461             =item 'strikethru' => [$distance, $thickness, ...]
4462              
4463             Strikes through the text (like HTML I<s> tag). A value of 'auto' places the
4464             line about 30% of the font size above the baseline, or a specified C<$distance>
4465             (above the baseline) and C<$thickness> (in points).
4466             Multiple strikethroughs can be made by passing several distances and
4467             thicknesses.
4468             A value of 'none' means no strikethrough. It is the default.
4469              
4470             Example:
4471            
4472             # 2 strikethroughs:
4473             # distance 4, thickness 1, color red
4474             # distance 7, thickness 1.5, color yellow
4475             'strikethru' => [4,[1,'red'],7,[1.5,'yellow']],
4476              
4477             =item 'strokecolor' => color_spec
4478              
4479             Defines the underline or strikethru line color, if different from the text
4480             color.
4481              
4482             =back
4483              
4484             =back
4485              
4486             =cut
4487              
4488             # TBD: consider 'overline' similar to underline
4489             # bidirectional/RTL identation, alignment meanings?
4490              
4491             sub _text_underline {
4492 0     0   0 my ($self, $xy1,$xy2, $underline, $color) = @_;
4493              
4494 0   0     0 $color ||= 'black';
4495 0         0 my @underline = ();
4496 0 0       0 if (ref($underline) eq 'ARRAY') {
4497 0         0 @underline = @{$underline};
  0         0  
4498             } else {
4499 0 0       0 if ($underline eq 'none') { return; }
  0         0  
4500 0         0 @underline = ($underline, 1);
4501             }
4502 0 0       0 push @underline,1 if @underline%2;
4503              
4504 0         0 my $upem = $self->{' font'}->upem();
4505 0   0     0 my $underlineposition = (-$self->{' font'}->underlineposition()*$self->{' fontsize'}/$upem ||1);
4506 0   0     0 my $underlinethickness = ($self->{' font'}->underlinethickness()*$self->{' fontsize'}/$upem ||1);
4507 0         0 my $pos = 1;
4508              
4509 0         0 while (@underline) {
4510 0         0 $self->add_post(_save());
4511              
4512 0         0 my $distance = shift @underline;
4513 0         0 my $thickness = shift @underline;
4514 0         0 my $scolor = $color;
4515 0 0       0 if (ref($thickness)) {
4516 0         0 ($thickness, $scolor) = @{$thickness};
  0         0  
4517             }
4518              
4519 0 0       0 if ($distance eq 'auto') {
4520 0         0 $distance = $pos*$underlineposition;
4521             }
4522 0 0       0 if ($thickness eq 'auto') {
4523 0         0 $thickness = $underlinethickness;
4524             }
4525              
4526 0         0 my ($x1,$y1, $x2,$y2);
4527 0         0 my $h = $distance+($thickness/2);
4528 0 0       0 if (scalar(@{$xy1}) > 2) {
  0         0  
4529             # actual baseline start and end points, not old reduced method
4530 0         0 my @xyz = @{$xy1};
  0         0  
4531 0         0 $x1 = $xyz[1]; $y1 = $xyz[2] - $h;
  0         0  
4532 0         0 @xyz = @{$xy2};
  0         0  
4533 0         0 $x2 = $xyz[1]; $y2 = $xyz[2] - $h;
  0         0  
4534             } else {
4535 0         0 ($x1,$y1) = $self->_textpos(@{$xy1}, 0, -$h);
  0         0  
4536 0         0 ($x2,$y2) = $self->_textpos(@{$xy2}, 0, -$h);
  0         0  
4537             }
4538              
4539 0         0 $self->add_post($self->_strokecolor($scolor));
4540 0         0 $self->add_post(_linewidth($thickness));
4541 0         0 $self->add_post(_move($x1,$y1));
4542 0         0 $self->add_post(_line($x2,$y2));
4543 0         0 $self->add_post(_stroke);
4544              
4545 0         0 $self->add_post(_restore());
4546 0         0 $pos++;
4547             }
4548 0         0 return;
4549             }
4550              
4551             sub _text_strikethru {
4552 0     0   0 my ($self, $xy1,$xy2, $strikethru, $color) = @_;
4553              
4554 0   0     0 $color ||= 'black';
4555 0         0 my @strikethru = ();
4556 0 0       0 if (ref($strikethru) eq 'ARRAY') {
4557 0         0 @strikethru = @{$strikethru};
  0         0  
4558             } else {
4559 0 0       0 if ($strikethru eq 'none') { return; }
  0         0  
4560 0         0 @strikethru = ($strikethru, 1);
4561             }
4562 0 0       0 push @strikethru,1 if @strikethru%2;
4563              
4564 0         0 my $upem = $self->{' font'}->upem();
4565             # fonts define an underline position and thickness, but not strikethrough
4566             # ideally would be just under 1ex
4567             #my $strikethruposition = (-$self->{' font'}->strikethruposition()*$self->{' fontsize'}/$upem ||1);
4568 0   0     0 my $strikethruposition = 5*(($self->{' fontsize'}||20)/20); # >0 is up
4569             # let's borrow the underline thickness for strikethrough purposes
4570 0   0     0 my $strikethruthickness = ($self->{' font'}->underlinethickness()*$self->{' fontsize'}/$upem ||1);
4571 0         0 my $pos = 1;
4572              
4573 0         0 while (@strikethru) {
4574 0         0 $self->add_post(_save());
4575              
4576 0         0 my $distance = shift @strikethru;
4577 0         0 my $thickness = shift @strikethru;
4578 0         0 my $scolor = $color;
4579 0 0       0 if (ref($thickness)) {
4580 0         0 ($thickness, $scolor) = @{$thickness};
  0         0  
4581             }
4582              
4583 0 0       0 if ($distance eq 'auto') {
4584 0         0 $distance = $pos*$strikethruposition;
4585             }
4586 0 0       0 if ($thickness eq 'auto') {
4587 0         0 $thickness = $strikethruthickness;
4588             }
4589              
4590 0         0 my ($x1,$y1, $x2,$y2);
4591 0         0 my $h = $distance+($thickness/2);
4592 0 0       0 if (scalar(@{$xy1}) > 2) {
  0         0  
4593             # actual baseline start and end points, not old reduced method
4594 0         0 my @xyz = @{$xy1};
  0         0  
4595 0         0 $x1 = $xyz[1]; $y1 = $xyz[2] + $h;
  0         0  
4596 0         0 @xyz = @{$xy2};
  0         0  
4597 0         0 $x2 = $xyz[1]; $y2 = $xyz[2] + $h;
  0         0  
4598             } else {
4599 0         0 ($x1,$y1) = $self->_textpos(@{$xy1}, 0, $h);
  0         0  
4600 0         0 ($x2,$y2) = $self->_textpos(@{$xy2}, 0, $h);
  0         0  
4601             }
4602              
4603 0         0 $self->add_post($self->_strokecolor($scolor));
4604 0         0 $self->add_post(_linewidth($thickness));
4605 0         0 $self->add_post(_move($x1,$y1));
4606 0         0 $self->add_post(_line($x2,$y2));
4607 0         0 $self->add_post(_stroke);
4608              
4609 0         0 $self->add_post(_restore());
4610 0         0 $pos++;
4611             }
4612 0         0 return;
4613             }
4614              
4615             sub text {
4616 31     31 1 174 my ($self, $text, %opts) = @_;
4617             # copy dashed option names to preferred undashed names
4618 31 50 33     132 if (defined $opts{'-align'} && !defined $opts{'align'}) { $opts{'align'} = delete($opts{'-align'}); }
  0         0  
4619 31 100 66     149 if (defined $opts{'-indent'} && !defined $opts{'indent'}) { $opts{'indent'} = delete($opts{'-indent'}); }
  1         3  
4620 31 50 33     106 if (defined $opts{'-underline'} && !defined $opts{'underline'}) { $opts{'underline'} = delete($opts{'-underline'}); }
  0         0  
4621 31 50 33     119 if (defined $opts{'-strokecolor'} && !defined $opts{'strokecolor'}) { $opts{'strokecolor'} = delete($opts{'-strokecolor'}); }
  0         0  
4622 31 50 33     151 if (defined $opts{'-strikethru'} && !defined $opts{'strikethru'}) { $opts{'strikethru'} = delete($opts{'-strikethru'}); }
  0         0  
4623              
4624 31         64 my $align = 'l'; # default
4625 31 100       94 if (defined $opts{'align'}) {
4626 26         73 $align = lc($opts{'align'});
4627 26 100 66     222 if ($align eq 'l' || $align eq 'left') {
    100 66        
    50 33        
    0 0        
4628 17         42 $align = 'l';
4629             } elsif ($align eq 'c' || $align eq 'center') {
4630 6         17 $align = 'c';
4631             } elsif ($align eq 'r' || $align eq 'right') {
4632 3         6 $align = 'r';
4633             } elsif ($align eq 'j' || $align eq 'justified') {
4634 0         0 $align = 'j';
4635             } else {
4636 0         0 $align = 'l'; # silent error on bad alignment
4637             }
4638             }
4639              
4640 31         189 $self->_Tpending(); # flush any accumulated PDF text settings
4641              
4642 31 100       100 if ($self->{' fontset'} == 0) {
4643 1 50 33     8 unless (defined($self->{' font'}) and $self->{' fontsize'}) {
4644 1         288 croak q{Can't add text without first setting a font and font size};
4645             }
4646 0         0 $self->font($self->{' font'}, $self->{' fontsize'});
4647 0         0 $self->{' fontset'} = 1;
4648             }
4649              
4650 30         120 my $wd = $self->advancewidth($text);
4651              
4652 30         49 my $indent = 0; # default
4653 30 100       112 if (defined $opts{'indent'}) {
4654 3         11 $indent = $opts{'indent'};
4655             # indent may be negative to "outdent" a line
4656             # TBD: later may define indentation for RTL/bidirectional
4657             }
4658              
4659             # now have alignment, indentation amount, text width
4660             # adjust indentation by text width and alignment. negative to move text left
4661 30 100 66     161 if ($align eq 'l' || $align eq 'j') {
    100          
4662             # no change
4663             } elsif ($align eq 'c') {
4664 6         16 $indent -= $wd/2;
4665             } else { # 'r'
4666 3         7 $indent -= $wd;
4667             }
4668              
4669             # indent is points to move text left (<0) or right (>0)
4670             # per input 'indent' AND alignment AND text width
4671 30 100       116 $self->matrix_update($indent, 0) if ($indent); # move current pos to start
4672              
4673 30         132 my $ulxy1 = [$self->_textpos2()]; # x,y start of under/thru line
4674              
4675 30 100       90 if ($indent) {
4676             # indent is positive >0 to move right (explicit 'indent' optional
4677             # amount plus left adjustment for centered or right alignment).
4678             # convert to milliems and scale
4679             $self->add(
4680             $self->{' font'}->text(
4681             $text,
4682             $self->{' fontsize'},
4683 12         100 -$indent*(1000/$self->{' fontsize'})*(100/$self->hscale()) ));
4684             } else {
4685             # indent ended up 0
4686             $self->add(
4687             $self->{' font'}->text(
4688             $text,
4689 18         102 $self->{' fontsize'} ));
4690             }
4691              
4692 30         142 $self->matrix_update($wd, 0); # move current position right to end of text
4693             # regardless of alignment used.
4694             # TBD need to check if will be left end for RTL/bidirectional
4695              
4696 30         89 my $ulxy2 = [$self->_textpos2()]; # x,y end of under/thru line
4697              
4698 30 50       97 if (defined $opts{'underline'}) {
4699 0         0 $self->_text_underline($ulxy1,$ulxy2, $opts{'underline'}, $opts{'strokecolor'});
4700             }
4701              
4702 30 50       113 if (defined $opts{'strikethru'}) {
4703 0         0 $self->_text_strikethru($ulxy1,$ulxy2, $opts{'strikethru'}, $opts{'strokecolor'});
4704             }
4705              
4706 30         144 return $wd;
4707             }
4708              
4709             sub _metaStart {
4710 0     0   0 my ($self, $tag, $obj) = @_;
4711              
4712 0         0 $self->add("/$tag");
4713 0 0       0 if (defined $obj) {
4714 0         0 my $dict = PDFDict();
4715 0         0 $dict->{'Metadata'} = $obj;
4716 0         0 $self->resource('Properties', $obj->name(), $dict);
4717 0         0 $self->add('/'.($obj->name()));
4718 0         0 $self->add('BDC');
4719             } else {
4720 0         0 $self->add('BMC');
4721             }
4722 0         0 return $self;
4723             }
4724              
4725             sub _metaEnd {
4726 0     0   0 my ($self) = shift;
4727              
4728 0         0 $self->add('EMC');
4729 0         0 return $self;
4730             }
4731              
4732             =head4 textHS
4733              
4734             $width = $content->textHS($HSarray, $settings, %opts)
4735              
4736             =over
4737              
4738             Takes an array of hashes produced by HarfBuzz::Shaper and outputs them to the
4739             PDF output file. HarfBuzz outputs glyph CIDs and positioning information.
4740             It may rearrange and swap characters (glyphs), and the result may bear no
4741             resemblance to the original Unicode point list. You should see
4742             examples/HarfBuzz.pl, which shows a number of examples with Latin and non-Latin
4743             text, as well as vertical writing.
4744             https://www.catskilltech.com/Examples has a sample available in case you want
4745             to see some examples of what HarfBuzz can do, and don't yet have
4746             HarfBuzz::Shaper installed.
4747              
4748             =over
4749              
4750             =item $HSarray
4751              
4752             This is the reference to array of hashes produced by HarfBuzz::Shaper, normally
4753             unchanged after being created (but I<can> be modified). See
4754             L<PDF::Builder::Docs/Using Shaper> for some things that can be done.
4755              
4756             =item $settings
4757              
4758             This a reference to a hash of various pieces of information that C<textHS()>
4759             needs in order to function. They include:
4760              
4761             =over
4762              
4763             =item 'script' => 'script_name'
4764              
4765             This is the standard 4 letter code (e.g., 'Latn') for the script (alphabet and
4766             writing system) you're using. Currently, only Latn (Western writing systems)
4767             do kerning, and 'Latn' is the default. HarfBuzz::Shaper will usually be able to
4768             figure out from the Unicode points used what the script is, and you might be
4769             able to use the C<set_script()> call to override its guess. However,
4770             PDF::Builder and HarfBuzz::Shaper do not talk to each other about the script
4771             being used.
4772              
4773             =item 'features' => array_of_features
4774              
4775             This item is B<required>, but may be empty, e.g.,
4776             C<$settings-E<gt>{'features'} = ();>.
4777             It can include switches using the standard HarfBuzz naming, and a + or -
4778             switch, such as '-liga' to turn B<off> ligatures. '-liga' and '-kern', to turn
4779             off ligatures and kerning, are the only features supported currently. B<Note>
4780             that this is separate from any switches for features that you send to
4781             HarfBuzz::Shaper (with C<$hb-E<gt>add_features()>, etc.) when you run it
4782             (before C<textHS()>).
4783              
4784             =item 'language' => 'language_code'
4785              
4786             This item is optional and currently does not appear to have any substantial
4787             effect with HarfBuzz::Shaper. It is the standard code for the
4788             language to be used, such as 'en' or 'en_US'. You might need to define this for
4789             HarfBuzz::Shaper, in case that system can't surmise the language rules to be
4790             used.
4791              
4792             =item 'dir' => 'flag'
4793              
4794             Tell C<textHS()> whether this text is to be written in a Left-To-Right manner
4795             (B<L>, the B<default>), Right-To-Left (B<R>), Top-To-Bottom (B<T>), or
4796             Bottom-To-Top (B<B>). From the script used (Unicode points), HarfBuzz::Shaper
4797             can usually figure out what direction to write text in. Also, HarfBuzz::Shaper
4798             does not share its information with PDF::Builder -- you need to separately
4799             specify the direction, unless you want to accept the default LTR direction. You
4800             I<can> use HarfBuzz::Shaper's C<get_direction()> call (in addition to
4801             C<get_language()> and C<get_script()>) to see what HarfBuzz thinks is the
4802             correct text direction. C<set_direction()> may be used to override Shaper's
4803             guess as to the direction.
4804              
4805             By the way, if the direction is RTL, HarfBuzz will reverse the text and return
4806             an array with the last character first (to be written LTR). Likewise, for BTT,
4807             HarfBuzz will reverse the text and return a string to be written from the top
4808             down. Languages which are normally written horizontally are usually set
4809             vertically with direction TTB. If setting text vertically, ligatures and
4810             kerning, as well as character connectivity for cursive scripts, are
4811             automatically turned off, so don't let the direction default to LTR or RTL in
4812             the Shaper call, and then try to fix it up in C<textHS()>.
4813              
4814             =item align => 'flag'
4815              
4816             Given the current output location, align the
4817             text at the B<B>eginning of the line (left for LTR, right for RTL), B<C>entered
4818             at the location, or at the B<E>nd of the line (right for LTR, left for RTL).
4819             The default is B<B>. B<C>entered is analogous to using C<text_center()>, and
4820             B<E>nd is analogous to using C<text_right()>. Similar alignments are done for
4821             TTB and BTT.
4822              
4823             =item 'dump' => flag
4824              
4825             Set to 1, it prints out positioning and glyph CID information (to STDOUT) for
4826             each glyph in the chunk. The default is 0 (no information dump).
4827              
4828             =item 'minKern' => amount (default 1)
4829              
4830             If the amount of kerning (font character width B<differs from> glyph I<ax>
4831             value) is I<larger> than this many character grid units, use the unaltered ax
4832             for the width (C<textHS()> will output a kern amount in the TJ operation).
4833             Otherwise, ignore kerning and use ax of the actual character width. The intent
4834             is to avoid bloating the PDF code with unnecessary tiny kerning adjustments in
4835             the TJ operation.
4836              
4837             =back
4838              
4839             =item %opts
4840              
4841             This a hash of options.
4842              
4843             =over
4844              
4845             =item 'underline' => underlining_instructions
4846              
4847             See C<text()> for available instructions.
4848              
4849             =item 'strikethru' => strikethrough_instructions
4850              
4851             See C<text()> for available instructions.
4852              
4853             =item 'strokecolor' => line_color
4854              
4855             Color specification (e.g., 'green', '#FF3377') for underline or strikethrough,
4856             if not given in an array with their instructions.
4857              
4858             =back
4859              
4860             =back
4861              
4862             Text is sent I<separately> to HarfBuzz::Shaper in 'chunks' ('segments') of a
4863             single script (alphabet), a
4864             single direction (LTR, RTL, TTB, or BTT), a single font file,
4865             and a single font size. A
4866             chunk may consist of a large amount of text, but at present, C<textHS()> can
4867             only output a single line. For long lines that need to be split into
4868             column-width lines, the best way may be to take the array of hashes returned by
4869             HarfBuzz::Shaper and split it into smaller chunks at spaces and other
4870             whitespace. You may have to query the font to see what the glyph CIDs are for
4871             space and anything else used.
4872              
4873             It is expected that when C<textHS()> is called, that the font and font size
4874             have already been set in PDF::Builder code, as this information is needed to
4875             interpret what HarfBuzz::Shaper is returning, and to write it to the PDF file.
4876             Needless to say, the font should be opened from the same file as was given
4877             to HarfBuzz::Shaper (C<ttfont()> only, with .ttf or .otf files), and the font
4878             size must be the same. The appropriate location on the page must also already
4879             have been specified.
4880              
4881             =back
4882              
4883             =cut
4884              
4885             sub textHS {
4886 0     0 1 0 my ($self, $HSarray, $settings, %opts) = @_;
4887             # TBD justify would be multiple lines split up from a long string,
4888             # not really applicable here
4889             # full justification to stretch/squeeze a line to fit a given width
4890             # might better be done on the $info array out of Shaper
4891             # indent probably not useful at this level
4892             # copy dashed option names to preferred undashed names
4893 0 0 0     0 if (defined $opts{'-underline'} && !defined $opts{'underline'}) { $opts{'underline'} = delete($opts{'-underline'}); }
  0         0  
4894 0 0 0     0 if (defined $opts{'-strikethru'} && !defined $opts{'strikethru'}) { $opts{'strikethru'} = delete($opts{'-strikethru'}); }
  0         0  
4895 0 0 0     0 if (defined $opts{'-strokecolor'} && !defined $opts{'strokecolor'}) { $opts{'strokecolor'} = delete($opts{'-strokecolor'}); }
  0         0  
4896              
4897 0         0 $self->_Tpending();
4898              
4899 0         0 my $font = $self->{' font'};
4900 0         0 my $fontsize = $self->{' fontsize'};
4901 0   0     0 my $dir = $settings->{'dir'} || 'L';
4902 0   0     0 my $align = $settings->{'align'} || 'B';
4903 0   0     0 my $dump = $settings->{'dump'} || 0;
4904 0   0     0 my $script = $settings->{'script'} || 'Latn'; # Latn (Latin), etc.
4905 0         0 my $language; # not used
4906 0 0       0 if (defined $settings->{'language'}) {
4907 0         0 $language = $settings->{'language'};
4908             }
4909 0   0     0 my $minKern = $settings->{'minKern'} || 1; # greater than 1 don't omit kern
4910 0         0 my (@ulxy1, @ulxy2);
4911              
4912 0         0 my $dokern = 1; # why did they take away smartmatch???
4913 0         0 foreach my $feature (@{ $settings->{'features'} }) {
  0         0  
4914 0 0       0 if ($feature ne '-kern') { next; }
  0         0  
4915 0         0 $dokern = 0;
4916 0         0 last;
4917             }
4918 0 0 0     0 if ($dir eq 'T' || $dir eq 'B') { $dokern = 0; }
  0         0  
4919              
4920             # check if font and font size set
4921 0 0       0 if ($self->{' fontset'} == 0) {
4922 0 0 0     0 unless (defined($self->{' font'}) and $self->{' fontsize'}) {
4923 0         0 croak q{Can't add text without first setting a font and font size};
4924             }
4925 0         0 $self->font($self->{' font'}, $self->{' fontsize'});
4926 0         0 $self->{' fontset'} = 1;
4927             }
4928             # TBD consider indent option (at Beginning of line)
4929              
4930             # Horiz width, Vert height
4931 0         0 my $chunkLength = $self->advancewidthHS($HSarray, $settings,
4932             %opts, 'doKern'=>$dokern, 'minKern'=>$minKern);
4933 0         0 my $kernPts = 0; # amount of kerning (left adjust) this glyph
4934 0         0 my $prevKernPts = 0; # amount previous glyph (THIS TJ operator)
4935              
4936             # Ltr: lower left of next character box
4937             # Rtl: lower right of next character box
4938             # Ttb: center top of next character box
4939             # Btt: center bottom of next character box
4940 0         0 my @currentOffset = (0, 0);
4941 0         0 my @currentPos = $self->textpos();
4942 0         0 my @startPos = @currentPos;
4943              
4944 0         0 my $mult;
4945             # need to first back up (to left) to write chunk
4946             # LTR/TTB B and RTL/BTT E write (LTR/TTB) at current position anyway
4947 0 0 0     0 if ($dir eq 'L' || $dir eq 'T') {
4948 0 0       0 if ($align eq 'B') {
    0          
4949 0         0 $mult = 0;
4950             } elsif ($align eq 'C') {
4951 0         0 $mult = -.5;
4952             } else { # align E
4953 0         0 $mult = -1;
4954             }
4955             } else { # dir R or B
4956 0 0       0 if ($align eq 'B') {
    0          
4957 0         0 $mult = -1;
4958             } elsif ($align eq 'C') {
4959 0         0 $mult = -.5;
4960             } else { # align E
4961 0         0 $mult = 0;
4962             }
4963             }
4964 0 0       0 if ($mult != 0) {
4965 0 0 0     0 if ($dir eq 'L' || $dir eq 'R') {
4966 0         0 $self->translate($currentPos[0]+$chunkLength*$mult, $currentPos[1]);
4967             # now can just write chunk LTR
4968             } else {
4969 0         0 $self->translate($currentPos[0], $currentPos[1]-$chunkLength*$mult);
4970             # now can just write chunk TTB
4971             }
4972             }
4973              
4974             # start of any underline or strikethru
4975 0         0 @ulxy1 = (0, $self->textpos());
4976              
4977 0         0 foreach my $glyph (@$HSarray) { # loop through all glyphs in chunk
4978 0         0 my $ax = $glyph->{'ax'}; # output as LTR, +ax = advance to right
4979 0         0 my $ay = $glyph->{'ay'};
4980 0         0 my $dx = $glyph->{'dx'};
4981 0         0 my $dy = $glyph->{'dy'};
4982 0         0 my $g = $glyph->{'g'};
4983 0         0 my $gCID = sprintf("%04x", $g);
4984 0         0 my $cw = $ax;
4985            
4986             # kerning for any LTR or RTL script? not just Latin script?
4987 0 0       0 if ($dokern) {
4988             # kerning, etc. cw != ax, but ignore tiny differences
4989             # cw = width font (and Reader) thinks character is
4990 0         0 $cw = $font->wxByCId($g)/1000*$fontsize;
4991             # if kerning ( ax < cw ), set kern amount as difference.
4992             # very small amounts ignore by setting ax = cw
4993             # (> minKern? use the kerning, else ax = cw)
4994             # Shaper may expand spacing, too!
4995 0         0 $kernPts = $cw - $ax; # sometimes < 0 !
4996 0 0       0 if ($kernPts != 0) {
4997 0 0       0 if (int(abs($kernPts*1000/$fontsize)+0.5) <= $minKern) {
4998             # small amount, cancel kerning
4999 0         0 $kernPts = 0;
5000 0         0 $ax = $cw;
5001             }
5002             }
5003 0 0 0     0 if ($dump && $cw != $ax) {
5004 0         0 print "cw exceeds ax by ".sprintf("%.2f", $cw-$ax)."\n";
5005             }
5006             # kerning to NEXT glyph (used on next loop)
5007             # this is why we use axs and axr instead of changing ax, so it
5008             # won't think a huge amount of kerning is requested!
5009             }
5010              
5011 0 0       0 if ($dump) {
5012 0         0 print "glyph CID $g ";
5013 0 0       0 if ($glyph->{'name'} ne '') { print "name '$glyph->{'name'}' "; }
  0         0  
5014 0         0 print "offset x/y $dx/$dy ";
5015 0         0 print "orig. ax $ax ";
5016             } # continued after $ax modification...
5017              
5018             # keep coordinated with advancewidthHS(), see for documentation
5019 0 0       0 if (defined $glyph->{'axs'}) {
    0          
    0          
    0          
5020 0         0 $ax = $glyph->{'axs'};
5021             } elsif (defined $glyph->{'axsp'}) {
5022 0         0 $ax *= $glyph->{'axsp'}/100;
5023             } elsif (defined $glyph->{'axr'}) {
5024 0         0 $ax -= $glyph->{'axr'};
5025             } elsif (defined $glyph->{'axrp'}) {
5026 0         0 $ax *= (1 - $glyph->{'axrp'}/100);
5027             }
5028              
5029 0 0       0 if ($dump) { # ...continued
5030 0         0 print "advance x/y $ax/$ay "; # modified ax
5031 0         0 print "char width $cw ";
5032 0 0 0     0 if ($ay != 0 || $dx != 0 || $dy != 0) {
      0        
5033 0         0 print "! "; # flag that adjustments needed
5034             }
5035 0 0       0 if ($kernPts != 0) {
5036 0         0 print "!! "; # flag that kerning is apparently done
5037             }
5038 0         0 print "\n";
5039             }
5040              
5041             # dy not 0? end everything and output Td and do a Tj
5042             # internal location (textpos) should be at dx=dy=0, as should
5043             # be currentOffset array. however, Reader current position is
5044             # likely to be at last Tm or Td.
5045             # note that RTL is output LTR
5046 0 0       0 if ($dy != 0) {
5047 0         0 $self->_endCID();
5048              
5049             # consider ignoring any kern request, if vertically adjusting dy
5050 0         0 my $xadj = $dx - $prevKernPts;
5051 0         0 my $yadj = $dy;
5052             # currentOffset should be at beginning of glyph before dx/dy
5053             # text matrix should be there, too
5054             # Reader is still back at Tm/Td plus any glyphs so far
5055 0         0 @currentPos = ($currentPos[0]+$currentOffset[0]+$xadj,
5056             $currentPos[1]+$currentOffset[1]+$yadj);
5057             # $self->translate(@currentPos);
5058 0         0 $self->distance($currentOffset[0]+$xadj,
5059             $currentOffset[1]+$yadj);
5060              
5061 0         0 $self->add("<$gCID> Tj");
5062             # add glyph to subset list
5063 0         0 $font->fontfile()->subsetByCId($g);
5064              
5065 0         0 @currentOffset = (0, 0);
5066             # restore positions to base line for next character
5067 0         0 @currentPos = ($currentPos[0]+$prevKernPts-$dx+$ax,
5068             $currentPos[1]-$dy+$ay);
5069             # $self->translate(@currentPos);
5070 0         0 $self->distance($prevKernPts-$dx+$ax, -$dy+$ay);
5071              
5072             } else {
5073             # otherwise simply add glyph to TJ array, with possible x adj
5074 0         0 $self->_outputCID($gCID, $dx, $prevKernPts, $font);
5075 0         0 $currentOffset[0] += $ax + $dx;
5076 0         0 $currentOffset[1] += $ay; # for LTR/RTL probably always 0
5077 0         0 $self->matrix_update($ax + $dx, $ay);
5078             }
5079              
5080 0         0 $prevKernPts = $kernPts; # for next glyph's adjustment
5081 0         0 $kernPts = 0;
5082             } # end of chunk by individual glyphs
5083 0         0 $self->_endCID();
5084              
5085             # if LTR, need to move to right end, if RTL, need to return to left end.
5086             # if TTB, need to move to the bottom, if BTT, need to return to top
5087 0 0 0     0 if ($dir eq 'L' || $dir eq 'T') {
5088 0 0       0 if ($align eq 'B') {
    0          
5089 0         0 $mult = 1;
5090             } elsif ($align eq 'C') {
5091 0         0 $mult = .5;
5092             } else { # align E
5093 0         0 $mult = 0;
5094             }
5095             } else { # dir R or B
5096 0         0 $mult = -1;
5097 0 0       0 if ($align eq 'B') {
    0          
5098             } elsif ($align eq 'C') {
5099 0         0 $mult = -.5;
5100             } else { # align E
5101 0         0 $mult = 0;
5102             }
5103             }
5104 0 0 0     0 if ($dir eq 'L' || $dir eq 'R') {
5105 0         0 $self->translate($startPos[0]+$chunkLength*$mult, $startPos[1]);
5106             } else {
5107 0         0 $self->translate($startPos[0], $startPos[1]-$chunkLength*$mult);
5108             }
5109              
5110 0 0 0     0 if ($dir eq 'L' || $dir eq 'R') {
5111 0         0 @ulxy2 = (0, $ulxy1[1]+$chunkLength, $ulxy1[2]);
5112             } else {
5113 0         0 @ulxy2 = (0, $ulxy1[1], $ulxy1[2]-$chunkLength);
5114             }
5115              
5116             # need to swap ulxy1 and ulxy2? draw UL or ST L to R. direction of 'up'
5117             # depends on LTR, so doesn't work if draw RTL. ditto for TTB/BTT.
5118 0 0 0     0 if (($dir eq 'L' || $dir eq 'R') && $ulxy1[1] > $ulxy2[1] ||
      0        
      0        
      0        
      0        
5119             ($dir eq 'T' || $dir eq 'B') && $ulxy1[2] < $ulxy2[2]) {
5120 0         0 my $t;
5121 0         0 $t = $ulxy1[1]; $ulxy1[1]=$ulxy2[1]; $ulxy2[1]=$t;
  0         0  
  0         0  
5122 0         0 $t = $ulxy1[2]; $ulxy1[2]=$ulxy2[2]; $ulxy2[2]=$t;
  0         0  
  0         0  
5123             }
5124              
5125             # handle outputting underline and strikethru here
5126 0 0       0 if (defined $opts{'underline'}) {
5127 0         0 $self->_text_underline(\@ulxy1,\@ulxy2, $opts{'underline'}, $opts{'strokecolor'});
5128             }
5129 0 0       0 if (defined $opts{'strikethru'}) {
5130 0         0 $self->_text_strikethru(\@ulxy1,\@ulxy2, $opts{'strikethru'}, $opts{'strokecolor'});
5131             }
5132              
5133 0         0 return $chunkLength;
5134             } # end of textHS
5135              
5136             # output any pending text state-related commands before ink hits paper
5137             # currently text matrix (Tm), font select (Tf), displacement (Td),
5138             # stroke color (text, RG/K/G/SC), fill color (text, rg/k/g/sc)
5139             # future?
5140             sub _Tpending {
5141 71     71   148 my ($self) = @_;
5142 71         111 my $item;
5143 71         170 foreach (qw(Tf Tm color Color)) {
5144 284         582 $item = $self->{' Tpending'}{$_};
5145 284 50 66     894 if (defined $item && $item ne '') {
5146 0         0 $self->add($item);
5147 0         0 $self->{' Tpending'}{$_} = '';
5148             }
5149             }
5150 71         153 return;
5151             }
5152              
5153             # output any pending graphics state-related commands before ink hits paper
5154             # currently stroke color (graphics), fill color (graphics)
5155             # future? linewidth (w), linejoin (j), linecap (J), linedash (d), et al.
5156             sub _Gpending {
5157 548     548   1122 my ($self) = @_;
5158 548         870 my $item;
5159 548         1142 foreach (qw(color Color)) {
5160 1096         2329 $item = $self->{' Gpending'}{$_};
5161 1096 50 66     3583 if (defined $item && $item ne '') {
5162 0         0 $self->add($item);
5163 0         0 $self->{' Gpending'}{$_} = '';
5164             }
5165             }
5166 548         1033 return;
5167             }
5168              
5169             sub _startCID {
5170 0     0   0 my ($self) = @_;
5171 0 0       0 if ($self->{' openglyphlist'}) { return; }
  0         0  
5172 0         0 $self->addNS(" [<");
5173 0         0 return;
5174             }
5175            
5176             sub _endCID {
5177 0     0   0 my ($self) = @_;
5178 0 0       0 if (!$self->{' openglyphlist'}) { return; }
  0         0  
5179 0         0 $self->addNS(">] TJ ");
5180             # TBD look into detecting empty list already, avoid <> in TJ
5181 0         0 $self->{' openglyphlist'} = 0;
5182 0         0 return;
5183             }
5184              
5185             sub _outputCID {
5186 0     0   0 my ($self, $glyph, $dx, $kern, $font) = @_;
5187             # outputs a single glyph to TJ array, either adding to existing glyph
5188             # string or starting new one after kern amount. kern > 0 moves left,
5189             # dx > 0 moves right, both in points (change to milliems).
5190             # add glyph to subset list
5191 0         0 $font->fontfile()->subsetByCId(hex($glyph));
5192              
5193 0 0       0 if (!$self->{' openglyphlist'}) {
5194             # need to output [< first
5195 0         0 $self->_startCID();
5196 0         0 $self->{' openglyphlist'} = 1;
5197             }
5198              
5199 0 0       0 if ($dx == $kern) {
5200             # no adjustment, just add to existing output
5201 0         0 $self->addNS($glyph); # <> still open
5202             } else {
5203 0         0 $kern -= $dx;
5204             # adjust right by dx after closing glyph string
5205             # dx>0 is move char RIGHT, kern>0 is move char LEFT, both in points
5206             # kern/fontsize*1000 is units to move left, round to 1 decimal place
5207             # >0 means move left (in TJ operation) that many char grid units
5208 0         0 $kern *= (1000/$self->{' fontsize'});
5209             # output correction (char grid units) and this glyph in new <> string
5210 0         0 $self->addNS(sprintf("> %.1f <%s", $kern, $glyph));
5211             # TBD look into detecting empty list already, avoid <> in TJ
5212             }
5213 0         0 return;
5214             }
5215              
5216             =head4 advancewidthHS, text_widthHS
5217              
5218             $width = $content->advancewidthHS($HSarray, $settings, %opts)
5219              
5220             =over
5221              
5222             Returns text chunk width (in points) for Shaper-defined glyph array.
5223             This is the horizontal width for LTR and RTL direction, and the vertical
5224             height for TTB and BTT direction.
5225             B<Note:> You must define the font and font size I<before> calling
5226             C<advancewidthHS()>.
5227              
5228             =over
5229              
5230             =item $HSarray
5231              
5232             The array reference of glyphs created by the HarfBuzz::Shaper call.
5233             See C<textHS()> for details.
5234              
5235             =item $settings
5236              
5237             the hash reference of settings. See C<textHS()> for details.
5238              
5239             =over
5240              
5241             =item 'dir' => 'L' etc.
5242              
5243             the direction of the text, to know which "advance" value to sum up.
5244              
5245             =back
5246              
5247             =item %opts
5248              
5249             Options. Unlike C<advancewidth()>, you
5250             cannot override the font, font size, etc. used by HarfBuzz::Shaper to calculate
5251             the glyph list.
5252              
5253             =over
5254              
5255             =item 'doKern' => flag (default 1)
5256              
5257             If 1, cancel minor kerns per C<minKern> setting. This flag should be 0 (false)
5258             if B<-kern> was passed to HarfBuzz::Shaper (do not kern text).
5259             This is treated as 0 if an ax override setting is given.
5260              
5261             =item 'minKern' => amount (default 1)
5262              
5263             If the amount of kerning (font character width B<differs from> glyph I<ax>
5264             value) is I<larger> than this many character grid units, use the unaltered I<ax>
5265             for the width (C<textHS()> will output a kern amount in the TJ operation).
5266             Otherwise, ignore kerning and use ax of the actual character width. The intent
5267             is to avoid bloating the PDF code with unnecessary tiny kerning adjustments in
5268             the TJ operation.
5269              
5270             =back
5271              
5272             =back
5273              
5274             Returns total width in points.
5275              
5276             B<Alternate name:> C<text_widthHS>
5277              
5278             =back
5279              
5280             =cut
5281              
5282 0     0 1 0 sub text_widthHS { return advancewidthHS(@_); } ## no critic
5283              
5284             sub advancewidthHS {
5285 0     0 1 0 my ($self, $HSarray, $settings, %opts) = @_;
5286             # copy dashed option names to preferred undashed names
5287 0 0 0     0 if (defined $opts{'-doKern'} && !defined $opts{'doKern'}) { $opts{'doKern'} = delete($opts{'-doKern'}); }
  0         0  
5288 0 0 0     0 if (defined $opts{'-minKern'} && !defined $opts{'minKern'}) { $opts{'minKern'} = delete($opts{'-minKern'}); }
  0         0  
5289              
5290             # check if font and font size set
5291 0 0       0 if ($self->{' fontset'} == 0) {
5292 0 0 0     0 unless (defined($self->{' font'}) and $self->{' fontsize'}) {
5293 0         0 croak q{Can't add text without first setting a font and font size};
5294             }
5295 0         0 $self->font($self->{' font'}, $self->{' fontsize'});
5296 0         0 $self->{' fontset'} = 1;
5297             }
5298              
5299 0   0     0 my $doKern = $opts{'doKern'} || 1; # flag
5300 0   0     0 my $minKern = $opts{'minKern'} || 1; # character grid units (about 1/1000 em)
5301 0         0 my $dir = $settings->{'dir'};
5302 0 0 0     0 if ($dir eq 'T' || $dir eq 'B') { # vertical text
5303 0         0 $doKern = 0;
5304             }
5305              
5306 0         0 my $width = 0;
5307 0         0 my $ax = 0;
5308 0         0 my $cw = 0;
5309             # simply go through the array and add up all the 'ax' values.
5310             # if 'axs' defined, use that instead of 'ax'
5311             # if 'axsp' defined, use that percentage of 'ax'
5312             # if 'axr' defined, reduce 'ax' by that amount (increase if <0)
5313             # if 'axrp' defined, reduce 'ax' by that percentage (increase if <0)
5314             # otherwise use 'ax' value unchanged
5315             # if vertical text, use ay instead
5316             #
5317             # as in textHS(), ignore kerning (small difference between cw and ax)
5318             # however, if user defined an override of ax, assume they want any
5319             # resulting kerning! only look at minKern (default 1 char grid unit)
5320             # if original ax is used.
5321            
5322 0         0 foreach my $glyph (@$HSarray) {
5323 0         0 $ax = $glyph->{'ax'};
5324 0 0 0     0 if ($dir eq 'T' || $dir eq 'B') {
5325 0         0 $ax = $glyph->{'ay'} * -1;
5326             }
5327              
5328 0 0       0 if (defined $glyph->{'axs'}) {
    0          
    0          
    0          
5329 0         0 $width += $glyph->{'axs'};
5330             } elsif (defined $glyph->{'axsp'}) {
5331 0         0 $width += $glyph->{'axsp'}/100 * $ax;
5332             } elsif (defined $glyph->{'axr'}) {
5333 0         0 $width += ($ax - $glyph->{'axr'});
5334             } elsif (defined $glyph->{'axrp'}) {
5335 0         0 $width += $ax * (1 - $glyph->{'axrp'}/100);
5336             } else {
5337 0 0       0 if ($doKern) {
5338             # kerning, etc. cw != ax, but ignore tiny differences
5339 0         0 my $fontsize = $self->{' fontsize'};
5340             # cw = width font (and Reader) thinks character is (points)
5341 0         0 $cw = $self->{' font'}->wxByCId($glyph->{'g'})/1000*$fontsize;
5342             # if kerning ( ax < cw ), set kern amount as difference.
5343             # very small amounts ignore by setting ax = cw
5344             # (> minKern? use the kerning, else ax = cw)
5345             # textHS() should be making the same adjustment as here
5346 0         0 my $kernPts = $cw - $ax; # sometimes < 0 !
5347 0 0       0 if ($kernPts > 0) {
5348 0 0       0 if (int(abs($kernPts*1000/$fontsize)+0.5) <= $minKern) {
5349             # small amount, cancel kerning
5350 0         0 $ax = $cw;
5351             }
5352             }
5353             }
5354 0         0 $width += $ax;
5355             }
5356             }
5357              
5358 0         0 return $width; # height >0 for TTB and BTT
5359             }
5360              
5361             =head2 Advanced Methods
5362              
5363             =head3 save
5364              
5365             $content->save()
5366              
5367             =over
5368              
5369             Saves the current I<graphics> state on a PDF stack. See PDF definition 8.4.2
5370             through 8.4.4 for details. This includes the line width, the line cap style,
5371             line join style, miter limit, line dash pattern, stroke color, fill color,
5372             current transformation matrix, current clipping port, flatness, and dictname.
5373              
5374             This method applies to I<only> I<gfx/graphics> objects. If attempted with
5375             I<text> objects, you will receive a one-time (per run) warning message, and
5376             should update your code B<not> to do save() and restore() on a text object.
5377             Only save() generates the message, as presumably each restore() has already had
5378             a save() performed.
5379              
5380             =back
5381              
5382             =cut
5383              
5384             # 8.4.1 Table 52 Graphics State Parameters (device independent) -----------
5385             # current transformation matrix*, current clipping path*, current color space,
5386             # current color*, TEXT painting parameters (see 9.3), line width*%, line cap*%,
5387             # line join*%, miter limit*%, dash pattern*%, rendering intent%, stroke adjust%,
5388             # blend mode%, soft mask, alpha constant%, alpha source%
5389             # 8.4.1 Table 53 Graphics State Parameters (device dependent) -------------
5390             # overprint%, overprint mode%, black generation%, undercolor removal%,
5391             # transfer%, halftone%, flatness*%, smoothness%
5392             # 9.3 Table 104 Text State Parameters -------------------------------------
5393             # character spacing+, word spacing+, horizontal scaling+, leading+, text font+,
5394             # text font size+, text rendering mode+, text rise+, text knockout%
5395             # * saved on graphics state stack
5396             # + now saved on graphics state stack since save/restore enabled for text
5397             # % see ExtGState.pm for setting as extended graphics state
5398              
5399             sub _save {
5400 11     11   84 return 'q';
5401             }
5402              
5403             sub save {
5404 11     11 1 32 my ($self) = shift;
5405              
5406 11         25 our @MSG_COUNT;
5407 11 50       45 if ($self->_in_text_object()) {
5408             # warning in text mode, no other effect
5409 0 0       0 if (!$MSG_COUNT[2]) {
5410 0         0 print STDERR "Can not call save() or restore() on a text object.\n";
5411 0         0 $MSG_COUNT[2]++;
5412             }
5413             } else {
5414 11         58 $self->_Gpending(); # flush buffered commands
5415 11         59 $self->add(_save());
5416             }
5417              
5418 11         44 return $self;
5419             }
5420              
5421             =head3 restore
5422              
5423             $content->restore()
5424              
5425             =over
5426              
5427             Restores the most recently saved graphics state (see C<save>),
5428             removing it from the stack. You cannot I<restore> the graphics state (pop it off
5429             the stack) unless you have done at least one I<save> (pushed it on the stack).
5430             This method applies to both I<text> and I<gfx/graphics> objects.
5431              
5432             =back
5433              
5434             =cut
5435              
5436             sub _restore {
5437 11     11   35 return 'Q';
5438             }
5439              
5440             sub restore {
5441 11     11 1 27 my ($self) = shift;
5442              
5443 11 50       52 if ($self->_in_text_object()) {
5444             # save() already gave any warning
5445             } else {
5446 11         40 $self->add(_restore());
5447             }
5448              
5449 11         35 return $self;
5450             }
5451              
5452             =head3 add
5453              
5454             $content->add(@content)
5455              
5456             =over
5457              
5458             Add raw content (arbitrary string(s)) to the PDF stream.
5459             You will generally want to use the other methods in this class instead,
5460             unless this is in order to implement some PDF operation that PDF::Builder
5461             does not natively support. An array of multiple strings may be given;
5462             they will be concatenated with spaces between them.
5463              
5464             Be careful when doing this, as you are dabbling in the black arts,
5465             directly setting PDF operations!
5466              
5467             One interesting use is to split up an overly long object stream that is giving
5468             your editor problems when exploring a PDF file. Add a newline B<add("\n")>
5469             every few hundred bytes of output or so, to do this. Note that you must use
5470             double quotes (quotation marks), rather than single quotes (apostrophes).
5471              
5472             Use extreme care if inserting B<BT> and B<ET> markers into the PDF stream.
5473             You may want to use C<textstart()> and C<textend()> calls instead, and even
5474             then, there are many side effects either way. It is generally not useful
5475             to suspend text mode with ET/textend() and BT/textstart(), but it is possible,
5476             if you I<really> need to do it.
5477              
5478             Another, useful, case is when your input PDF is from the B<Chrome browser>
5479             printing a page to PDF with
5480             headers and/or footers. In some versions, this leaves the PDF page with a
5481             strange scaling (such as the page height in points divided by 3300) and the
5482             Y-axis flipped so 0 is at the top. This causes problems when trying to add
5483             additional text or graphics in a new text or graphics record, where text is
5484             flipped (mirrored) upside down and at the wrong end of the page. If this
5485             happens, you might be able to cure it by adding
5486              
5487             $scale = .23999999; # example, 792/3300, examine PDF or experiment!
5488             ...
5489             if ($scale != 1) {
5490             my @pageDim = $page->mediabox(); # e.g., 0 0 612 792
5491             my $size_page = $pageDim[3]/$scale; # 3300 = 792/.23999999
5492             my $invScale = 1.0/$scale; # 4.16666684
5493             $text->add("$invScale 0 0 -$invScale 0 $size_page cm");
5494             }
5495              
5496             as the first output to the C<$text> stream. Unfortunately, it is difficult to
5497             predict exactly what C<$scale> should be, as it may be 3300 units per page, or
5498             a fixed amount. You may need to examine an uncompressed PDF file stream to
5499             see what is being used. It I<might> be possible to get the input (original)
5500             PDF into a string and look for a certain pattern of "cm" output
5501              
5502             .2399999 0 0 -.23999999 0 792 cm
5503              
5504             or similar, which is not within a save/restore (q/Q). If the stream is
5505             already compressed, this might not be possible.
5506              
5507             =back
5508              
5509             =head3 addNS
5510              
5511             $content->addNS(@content)
5512              
5513             =over
5514              
5515             Like C<add()>, but does B<not> make sure there is a space between each element
5516             and before and after the new content. It is up to I<you> to ensure that any
5517             necessary spaces in the PDF stream are placed there explicitly!
5518              
5519             =back
5520              
5521             =cut
5522              
5523             # add to 'poststream' string (dumped by ET)
5524             sub add_post {
5525 0     0 0 0 my ($self) = shift;
5526              
5527 0 0       0 if (@_) {
5528 0 0       0 unless ($self->{' poststream'} =~ m|\s$|) {
5529 0         0 $self->{' poststream'} .= ' ';
5530             }
5531 0         0 $self->{' poststream'} .= join(' ', @_) . ' ';
5532             }
5533              
5534 0         0 return $self;
5535             }
5536              
5537             sub add {
5538 919     919 1 1848 my $self = shift;
5539              
5540 919 50       4255 if (@_) {
5541 919 100 66     5913 unless (defined $self->{' stream'} && $self->{' stream'} =~ m|\s$|) {
5542 159         543 $self->{' stream'} .= ' ';
5543             }
5544             # have started seeing undefined elements in @_. skip them for now.
5545             #$self->{' stream'} .= encode('iso-8859-1', join(' ', @_) . ' ');
5546 919         1809 my $ecstr = '';
5547 919         2058 foreach (@_) {
5548 2521 50       5130 if (defined $_) {
5549 2521 100       5034 if ($ecstr eq '') { # first
5550 919         1851 $ecstr = $_;
5551             } else {
5552 1602         3272 $ecstr .= " $_";
5553             }
5554             }
5555             }
5556 919 50       2147 if ($ecstr ne '') {
5557 919         7402 $self->{' stream'} .= encode('iso-8859-1', $ecstr . ' ');
5558             }
5559             }
5560              
5561 919         22989 return $self;
5562             }
5563              
5564             sub addNS {
5565 0     0 1 0 my $self = shift;
5566              
5567 0 0       0 if (@_) {
5568 0         0 $self->{' stream'} .= encode('iso-8859-1', join('', @_));
5569             }
5570              
5571 0         0 return $self;
5572             }
5573              
5574             # Shortcut method for determining if we're inside a text object
5575             # (i.e., between BT and ET). See textstart() and textend().
5576             sub _in_text_object {
5577 645     645   1493 my ($self) = shift;
5578              
5579 645         2059 return $self->{' apiistext'};
5580             }
5581              
5582             =head3 compressFlate
5583              
5584             $content->compressFlate()
5585              
5586             =over
5587              
5588             Marks content for compression on output. This is done automatically
5589             in nearly all cases, so you shouldn't need to call this yourself.
5590              
5591             The C<new()> call can set the B<compress> parameter to 'flate' (default) to
5592             compress all object streams, or 'none' to suppress compression and allow you
5593             to examine the output in an editor.
5594              
5595             =back
5596              
5597             =cut
5598              
5599             sub compressFlate {
5600 28     28 1 75 my $self = shift;
5601              
5602 28         114 $self->{'Filter'} = PDFArray(PDFName('FlateDecode'));
5603 28         73 $self->{'-docompress'} = 1;
5604              
5605 28         62 return $self;
5606             }
5607              
5608             =head3 textstart
5609              
5610             $content->textstart()
5611              
5612             =over
5613              
5614             Starts a text object (ignored if already in a text object). You will likely
5615             want to use the C<text()> method (text I<context>, not text output) instead.
5616              
5617             Note that calling this method, besides outputting a B<BT> marker, will reset
5618             most text settings to their default values. In addition, B<BT> itself will
5619             reset some transformation matrices.
5620              
5621             =back
5622              
5623             =cut
5624              
5625             sub textstart {
5626 20     20 1 53 my ($self) = @_;
5627              
5628 20 50       123 unless ($self->_in_text_object()) {
5629 20         109 $self->add(' BT ');
5630 20         56 $self->{' apiistext'} = 1;
5631 20         49 $self->{' font'} = undef;
5632 20         43 $self->{' fontset'} = 0;
5633 20         43 $self->{' fontsize'} = 0;
5634 20         266 $self->{' charspace'} = 0;
5635 20         73 $self->{' hscale'} = 100;
5636 20         152 $self->{' wordspace'} = 0;
5637 20         56 $self->{' leading'} = 0;
5638 20         72 $self->{' rise'} = 0;
5639 20         49 $self->{' render'} = 0;
5640 20         59 $self->{' textlinestart'} = 0;
5641 20         50 @{$self->{' matrix'}} = (1,0,0,1,0,0);
  20         78  
5642 20         72 @{$self->{' textmatrix'}} = (1,0,0,1,0,0);
  20         60  
5643 20         66 @{$self->{' textlinematrix'}} = (0,0);
  20         57  
5644 20         44 @{$self->{' fillcolor'}} = (0);
  20         48  
5645 20         56 @{$self->{' strokecolor'}} = (0);
  20         49  
5646 20         39 @{$self->{' translate'}} = (0,0);
  20         51  
5647 20         42 @{$self->{' scale'}} = (1,1);
  20         50  
5648 20         41 @{$self->{' skew'}} = (0,0);
  20         52  
5649 20         39 $self->{' rotate'} = 0;
5650 20         43 $self->{' openglyphlist'} = 0;
5651             }
5652              
5653 20         703 return $self;
5654             }
5655              
5656             =head3 textend
5657              
5658             $content->textend()
5659              
5660             =over
5661              
5662             Ends a text object (ignored if not in a text object).
5663              
5664             Note that calling this method, besides outputting an B<ET> marker, will output
5665             any accumulated I<poststream> content.
5666              
5667             =back
5668              
5669             =cut
5670              
5671             sub textend {
5672 152     152 1 468 my ($self) = @_;
5673              
5674 152 100       617 if ($self->_in_text_object()) {
5675 16         77 $self->add(' ET ', $self->{' poststream'});
5676 16         44 $self->{' apiistext'} = 0;
5677 16         45 $self->{' poststream'} = '';
5678             }
5679              
5680 152         336 return $self;
5681             }
5682              
5683             # helper function for many methods
5684             sub resource {
5685 34     34 0 153 my ($self, $type, $key, $obj, $force) = @_;
5686              
5687 34 100       163 if ($self->{' apipage'}) {
5688             # we are a content stream on a page.
5689 32         238 return $self->{' apipage'}->resource($type, $key, $obj, $force);
5690             } else {
5691             # we are a self-contained content stream.
5692 2   33     9 $self->{'Resources'} //= PDFDict();
5693              
5694 2         4 my $dict = $self->{'Resources'};
5695 2 50       10 $dict->realise() if ref($dict) =~ /Objind$/;
5696              
5697 2   33     52 $dict->{$type} ||= PDFDict();
5698 2 50       9 $dict->{$type}->realise() if ref($dict->{$type}) =~ /Objind$/;
5699 2 50       6 unless (defined $obj) {
5700 0   0       return $dict->{$type}->{$key} || undef;
5701             } else {
5702 2 50       9 if ($force) {
5703 0         0 $dict->{$type}->{$key} = $obj;
5704             } else {
5705 2   33     11 $dict->{$type}->{$key} ||= $obj;
5706             }
5707 2         7 return $dict;
5708             }
5709             }
5710             }
5711              
5712             1;