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