File Coverage

blib/lib/SWF/Builder/Shape.pm
Criterion Covered Total %
statement 113 568 19.8
branch 12 124 9.6
condition 8 72 11.1
subroutine 22 77 28.5
pod 0 6 0.0
total 155 847 18.3


line stmt bran cond sub pod time code
1             package SWF::Builder::Shape;
2              
3 1     1   5 use strict;
  1         2  
  1         35  
4 1     1   5 use Carp;
  1         2  
  1         62  
5 1     1   5 use SWF::Element;
  1         1  
  1         18  
6 1     1   4 use SWF::Builder::ExElement;
  1         2  
  1         1832  
7              
8             our $VERSION="0.02";
9              
10             ####
11              
12             @SWF::Builder::Shape::ISA = ('SWF::Builder::Shape::ExDraw');
13              
14             sub new {
15 5     5 0 56 my $class = shift;
16            
17 5         39 my $self = bless {
18             _current_line_width => 1,
19             _current_X => 0,
20             _current_Y => 0,
21             _prev_X => 0,
22             _prev_Y => 0,
23             _start_X => 0,
24             _start_Y => 0,
25             _pos_stack => [],
26             _current_font => undef,
27             _current_size => 12,
28             _edges => SWF::Element::SHAPE->ShapeRecords->new,
29             _bounds => SWF::Builder::ExElement::BoundaryRect->new,
30             }, $class;
31            
32 5         23 $self->_init;
33 5         37 $self->moveto(0,0);
34             }
35              
36 4     4   6 sub _init {}
37              
38             sub _set_bounds {
39 240     240   375 my ($self, $x, $y) = @_;
40 240         397 my $cw = $self->{_current_line_width} * 10;
41            
42 240         997 $self->{_bounds}->set_boundary($x-$cw, $y-$cw, $x+$cw, $y+$cw);
43             }
44              
45             sub _get_stylerecord {
46 23     23   29 my $self = shift;
47 23         53 my $edges = $self->{_edges};
48 23         24 my $r;
49 23 100       84 if (ref($edges->[-1])=~/STYLECHANGERECORD$/) {
50 15         24 $r = $edges->[-1];
51             } else {
52 8         37 $r = $edges->new_element;
53 8         136 push @$edges, $r;
54             }
55 23         45 return $r;
56             }
57              
58             sub get_bbox {
59 0     0 0 0 return map{$_/20} @{shift->{_bounds}};
  0         0  
  0         0  
60             }
61              
62             #### drawing elements ####
63             # handling _edges directly.
64             # based on TWIPS.
65              
66             sub _set_style {
67 23     23   78 my ($self, %param) = @_;
68 23         59 my $r = $self->_get_stylerecord;
69            
70 23         52 for my $p (qw/ MoveDeltaX MoveDeltaY FillStyle0 FillStyle1 LineStyle /) {
71 115 100       797 $r->$p($param{$p}) if exists $param{$p};
72             }
73 23         91 return $r;
74             }
75              
76             sub _r_lineto_twips {
77 0     0   0 my $self = shift;
78 0         0 my $edges = $self->{_edges};
79            
80 0         0 while (my($dx, $dy) = splice(@_, 0, 2)) {
81 0         0 $dx = _round($dx);
82 0         0 $dy = _round($dy);
83 0 0 0     0 if ($dx or $dy) {
84 0         0 $self->{_prev_X} = $self->{_current_X};
85 0         0 $self->{_prev_Y} = $self->{_current_Y};
86 0         0 push @$edges, $edges->new_element( DeltaX => $dx, DeltaY => $dy );
87 0         0 $dx = ($self->{_current_X} += $dx);
88 0         0 $dy = ($self->{_current_Y} += $dy);
89 0         0 $self->_set_bounds($dx, $dy);
90             }
91             }
92 0         0 $self;
93             }
94              
95             sub _lineto_twips {
96 7     7   15 my $self = shift;
97 7         14 my $edges = $self->{_edges};
98            
99 7         26 while (my($x, $y) = splice(@_, 0, 2)) {
100 7         19 $x = _round($x);
101 7         19 $y = _round($y);
102 7         17 my $dx = $x-$self->{_current_X};
103 7         15 my $dy = $y-$self->{_current_Y};
104 7 50 66     29 if ($dx or $dy) {
105 7         13 $self->{_prev_X} = $self->{_current_X};
106 7         95 $self->{_prev_Y} = $self->{_current_Y};
107 7         26 push @$edges, $edges->new_element( DeltaX => $dx, DeltaY => $dy );
108 7         488 $self->{_current_X} = $x;
109 7         14 $self->{_current_Y} = $y;
110 7         20 $self->_set_bounds($x, $y);
111             }
112             }
113 7         30 $self;
114             }
115              
116             sub _r_curveto_twips {
117 0     0   0 my $self = shift;
118 0         0 my $edges = $self->{_edges};
119            
120 0         0 while(my($cdx, $cdy, $adx, $ady) = splice(@_, 0, 4)) {
121 0         0 my $curx = $self->{_current_X};
122 0         0 my $cury = $self->{_current_Y};
123 0         0 $cdx = _round($cdx);
124 0         0 $cdy = _round($cdy);
125 0         0 $adx = _round($adx);
126 0         0 $ady = _round($ady);
127 0 0 0     0 if ($cdx == 0 and $cdy == 0) {
    0 0        
128 0 0 0     0 if ($adx != 0 or $ady != 0) {
129 0         0 push @$edges, $edges->new_element( DeltaX => $adx, DeltaY => $ady);
130             } else {
131 0         0 next;
132             }
133             } elsif ($adx == 0 and $ady == 0) {
134 0         0 push @$edges, $edges->new_element( DeltaX => $cdx, DeltaY => $cdy);
135             } else {
136 0         0 push @$edges, $edges->new_element
137             (
138             ControlDeltaX => $cdx,
139             ControlDeltaY => $cdy,
140             AnchorDeltaX => $adx,
141             AnchorDeltaY => $ady,
142             );
143             }
144 0 0 0     0 if ($adx or $ady) {
145 0         0 $self->{_prev_X} = $curx + $cdx;
146 0         0 $self->{_prev_Y} = $cury + $cdy;
147             } else {
148 0         0 $self->{_prev_X} = $curx;
149 0         0 $self->{_prev_Y} = $cury;
150             }
151 0         0 $adx = $self->{_current_X} = $curx + $cdx + $adx;
152 0         0 $ady = $self->{_current_Y} = $cury + $cdy + $ady;
153 0         0 $self->_set_bounds($adx, $ady);
154 0         0 $self->_set_bounds($curx+$cdx, $cury+$cdy, 1); # 1: off curve
155             }
156 0         0 $self;
157             }
158              
159             sub _curveto_twips {
160 110     110   310 my $self = shift;
161 110         166 my $edges = $self->{_edges};
162            
163 110         320 while(my ($cx, $cy, $ax, $ay) = splice(@_, 0, 4)) {
164 110         145 my $curx = $self->{_current_X};
165 110         186 my $cury = $self->{_current_Y};
166 110         266 $cx = _round($cx);
167 110         248 $cy = _round($cy);
168 110         352 $ax = _round($ax);
169 110         242 $ay = _round($ay);
170 110         149 my $cdx = $cx-$curx;
171 110         129 my $cdy = $cy-$cury;
172 110         122 my $adx = $ax-$cx;
173 110         115 my $ady = $ay-$cy;
174 110 50 66     459 if ($cdx == 0 and $cdy == 0) {
    50 66        
175 0 0 0     0 if ($adx != 0 or $ady != 0) {
176 0         0 push @$edges, $edges->new_element( DeltaX => $adx, DeltaY => $ady);
177             } else {
178 0         0 next;
179             }
180             } elsif ($adx == 0 and $ady == 0) {
181 0         0 push @$edges, $edges->new_element( DeltaX => $cdx, DeltaY => $cdy);
182             } else {
183 110         325 push @$edges, $edges->new_element
184             (
185             ControlDeltaX => $cdx,
186             ControlDeltaY => $cdy,
187             AnchorDeltaX => $adx,
188             AnchorDeltaY => $ady,
189             );
190             }
191 110 50 66     9777 if ($adx or $ady) {
192 110         177 $self->{_prev_X} = $cx;
193 110         151 $self->{_prev_Y} = $cy;
194             } else {
195 0         0 $self->{_prev_X} = $curx;
196 0         0 $self->{_prev_Y} = $cury;
197             }
198 110         147 $self->{_current_X} = $ax;
199 110         120 $self->{_current_Y} = $ay;
200 110         207 $self->_set_bounds($ax, $ay);
201 110         233 $self->_set_bounds($cx, $cy, 1); # 1: off curve
202             }
203 110         484 $self;
204             }
205              
206             sub _null_edge {
207 0     0   0 my $self = shift;
208              
209 0         0 push @{$self->{_edges}}, $self->{_edges}->new_element( DeltaX => 0, DeltaY => 0 );
  0         0  
210 0         0 $self;
211             }
212              
213             sub _r_moveto_twips {
214 0     0   0 my ($self, $dx, $dy)=@_;
215            
216 0         0 $dx = _round($dx);
217 0         0 $dy = _round($dy);
218 0         0 $dx = $self->{_current_X} + $dx;
219 0         0 $dy = $self->{_current_Y} + $dy;
220 0         0 $self->_set_style(MoveDeltaX => $dx, MoveDeltaY => $dy);
221 0         0 $self->{_start_X} = $self->{_prev_X} = $self->{_current_X} = $dx;
222 0         0 $self->{_start_Y} = $self->{_prev_Y} = $self->{_current_Y} = $dy;
223 0         0 $self->_set_bounds($dx, $dy);
224 0         0 $self;
225             }
226              
227             sub _moveto_twips {
228 13     13   20 my ($self, $x, $y)=@_;
229            
230 13         47 $x = _round($x);
231 13         33 $y = _round($y);
232 13         46 $self->_set_style(MoveDeltaX => $x, MoveDeltaY => $y);
233 13         35 $self->{_start_X} = $self->{_prev_X} = $self->{_current_X} = $x;
234 13         33 $self->{_start_Y} = $self->{_prev_Y} = $self->{_current_Y} = $y;
235 13         33 $self->_set_bounds($x, $y);
236 13         221 $self;
237             }
238              
239             sub _current_font {
240 0     0   0 my ($self, $font) = @_;
241              
242 0 0       0 $self->{_current_font} = $font if defined $font;
243 0         0 $self->{_current_font};
244             }
245              
246             sub _current_size {
247 0     0   0 my ($self, $size) = @_;
248              
249 0 0       0 $self->{_current_size} = $size if defined $size;
250 0         0 $self->{_current_size};
251             }
252              
253             sub _current_angle {
254 0     0   0 my $self = shift;
255              
256 0         0 return atan2($self->{_current_Y} - $self->{_prev_Y}, $self->{_current_X} - $self->{_prev_X});
257             }
258              
259              
260             sub push_pos {
261 0     0 0 0 my $self = shift;
262 0         0 push @{$self->{_pos_stack}}, [$self->{_current_X}, $self->{_current_Y}];
  0         0  
263 0         0 $self;
264             }
265              
266             sub pop_pos {
267 0     0 0 0 my $self = shift;
268 0         0 $self->_moveto_twips( @{pop @{$self->{_pos_stack}}} );
  0         0  
  0         0  
269 0         0 $self;
270             }
271              
272             sub lineto_pop_pos {
273 0     0 0 0 my $self = shift;
274 0         0 $self->_lineto_twips( @{pop @{$self->{_pos_stack}}} );
  0         0  
  0         0  
275 0         0 $self;
276             }
277              
278             sub close_path {
279 0     0 0 0 my $self = shift;
280              
281 0         0 $self->_lineto_twips( $self->{_start_X}, $self->{_start_Y} );
282             }
283              
284             ####
285              
286             package SWF::Builder::Shape::ExDraw;
287              
288 1     1   7 use warnings::register;
  1         7  
  1         998  
