File Coverage

blib/lib/SVGPDF/Path.pm
Criterion Covered Total %
statement 135 137 98.5
branch 34 42 80.9
condition 4 9 44.4
subroutine 9 9 100.0
pod 0 4 0.0
total 182 201 90.5


line stmt bran cond sub pod time code
1             #! perl
2              
3 2     2   983 use v5.26;
  2         5  
4 2     2   7 use Object::Pad;
  2         4  
  2         9  
5 2     2   184 use utf8;
  2         2  
  2         9  
6 2     2   36 use Carp;
  2         2  
  2         413  
7              
8             class SVGPDF::Path :isa(SVGPDF::Element);
9              
10 2     2   1061 use SVGPDF::Contrib::PathExtract qw( extract_path_info );
  2         5  
  2         5502  
11              
12 41     41 0 50 method process () {
  41         64  
  41         33  
13 41         94 my $atts = $self->atts;
14 41         88 my $xo = $self->xo;
15 41 50       97 return if $atts->{omit}; # for testing/debugging.
16              
17 41 100       88 if ( defined $atts->{id} ) {
18 2         6 $self->root->defs->{ "#" . $atts->{id} } = $self;
19             # MathJax uses curves to draw glyphs. These glyphs are filles
20             # *and* stroked with a very small stroke-width. According to
21             # the PDF specs, this should yield a 1-pixel (device pixel)
22             # stroke, which results in fat glyphs on screen.
23             # To avoid this, disable stroke when drawing MathJax glyphs.
24 2 50       8 if ( $atts->{id} =~ /^MJX-/ ) {
25 0         0 $atts->{stroke} = 'none';
26             }
27             }
28              
29 41         132 my ( $d, $tf ) = $self->get_params( $atts, "d:!", "transform:s" );
30              
31 41         371 ( my $t = $d ) =~ s/\s+/ /g;
32 41 100       177 $t = substr($t,0,20) . "..." if length($t) > 20;
33 41 50       106 $self->_dbg( $self->name, " d=\"$t\"", $tf ? " tf=\"$tf\"" : "" );
34 41 50       109 return unless $d;
35              
36 41         97 $self->_dbg( "+ xo save" );
37 41         196 $xo->save;
38 41         1677 $self->set_transform($tf);
39 41         248 $self->set_graphics;
40              
41             # Get path info, turning relative coordinates into absolute
42             # and eliminate S and T curves.
43 41         215 my @d = extract_path_info( $d, { absolute => 1, no_smooth => 1 } );
44              
45 41         85 my $open; # path is open
46              
47 41         114 my $paint = $self->_paintsub;
48              
49             # Initial x,y for path, if open. See 'z'.
50 41         59 my ( $ix, $iy );
51              
52             # Current point. Starting point of this path.
53             # Since we're always use absolute coordinates, this is the
54             # starting point for subpaths as well.
55 41         57 my ( $cx, $cy ) = ( 0, 0 );
56              
57             # For debugging: collect control points.
58 41         40 my @cp;
59              
60 41         49 my $id = -1; # for diagnostics
61 41         60 while ( @d ) {
62 123         178 my $d = shift(@d);
63 123         194 my $op = $d->{svg_key};
64 123         128 $id++;
65              
66             # Reset starting point for the subpath.
67 123         198 my ( $x, $y ) = ( 0, 0 );
68              
69             # Remember initial point of path.
70 123 100 66     325 $ix = $cx, $iy = $cy unless $open++ || $op eq "Z";
71              
72 123 50 33     2883 warn(sprintf("%s[%d] x=%.2f,y=%.2f cx=%.2f,cy=%.2f ix=%.2f,iy=%.2f\n",
73             $op, $id, $x, $y, $cx, $cy, $ix, $iy))
74             if 0 & ($x || $y || $ix || $iy);
75              
76             # MoveTo
77 123 100       381 if ( $op eq "M" ) {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
78 43         94 $x += $d->{point}->[0];
79 43         53 $y += $d->{point}->[1];
80 43         91 $self->_dbg( "xo move(%.2f,%.2f)", $x, $y );
81 43         210 $xo->move( $x, $y );
82             }
83              
84             # Horizontal LineTo.
85             elsif ( $op eq "H" ) {
86 1         3 $x += $d->{x};
87 1         1 $y = $cy;
88 1         4 $self->_dbg( "xo hline(%.2f)", $x );
89 1         5 $xo->hline($x);
90             }
91              
92             # Vertical LineTo.
93             elsif ( $op eq "V" ) {
94 1         2 $x = $cx;
95 1         2 $y += $d->{y};
96 1         4 $self->_dbg( "xo vline(%.2f)", $y );
97 1         5 $xo->vline($y);
98             }
99              
100             # Generic LineTo.
101             elsif ( $op eq "L" ) {
102 29         70 $x += $d->{point}->[0];
103 29         48 $y += $d->{point}->[1];
104 29         67 $self->_dbg( "xo line(%.2f,%.2f)", $x, $y );
105 29         108 $xo->line( $x, $y );
106             }
107              
108             # Cubic Bézier curves.
109             elsif ( $op eq "C" ) {
110             my @c = ( # control point 1
111             $x + $d->{control1}->[0],
112             $y + $d->{control1}->[1],
113             # control point 2
114             $x + $d->{control2}->[0],
115             $y + $d->{control2}->[1],
116             # end point
117             $x + $d->{end}->[0],
118 11         60 $y + $d->{end}->[1],
119             );
120 11         29 $self->_dbg( "xo curve(%.2f,%.2f %.2f,%.2f %.2f,%.2f)", @c );
121 11         42 $xo->curve(@c);
122 11         1246 push( @cp, [ $cx, $cy, $c[0], $c[1] ] );
123 11         16 push( @cp, [ $c[4], $c[5], $c[2], $c[3] ] );
124 11         15 $x = $c[4]; $y = $c[5]; # end point
  11         16  
125             }
126              
127             # Quadratic Bézier curves.
128             elsif ( $op eq "Q" ) {
129             my @c = ( # control point 1
130             $x + $d->{control}->[0],
131             $y + $d->{control}->[1],
132             # end point
133             $x + $d->{end}->[0],
134 10         41 $y + $d->{end}->[1],
135             );
136 10         29 $self->_dbg( "xo spline(%.2f,%.2f %.2f,%.2f)", @c );
137 10         43 $xo->spline(@c);
138 10         1395 push( @cp, [ $cx, $cy, $c[0], $c[1] ] );
139 10         18 push( @cp, [ $c[2], $c[3], $c[0], $c[1] ] );
140 10         11 $x = $c[2]; $y = $c[3]; # end point
  10         14  
141             }
142              
143             # Arcs.
144             elsif ( $op eq "A" ) {
145 22         40 my $rx = $d->{rx}; # radius 1
146 22         35 my $ry = $d->{ry}; # radius 2
147 22         33 my $rot = $d->{x_axis_rotation}; # rotation
148 22         26 my $large = $d->{large_arc_flag}; # select larger arc
149 22         33 my $sweep = $d->{sweep_flag}; # clockwise
150 22         49 my $ex = $x + $d->{x}; # end point
151 22         41 my $ey = $y + $d->{y};
152 22         63 $self->_dbg( "xo arc(%.2f,%.2f %.2f %d,%d %.2f,%.2f)",
153             $rx, $ry, $rot, $large, $sweep, $ex, $ey );
154              
155             # for circular arcs.
156 22 100       67 if ( $rx == $ry ) {
157 11         33 $self->_dbg( "circular_arc(%.2f,%.2f %.2f,%.2f %.2f ".
158             "move=%d large=%d dir=%d rot=%.2f)",
159             $cx, $cy, $ex, $ey, $rx,
160             0, $large, $sweep, $rot );
161 11         39 $self->circular_arc( $cx, $cy, $ex, $ey, $rx,
162             move => 0,
163             large => $large,
164             rotate => $rot,
165             dir => $sweep );
166             }
167             else {
168 11         34 $self->_dbg( "elliptic_arc(%.2f,%.2f %.2f,%.2f %.2f,%.2f ".
169             "move=%d large=%d dir=%d rot=%.2f)",
170             $cx, $cy, $ex, $ey, $rx, $ry,
171             0, $large, $sweep, $rot );
172 11         44 $self->elliptic_arc( $cx, $cy, $ex, $ey,
173             $rx, $ry,
174             move => 0,
175             large => $large,
176             rotate => $rot,
177             dir => $sweep );
178             }
179 22         50 ( $x, $y ) = ( $ex, $ey ); # end point
180             }
181              
182             # Close path and paint.
183             elsif ( $op eq "Z" ) {
184 6         13 $self->_dbg( "xo z" );
185 6 50       17 if ( $open ) {
186 6         21 $xo->close;
187 6         183 $open = 0;
188             # currentpoint becomes the initial point.
189 6         8 $x = $ix;
190 6         6 $y = $iy;
191             }
192 6 50 33     15 if ( @d && $d[0]->{svg_key} eq 'M' ) {
193             # Close is followed by a move -> do not paint yet.
194             }
195             else {
196 6         28 $paint->();
197             }
198             }
199              
200             # Unidenfied subpath element.
201             else {
202 0         0 croak("Unidenfied subpath element[$id] $op");
203             }
204              
205 123 100       6263 ( $cx, $cy ) = ( $x, $y ) unless $op eq "Z";
206             }
207              
208 41 100       109 $paint->() if $open;
209 41         1054 $self->_dbg( "- xo restore" );
210 41         122 $xo->restore;
211              
212             # Show collected control points.
213 41         1169 if ( 0 && $self->root->debug && @cp ) {
214             $xo->save;
215             $xo->stroke_color('lime');
216             $xo->line_width(0.5);
217             for ( @cp ) {
218             $self->_dbg( "xo line(%.2f %.2f %.2f %.2f)", @$_ );
219             $xo->move( $_->[0], $_->[1] );
220             $xo->line( $_->[2], $_->[3] );
221             }
222             $xo->stroke;
223             $xo->restore;
224             }
225              
226 41         124 $self->css_pop;
227             }
228              
229 189     189 0 218 method curve ( @points ) {
  189         256  
  189         229  
  189         163  
230 189         399 $self->_dbg( "+ xo curve( %.2f,%.2f %.2f,%.2f %.2f,%.2f )", @points );
231 189         591 $self->xo->curve(@points);
232 189         23947 $self->_dbg( "-" );
233             }
234              
235 11     11 0 30 method elliptic_arc( $x1,$y1, $x2,$y2, $rx,$ry, %opts) {
  11         23  
  11         13  
  11         11  
  11         28  
  11         10  
  11         14  
  11         13  
  11         40  
  11         11  
236 11         498 require SVGPDF::Contrib::Bogen;
237              
238 11         48 SVGPDF::Contrib::Bogen::bogen_ellip
239             ( $self, $x1,$y1, $x2,$y2, $rx,$ry, %opts );
240             }
241              
242 11     11 0 15 method circular_arc( $x1,$y1, $x2,$y2, $r, %opts) {
  11         17  
  11         13  
  11         17  
  11         11  
  11         10  
  11         14  
  11         39  
  11         11  
243 11         75 require SVGPDF::Contrib::Bogen;
244              
245             SVGPDF::Contrib::Bogen::bogen
246             ( $self, $x1,$y1, $x2,$y2, $r,
247 11         41 $opts{move}, $opts{large}, $opts{dir} );
248             }
249              
250             1;