289              
290             # based on pixels (20TWIPS).
291              
292             sub get_pos {
293 0     0   0 my $self = shift;
294 0         0 return ($self->{_current_X}/20, $self->{_current_Y}/20);
295             }
296              
297             #### basic drawing ####
298             # using SWF::Builder::Shape::_*_twips
299              
300             sub r_lineto {
301 0     0   0 my $self = shift;
302              
303 0 0       0 Carp::croak "Invalid count of coordinates" if @_ % 2;
304 0         0 $self->_r_lineto_twips(map $_*20, @_);
305             }
306              
307             sub lineto {
308 7     7   10 my $self = shift;
309            
310 7 50       26 Carp::croak "Invalid count of coordinates" if @_ % 2;
311 7         44 $self->_lineto_twips(map $_*20, @_);
312             }
313              
314             sub r_curveto {
315 0     0   0 my $self = shift;
316            
317 0 0       0 Carp::croak "Invalid count of coordinates" if @_ % 4;
318 0         0 $self->_r_curveto_twips(map $_*20, @_);
319             }
320              
321             sub curveto {
322 110     110   352 my $self = shift;
323            
324 110 50       234 Carp::croak "Invalid count of coordinates" if @_ % 4;
325 110         852 $self->_curveto_twips(map $_*20, @_);
326             }
327              
328             sub moveto {
329 13     13   22 my ($self, $x, $y)=@_;
330              
331 13         56 $self->_moveto_twips($x*20, $y*20);
332             }
333              
334             sub r_moveto {
335 0     0   0 my ($self, $dx, $dy)=@_;
336              
337 0         0 $self->_r_moveto_twips($dx*20, $dy*20);
338             }
339              
340             my %style = ('none' => 0, 'fill' => 1, 'draw' => 1);
341             sub fillstyle {
342 4     4   10 my ($self, $f) = @_;
343 4         5 my $index;
344 4 50       11 if (exists $style{$f}) {
345 0         0 $index = $style{$f};
346             } else {
347 4         9 $index = $f;
348             }
349 4         18 $self->_set_style(FillStyle0 => $index);
350 4         28 $self;
351             }
352             *fillstyle0 = \&fillstyle;
353              
354             sub fillstyle1 {
355 0     0   0 my ($self, $f) = @_;
356 0         0 my $index;
357 0 0       0 if (exists $style{$f}) {
358 0         0 $index = $style{$f};
359             } else {
360 0         0 $index = $f;
361             }
362 0         0 $self->_set_style(FillStyle1 => $index);
363 0         0 $self;
364             }
365              
366             sub linestyle {
367 4     4   8 my ($self, $f) = @_;
368 4         5 my $index;
369 4 50       11 if (exists $style{$f}) {
370 0         0 $index = $style{$f};
371             } else {
372 4         7 $index = $f;
373             }
374 4         8 $self->_set_style(LineStyle => $index);
375 4         14 $self;
376             }
377              
378             sub font {
379 0     0   0 my ($self, $font) = @_;
380            
381 0 0 0     0 Carp::croak "Invalid font" unless UNIVERSAL::isa($font, 'SWF::Builder::Character::Font') and $font->embed;
382 0         0 $self->_current_font($font);
383 0         0 $self;
384             }
385              
386             sub size {
387 0     0   0 my $self = shift;
388 0         0 $self->_current_size(shift);
389 0         0 $self;
390             }
391              
392             sub text {
393 0     0   0 my ($self, $font, $text) = @_;
394            
395 0 0       0 unless (defined $text) {
396 0         0 $text = $font;
397 0         0 $font = $self->_current_font;
398             }
399 0 0 0     0 Carp::croak "Invalid font" unless UNIVERSAL::isa($font, 'SWF::Builder::Character::Font') and eval{$font->embed};
  0         0  
400              
401 0         0 for my $c (split //, $text) {
402 0         0 my $gshape = $self->transform( [scale => $self->_current_size / 51.2, translate => [$self->get_pos]] );
403 0         0 my $adv = $font->_draw_glyph($c, $gshape);
404 0         0 $gshape->moveto($adv, 0);
405             }
406 0         0 $self;
407             }
408              
409             ### extension drawing ###
410             # no-use _*_twips. using basic drawing.
411              
412 1     1   8 use constant PI => 2*atan2(1,0);
  1         3  
  1         3581  
413              
414             sub box {
415 1     1   4 my ($self, $x1, $y1, $x2, $y2) = @_;
416              
417 1         5 $self->moveto($x1,$y1)
418             ->lineto($x2, $y1)
419             ->lineto($x2,$y2)
420             ->lineto($x1, $y2)
421             ->lineto($x1, $y1);
422             }
423              
424             sub rect {
425 0     0     my ($self, $w, $h, $rx, $ry) = @_;
426              
427 0 0         unless (defined $rx) {
428 0           $self->r_lineto($w,0)
429             ->r_lineto(0,$h)
430             ->r_lineto(-$w,0)
431             ->r_lineto(0,-$h);
432             } else {
433 0 0         $ry = $rx unless defined $ry;
434 0           my $rcx = 0.414213562373095 * $rx;
435 0           my $rcy = 0.414213562373095 * $ry;
436 0           my $rax = 0.292893218813453 * $rx;
437 0           my $ray = 0.292893218813453 * $ry;
438 0           $w -= $rx+$rx;
439 0           $h -= $ry+$ry;
440 0           $self->r_moveto($rx, 0)
441             ->r_lineto($w,0)
442             ->r_curveto($rcx, 0, $rax, $ray, $rax, $ray, 0, $rcy)
443             ->r_lineto(0,$h)
444             ->r_curveto(0, $rcy, -$rax, $ray, -$rax, $ray, -$rcx, 0)
445             ->r_lineto(-$w,0)
446             ->r_curveto(-$rcx, 0, -$rax, -$ray, -$rax, -$ray, 0, -$rcy)
447             ->r_lineto(0,-$h)
448             ->r_curveto(0, -$rcy, $rax, -$ray, $rax, -$ray, $rcx, 0)
449             ->r_moveto(-$rx, 0);
450             }
451             }
452              
453             sub curve3to {
454 0     0     require Math::Bezier::Convert;
455            
456 0           my $self = shift;
457 0           my @p = Math::Bezier::Convert::cubic_to_quadratic($self->get_pos, @_);
458 0           shift @p;
459 0           shift @p;
460 0           $self->curveto(@p);
461             }
462              
463             sub r_curve3to {
464 0     0     require Math::Bezier::Convert;
465              
466 0           my $self = shift;
467 0           my @p;
468 0           my ($cx, $cy) = $self->get_pos;
469              
470 0           push @p, $cx, $cy;
471 0           while(my ($x, $y) = splice(@_, 0, 2)) {
472 0           $cx += $x;
473 0           $cy += $y;
474 0           push @p, $cx, $cy;
475             }
476 0           @p = Math::Bezier::Convert::cubic_to_quadratic(@p);
477 0           shift @p;
478 0           shift @p;
479 0           $self->curveto(@p);
480             }
481              
482             sub circle {
483 0     0     my ($self, $r) = @_;
484              
485 0           my $rc = 0.414213562373095 * $r; #
486 0           my $ra = 0.292893218813453 * $r;
487 0           $self->r_moveto(0, -$r)
488             ->r_curveto($rc, 0, $ra, $ra, $ra, $ra, 0, $rc, 0, $rc, -$ra, $ra, -$ra, $ra, -$rc, 0, -$rc, 0, -$ra, -$ra, -$ra, -$ra, 0, -$rc, 0, -$rc, $ra, -$ra, $ra, -$ra, $rc, 0)
489             ->r_moveto(0, $r);
490             }
491              
492             sub ellipse {
493 0     0     my ($self, $rx, $ry, $rot) = @_;
494              
495 0   0       $self->transform( [scale => [1, $ry/$rx], rotate => ($rot||0)] )
496             ->circle($rx)
497             ->end_transform;
498             }
499              
500             sub transform {
501 0     0     my ($self, $matrix, $sub) = @_;
502              
503 0 0         unless (UNIVERSAL::isa($matrix, 'SWF::Builder::ExElement::MATRIX')) {
504 0           $matrix = SWF::Builder::ExElement::MATRIX->new->init($matrix);
505             }
506              
507 0           my $t = SWF::Builder::Shape::Transformer->new($self, $matrix);
508 0 0         if (defined $sub) {
509 0           $sub->($t);
510 0           return $self;
511             } else {
512 0           return $t;
513             }
514             }
515              
516             sub arcto {
517 0     0     my ($self, $startangle, $centralangle, $rx, $ry, $rot) = @_;
518              
519 0 0 0       return $self unless $centralangle and $rx;
520 0   0       $rot ||= 0;
521 0   0       $ry ||= $rx;
522              
523 0           my $ca = $centralangle * PI / 180;
524 0           my $sa = $startangle * PI / 180;
525 0           my $ra = $rot * PI / 180;
526              
527 0 0         if ($rx == $ry) {
528 0           $sa += $ra;
529 0           $self->_arcto_rad($sa, $ca, $rx, $ry);
530             } else {
531 0           $sa -= $ra;
532 0           my $sa2 = $sa;
533              
534 0 0         if (($startangle - $rot) % 90 != 0) {
535 0           $sa = atan2($rx * sin($sa)/cos($sa), $ry);
536 0 0 0       if ($sa2 > PI/2 or $sa2 < -PI()/2) {
537 0           $sa += PI*int(($sa2+PI*($sa2<=>0)/2)/PI);
538             }
539             }
540              
541 0 0         if (($startangle + $centralangle - $rot) % 90 != 0) {
542 0           $ca += $sa2;
543 0           my $ca2 = $ca;
544 0           $ca = atan2($rx * sin($ca)/cos($ca), $ry);
545 0 0 0       if ($ca2 > PI/2 or $ca2 < -PI()/2) {
546 0           $ca += PI*int(($ca2+PI*($ca2<=>0)/2)/PI);
547             }
548 0           $ca -= $sa;
549             }
550 0 0         if ($rot) {
551 0           $self->transform([rotate => $rot])
552             ->_arcto_rad($sa, $ca, $rx, $ry)
553             ->end_transform;
554             } else {
555 0           $self->_arcto_rad($sa, $ca, $rx, $ry);
556             }
557             }
558             }
559            
560             sub _arcto_rad {
561 0     0     my ($self, $sa, $ca, $rx, $ry) = @_;
562 0           my $c = int(abs($ca) / 0.785398163397448) + 1;
563 0           $ca /= $c;
564 0           my $tan_ca2 = sin($ca/2) / cos($ca/2);
565 0           my $cos_ca1 = cos($ca) - 1;
566 0           my $sin_tan = sin($ca) - $tan_ca2;
567 0           my @p;
568 0           for (;$c > 0; $c--, $sa += $ca) {
569 0           my ($sin, $cos) = (sin($sa), cos($sa));
570 0           push @p, ($rx * -$sin * $tan_ca2, $ry * $cos * $tan_ca2,
571             $rx * ($cos * $cos_ca1 - $sin * $sin_tan),
572             $ry * ($sin * $cos_ca1 + $cos * $sin_tan));
573            
574             }
575 0           $self->r_curveto(@p);
576             }
577              
578             sub radial_moveto {
579 0     0     my ($self, $r, $theta) = @_;
580              
581 0           $theta = $self->_current_angle + $theta * PI / 180;
582 0           $self->r_moveto($r * cos($theta), $r * sin($theta));
583             }
584              
585             sub r_radial_moveto {
586 0     0     my ($self, $r, $theta) = @_;
587              
588 0           $theta = $theta * PI / 180;
589 0           $self->r_moveto($r * cos($theta), $r * sin($theta));
590             }
591              
592             sub radial_lineto {
593 0     0     my $self = shift;
594 0           my @p;
595 0           while ( my ($r, $theta) = splice(@_, 0, 2) ) {
596 0           $theta = $theta * PI / 180;
597 0           push @p, ($r * cos($theta), $r * sin($theta));
598             }
599 0           $self->r_lineto(@p);
600             }
601              
602             sub r_radial_lineto {
603 0     0     my $self = shift;
604 0           my @p;
605 0           my $theta = $self->_current_angle;
606 0           while ( my ($r, $dtheta) = splice(@_, 0, 2) ) {
607 0           $theta += $dtheta * PI / 180;
608 0           push @p, ($r * cos($theta), $r * sin($theta));
609             }
610 0           $self->r_lineto(@p);
611             }
612              
613             sub starshape {
614 0     0     my ($self, $or, $points, $ir, $screw) = @_;
615              
616 0   0       $screw ||= 0;
617 0   0       $points ||= 5;
618 0 0         unless (defined $ir) {
619 0           $ir = 0.381966011250105 * $or;
620             } else {
621 0           $ir = (0.5*$ir)**1.388483827 * $or;
622             }
623              
624 0           my $step = 2*PI / $points;
625 0           my $oa = -0.5 * PI;
626 0           my $ia = $oa + 0.5*$step + $screw * PI / 180;
627 0           my ($ox, $oy) = $self->get_pos;
628              
629 0           $self->r_moveto(0, -$or);
630              
631 0           for (1..$points) {
632 0           $oa += $step;
633 0           $self->lineto($ox + $ir * cos($ia), $oy + $ir * sin($ia), $ox + $or * cos($oa), $oy + $or * sin($oa));
634 0           $ia += $step;
635             }
636 0           $self->r_moveto(0, $or);
637             }
638              
639             {
640             my $qrnnum = qr/(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee](?:[+-]?\d+))?/o;
641             my $qrnum = qr/(-?$qrnnum)/o;
642             my $qrwsp = qr/[ \x09\x0d\x0a]/o;
643             my $qrdlm = qr/(?:(?:$qrwsp+,?$qrwsp*)|(?:,$qrwsp*))/o;
644             my $qrcoord = qr/$qrnum$qrdlm?$qrnum/o;
645             my $qrn = qr/\A$qrnum(?:$qrdlm?$qrnum)*\Z/o;
646             my $qrc1 = qr/\A$qrcoord(?:$qrdlm?$qrcoord)*\Z/o;
647             my $qrc2 = qr/\A$qrcoord$qrdlm?$qrcoord(?:$qrdlm?$qrcoord$qrdlm?$qrcoord)*\Z/o;
648              
649             my %qr =
650             ( M => $qrc1,
651             Z => qr/\A\Z/o,
652             L => $qrc1,
653             H => $qrn,
654             V => $qrn,
655             C => qr/\A$qrcoord$qrdlm?$qrcoord$qrdlm?$qrcoord(?:$qrdlm?$qrcoord$qrdlm?$qrcoord$qrdlm?$qrcoord)*\Z/o,
656             S => $qrc2,
657             Q => $qrc2,
658             T => $qrc1,
659             A => qr/\A$qrnum$qrdlm?$qrnum$qrdlm?$qrnum$qrdlm$qrnum$qrdlm$qrnum$qrdlm$qrnum$qrdlm?$qrnum(?:$qrdlm?$qrnum$qrdlm?$qrnum$qrdlm?$qrnum$qrdlm$qrnum$qrdlm$qrnum$qrdlm$qrnum$qrdlm?$qrnum)*\Z/o,
660             );
661              
662             sub path {
663 0     0     my ($self, $path) = @_;
664 0           my $pathobj;
665              
666 0 0         if ($path =~ s/\A$qrwsp*([Mm])([^MmZzLlHhVvCcSsQqTtAa]*)//o) {
667 0           my ($com, $param) = ($1, $2);
668 0           $param =~ s/\A$qrwsp+//o;
669 0           $param =~ s/$qrwsp+\Z//o;
670 0 0         $param =~ $qr{M} or Carp::croak "Invalid path command '$com$param'";
671 0           my @p = grep {defined $_} $param =~/$qrnum/og;
  0            
672 0 0         if ($com eq 'm') {
673 0           for (my $i = 2; $i <= $#p; $i+=2) {
674 0           $p[$i] += $p[0];
675 0           $p[$i+1] += $p[1];
676             }
677             }
678 0           $pathobj = bless {
679             shape => $self,
680             _subpath_origin => [@p[0,1]],
681             _ref_cp => ['M', 0, 1],
682             _current_X => $p[0],
683             _current_Y => $p[1],
684             }, 'SWF::Builder::Shape::Path';
685            
686 0           $pathobj->M(@p);
687             } else {
688 0 0         if (warnings::enabled()) {
689 0           warnings::warn("Path data should begin with 'm' or 'M'");
690             }
691 0           my ($x, $y) = $self->get_pos;
692 0           $pathobj = bless {
693             shape => $self,
694             _subpath_origin => [$x, $y],
695             _ref_cp => ['M', 0, 1],
696             _current_X => $x,
697             _current_Y => $y,
698             }, 'SWF::Builder::Shape::Path';
699             }
700              
701 0           while ($path =~ /([MmZzLlHhVvCcSsQqTtAa])([^MmZzLlHhVvCcSsQqTtAa]*)/g) {
702 0           my ($com, $param) = ($1, $2);
703 0           $param =~ s/\A$qrwsp+//o;
704 0           $param =~ s/$qrwsp+\Z//o;
705 0 0         $param =~ $qr{uc($com)} or Carp::croak "Invalid path command '$com$param'";
706 0           my @p = grep {defined $_} $param =~ /$qrnum/og;
  0            
707 0 0         $pathobj->$com(@p) if ($com eq lc $com);
708 0           $com = uc $com;
709 0           $pathobj->$com(@p);
710              
711 0           $pathobj->{_current_X} = $p[-2];
712 0           $pathobj->{_current_Y} = $p[-1];
713 0           $pathobj->{_ref_cp}[0] = $com;
714             }
715 0           $self;
716             }
717              
718              
719             package SWF::Builder::Shape::Path;
720              
721             sub a {
722 0     0     my $pathobj = shift;
723 0           for (my $i = 5; $i <= $#_; $i+=7) {
724 0           $_[$i] += $pathobj->{_current_X};
725 0           $_[$i+1] += $pathobj->{_current_Y};
726             }
727             }
728              
729             sub h {
730 0     0     my $pathobj = shift;
731 0           for (my $i = 0; $i <= $#_; $i++) {
732 0           $_[$i] += $pathobj->{_current_X};
733             }
734             }
735              
736             sub v {
737 0     0     my $pathobj = shift;
738 0           for (my $i = 0; $i <= $#_; $i++) {
739 0           $_[$i] += $pathobj->{_current_Y};
740             }
741             }
742              
743             sub m {
744 0     0     my $pathobj = shift;
745 0           for (my $i = 0; $i <= $#_; $i+=2) {
746 0           $_[$i] += $pathobj->{_current_X};
747 0           $_[$i+1] += $pathobj->{_current_Y};
748             }
749             }
750              
751             *c = *q = *t = *s = *l = \&m;
752              
753 0     0     sub z {}
754              
755             sub M {
756 0     0     my ($pathobj, $x, $y, @coords) = @_;
757 0           $pathobj->{shape}->moveto($x, $y);
758 0           @{$pathobj->{_subpath_origin}} = ($x, $y);
  0            
759 0 0         if (@coords) {
760 0           $pathobj->L(@coords);
761             }
762             }
763              
764             sub Z {
765 0     0     my $pathobj = shift;
766 0           $pathobj->{shape}->lineto(@{$pathobj->{_subpath_origin}});
  0            
767             }
768              
769             sub L {
770 0     0     my $pathobj = shift;
771 0           $pathobj->{shape}->lineto(@_);
772             }
773              
774             sub H {
775 0     0     my $pathobj = shift;
776 0           my $y = $pathobj->{_current_Y};
777 0           $pathobj->{shape}->lineto(map {($_, $y)} @_);
  0            
778             }
779              
780             sub V {
781 0     0     my $pathobj = shift;
782 0           my $x = $pathobj->{_current_X};
783 0           $pathobj->{shape}->lineto(map {($x, $_)} @_);
  0            
784             }
785              
786             sub C {
787 0     0     my $pathobj = shift;
788 0           $pathobj->{_ref_cp}[1] = $_[-2]*2 - $_[-4];
789 0           $pathobj->{_ref_cp}[2] = $_[-1]*2 - $_[-3];
790 0           $pathobj->{shape}->curve3to(@_);
791             }
792             sub S {
793 0     0     my $pathobj = shift;
794 0           my @coords;
795            
796 0 0         if ($pathobj->{_ref_cp}[0] =~/[CS]/) {
797 0           push @coords, $pathobj->{_ref_cp}[1], $pathobj->{_ref_cp}[2];
798             } else {
799 0           push @coords, $pathobj->{_current_X}, $pathobj->{_current_Y};
800             }
801 0           my ($dx, $dy);
802 0           while (my ($cx, $cy, $x, $y) = splice(@_, 0, 4)) {
803 0           $dx = $x-$cx;
804 0           $dy = $y-$cy;
805 0           push @coords, $cx, $cy, $x, $y, $x+$dx, $y+$dy;
806             }
807 0           $pathobj->{_ref_cp}[2] = pop @coords;
808 0           $pathobj->{_ref_cp}[1] = pop @coords;
809 0           $pathobj->{shape}->curve3to(@coords);
810             }
811              
812             sub Q {
813 0     0     my $pathobj = shift;
814 0           $pathobj->{_ref_cp}[1] = $_[-2]*2 - $_[-4];
815 0           $pathobj->{_ref_cp}[2] = $_[-1]*2 - $_[-3];
816 0           $pathobj->{shape}->curveto(@_);
817             }
818              
819             sub T {
820 0     0     my $pathobj = shift;
821 0           my @coords;
822              
823 0 0         if ($pathobj->{_ref_cp}[0] =~/[QT]/) {
824 0           push @coords, $pathobj->{_ref_cp}[1], $pathobj->{_ref_cp}[2];
825             } else {
826 0           push @coords, $pathobj->{_current_X}, $pathobj->{_current_Y};
827             }
828 0           my ($dx, $dy);
829 0           while (my ($x, $y) = splice(@_, 0, 2)) {
830 0           $dx = $x-$coords[-2];
831 0           $dy = $y-$coords[-1];
832 0           push @coords, $x, $y, $x+$dx, $y+$dy;
833             }
834 0           $pathobj->{_ref_cp}[2] = pop @coords;
835 0           $pathobj->{_ref_cp}[1] = pop @coords;
836 0           $pathobj->{shape}->curveto(@coords);
837             }
838              
839 1     1   14 use constant PI => 2*atan2(1,0);
  1         3  
  1         745  
840              
841             sub A {
842 0     0     my $pathobj = shift;
843 0           my $x1 = $pathobj->{_current_X};
844 0           my $y1 = $pathobj->{_current_Y};
845            
846 0           while (my ($rx, $ry, $rot, $laf, $swf, $x2, $y2) = splice(@_, 0, 7)) {
847              
848 0 0 0       next if ($x1 == $x2 and $y1 == $y2);
849              
850 0 0 0       if ($rx == 0 or $ry == 0) {
851 0           $pathobj->{shape}->lineto($x2, $y2);
852 0           next;
853             }
854              
855 0           $rx = abs($rx);
856 0           $ry = abs($ry);
857 0           $laf = !!$laf;
858 0           $swf = !!$swf;
859              
860 0           my $ra = $rot * PI / 180;
861 0           my $sin = sin($ra);
862 0           my $cos = cos($ra);
863              
864 0           my $dx = ($x1-$x2)/2;
865 0           my $dy = ($y1-$y2)/2;
866 0           my $x1p = $cos * $dx + $sin * $dy;
867 0           my $y1p = -$sin * $dx + $cos * $dy;
868 0           my ($cxp, $cyp);
869 0           my $lambda = ($x1p*$x1p)/($rx*$rx) + ($y1p*$y1p)/($ry*$ry);
870 0 0         if ($lambda > 1) {
871 0           $rx *= sqrt($lambda);
872 0           $ry *= sqrt($lambda);
873 0           $cxp = $cyp = 0;
874             } else {
875 0           my $k = sqrt(($rx*$rx*$ry*$ry-$rx*$rx*$y1p*$y1p-$ry*$ry*$x1p*$x1p) / ($rx*$rx*$y1p*$y1p+$ry*$ry*$x1p*$x1p));
876 0 0         $k = -$k if $laf == $swf;
877 0           $cxp = $k * $rx*$y1p/$ry;
878 0           $cyp = $k * -$ry*$x1p/$rx;
879             }
880 0           my $cx = $cos * $cxp - $sin * $cyp + ($x1 + $x2)/2;
881 0           my $cy = $sin * $cxp + $cos * $cyp + ($y1 + $y2)/2;
882 0           my $ux = ($x1p - $cxp) / $rx;
883 0           my $uy = ($y1p - $cyp) / $ry;
884 0           my $u = sqrt($ux*$ux+$uy*$uy);
885 0           my $vx = (-$x1p - $cxp) / $rx;
886 0           my $vy = (-$y1p - $cyp) / $ry;
887 0           my $v = sqrt($vx*$vx+$vy*$vy);
888 0           my $uv1 = $ux / $u;
889 0           my $theta1 = atan2(sqrt(1-$uv1*$uv1), $uv1);
890 0 0         $theta1 = -$theta1 if $uy<0;
891 0           my $uvd = ($ux*$vx+$uy*$vy)/($u*$v);
892 0 0         my $dtheta = atan2(($lambda>1)?0:sqrt(1-$uvd*$uvd), $uvd);
893 0 0         $dtheta = -$dtheta if ($ux*$vy - $uy*$vx)<0;
894 0 0 0       if ($swf == 0 and $dtheta > 0) {
    0 0        
895 0           $dtheta -= 2*PI;
896             } elsif ($swf == 1 and $dtheta < 0) {
897 0           $dtheta += 2*PI;
898             }
899              
900 0           $pathobj->{shape}->transform([rotate => $rot])
901             ->_arcto_rad($theta1, $dtheta, $rx, $ry)
902             ->end_transform;
903             } continue {
904 0           $x1 = $x2;
905 0           $y1 = $y2;
906             }
907              
908             }
909             }
910              
911              
912             #####
913              
914             {
915             package SWF::Builder::Shape::Transformer;
916              
917 1     1   7 use warnings::register;
  1         2  
  1         1006  
918              
919             @SWF::Builder::Shape::Transformer::ISA = ('SWF::Builder::Shape::ExDraw');
920              
921             sub new {
922 0     0     my ($class, $shape, $matrix) = @_;
923              
924 0           my $self = bless {
925             shape => $shape,
926             matrix => $matrix,
927             inv_matrix => undef,
928             }, $class;
929             }
930              
931             sub get_pos {
932 0     0     my $self = shift;
933 0           my $m = $self->{matrix};
934 0           my $im = $self->{inv_matrix};
935              
936 0 0         unless (defined $im) {
937 0           my $a = $m->ScaleX;
938 0           my $b = $m->RotateSkew0;
939 0           my $c = $m->RotateSkew1;
940 0           my $d = $m->ScaleY;
941 0           my $det = $a*$d - $b*$c;
942              
943 0           $im = SWF::Element::MATRIX->new;
944              
945 0 0         if ($det) {
946 0           $im->ScaleX($d / $det);
947 0           $im->RotateSkew0(-$b / $det);
948 0           $im->RotateSkew1(-$c / $det);
949 0           $im->ScaleY($a / $det);
950             } else {
951 0 0         if (warnings::enabled()) {
952 0           warnings::warn("Can't calculate inverse mapping");
953             }
954 0 0         if ($a-$b == 0) {
955 0           $im->RotateSkew1(0);
956 0           $im->ScaleX(0);
957 0 0         if ($c-$d == 0) {
958 0           $im->RotateSkew0(0);
959 0           $im->ScaleY(0);
960             } else {
961 0           $im->RotateSkew0(1/($c-$d));
962 0           $im->ScaleY(-1/($c-$d));
963             }
964             } else {
965 0           $im->ScaleX(1/($a-$b));
966 0           $im->RotateSkew0(0);
967 0           $im->RotateSkew1(-1/($a-$b));
968 0           $im->ScaleY(0);
969             }
970             }
971 0           $self->{inv_matrix} = $im;
972             }
973 0           my ($x, $y) = $self->{shape}->get_pos;
974 0           $x -= $m->TranslateX * 20; # twips -> pixels
975 0           $y -= $m->TranslateY * 20;
976 0           return ($x * $im->ScaleX + $y * $im->RotateSkew1, $x * $im->RotateSkew0 + $y * $im->ScaleY);
977             }
978              
979             sub _transform {
980 0     0     my $self = shift;
981 0           my $sx = $self->{matrix}->ScaleX;
982 0           my $sy = $self->{matrix}->ScaleY;
983 0           my $r0 = $self->{matrix}->RotateSkew0;
984 0           my $r1 = $self->{matrix}->RotateSkew1;
985 0   0       my $tx = $self->{matrix}->TranslateX||0;
986 0   0       my $ty = $self->{matrix}->TranslateY||0;
987 0           my @p;
988              
989 0           while (my ($x, $y) = splice(@_, 0, 2)) {
990 0           push @p, $x * $sx + $y * $r1 + $tx, $x * $r0 + $y * $sy + $ty;
991             }
992 0           return @p;
993             }
994              
995             sub _r_transform {
996 0     0     my $self = shift;
997 0           my $sx = $self->{matrix}->ScaleX;
998 0           my $sy = $self->{matrix}->ScaleY;
999 0           my $r0 = $self->{matrix}->RotateSkew0;
1000 0           my $r1 = $self->{matrix}->RotateSkew1;
1001 0           my @p;
1002              
1003 0           while (my ($x, $y) = splice(@_, 0, 2)) {
1004 0           push @p, $x * $sx + $y * $r1, $x * $r0 + $y * $sy;
1005             }
1006 0           return @p;
1007             }
1008              
1009             sub end_transform {
1010 0     0     return shift->{shape};
1011             }
1012              
1013             sub AUTOLOAD {
1014 0     0     our $AUTOLOAD;
1015 0 0         return if $AUTOLOAD =~ /::DESTROY$/;
1016              
1017 0           my $self = shift;
1018 0 0         if ($AUTOLOAD =~ /::((_r)?[^:]+to_twips)$/) {
1019 0           my $method = $1;
1020 0 0         if ($2) {
1021 0           $self->{shape}->$method($self->_r_transform(@_));
1022             } else {
1023 0           $self->{shape}->$method($self->_transform(@_));
1024             }
1025             } else {
1026 0           $self->{shape}->$1(@_);
1027             }
1028 0           $self;
1029             }
1030             }