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   1875 use v5.26;
  2         26  
4 2     2   13 use Object::Pad;
  2         4  
  2         17  
5 2     2   303 use utf8;
  2         5  
  2         16  
6 2     2   87 use Carp;
  2         4  
  2         675  
7              
8             class SVGPDF::Path :isa(SVGPDF::Element);
9              
10 2     2   1674 use SVGPDF::Contrib::PathExtract qw( extract_path_info );
  2         9  
  2         7911  
11              
12 36     36 0 64 method process () {
  36         93  
  36         68  
13 36         117 my $atts = $self->atts;
14 36         149 my $xo = $self->xo;
15 36 50       107 return if $atts->{omit}; # for testing/debugging.
16              
17 36 100       103 if ( defined $atts->{id} ) {
18 2         5 $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 36         112 my ( $d, $tf ) = $self->get_params( $atts, "d:!", "transform:s" );
30              
31 36         460 ( my $t = $d ) =~ s/\s+/ /g;
32 36 100       204 $t = substr($t,0,20) . "..." if length($t) > 20;
33 36 50       114 $self->_dbg( $self->name, " d=\"$t\"", $tf ? " tf=\"$tf\"" : "" );
34 36 50       129 return unless $d;
35              
36 36         122 $self->_dbg( "+ xo save" );
37 36         278 $xo->save;
38 36         1956 $self->set_transform($tf);
39 36         144 $self->set_graphics;
40              
41             # Get path info, turning relative coordinates into absolute
42             # and eliminate S and T curves.
43 36         266 my @d = extract_path_info( $d, { absolute => 1, no_smooth => 1 } );
44              
45 36         99 my $open; # path is open
46              
47 36         162 my $paint = $self->_paintsub;
48              
49             # Initial x,y for path, if open. See 'z'.
50 36         124 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 36         121 my ( $cx, $cy ) = ( 0, 0 );
56              
57             # For debugging: collect control points.
58 36         55 my @cp;
59              
60 36         58 my $id = -1; # for diagnostics
61 36         96 while ( @d ) {
62 111         228 my $d = shift(@d);
63 111         245 my $op = $d->{svg_key};
64 111         165 $id++;
65              
66             # Reset starting point for the subpath.
67 111         215 my ( $x, $y ) = ( 0, 0 );
68              
69             # Remember initial point of path.
70 111 100 66     382 $ix = $cx, $iy = $cy unless $open++ || $op eq "Z";
71              
72 111 50 33     4325 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 111 100       506 if ( $op eq "M" ) {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
78 38         159 $x += $d->{point}->[0];
79 38         71 $y += $d->{point}->[1];
80 38         145 $self->_dbg( "xo move(%.2f,%.2f)", $x, $y );
81 38         227 $xo->move( $x, $y );
82             }
83              
84             # Horizontal LineTo.
85             elsif ( $op eq "H" ) {
86 1         4 $x += $d->{x};
87 1         2 $y = $cy;
88 1         5 $self->_dbg( "xo hline(%.2f)", $x );
89 1         5 $xo->hline($x);
90             }
91              
92             # Vertical LineTo.
93             elsif ( $op eq "V" ) {
94 1         3 $x = $cx;
95 1         3 $y += $d->{y};
96 1         7 $self->_dbg( "xo vline(%.2f)", $y );
97 1         7 $xo->vline($y);
98             }
99              
100             # Generic LineTo.
101             elsif ( $op eq "L" ) {
102 28         104 $x += $d->{point}->[0];
103 28         67 $y += $d->{point}->[1];
104 28         148 $self->_dbg( "xo line(%.2f,%.2f)", $x, $y );
105 28         192 $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         70 $y + $d->{end}->[1],
119             );
120 11         36 $self->_dbg( "xo curve(%.2f,%.2f %.2f,%.2f %.2f,%.2f)", @c );
121 11         57 $xo->curve(@c);
122 11         1352 push( @cp, [ $cx, $cy, $c[0], $c[1] ] );
123 11         23 push( @cp, [ $c[4], $c[5], $c[2], $c[3] ] );
124 11         18 $x = $c[4]; $y = $c[5]; # end point
  11         19  
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         115 $y + $d->{end}->[1],
135             );
136 10         51 $self->_dbg( "xo spline(%.2f,%.2f %.2f,%.2f)", @c );
137 10         75 $xo->spline(@c);
138 10         2315 push( @cp, [ $cx, $cy, $c[0], $c[1] ] );
139 10         30 push( @cp, [ $c[2], $c[3], $c[0], $c[1] ] );
140 10         18 $x = $c[2]; $y = $c[3]; # end point
  10         23  
141             }
142              
143             # Arcs.
144             elsif ( $op eq "A" ) {
145 17         74 my $rx = $d->{rx}; # radius 1
146 17         37 my $ry = $d->{ry}; # radius 2
147 17         40 my $rot = $d->{x_axis_rotation}; # rotation
148 17         67 my $large = $d->{large_arc_flag}; # select larger arc
149 17         39 my $sweep = $d->{sweep_flag}; # clockwise
150 17         61 my $ex = $x + $d->{x}; # end point
151 17         45 my $ey = $y + $d->{y};
152 17         79 $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 17 100       73 if ( $rx == $ry ) {
157 6         30 $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 6         34 $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         53 $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         68 $self->elliptic_arc( $cx, $cy, $ex, $ey,
173             $rx, $ry,
174             move => 0,
175             large => $large,
176             rotate => $rot,
177             dir => $sweep );
178             }
179 17         56 ( $x, $y ) = ( $ex, $ey ); # end point
180             }
181              
182             # Close path and paint.
183             elsif ( $op eq "Z" ) {
184 5         22 $self->_dbg( "xo z" );
185 5 50       20 if ( $open ) {
186 5         55 $xo->close;
187 5         241 $open = 0;
188             # currentpoint becomes the initial point.
189 5         10 $x = $ix;
190 5         10 $y = $iy;
191             }
192 5 50 33     18 if ( @d && $d[0]->{svg_key} eq 'M' ) {
193             # Close is followed by a move -> do not paint yet.
194             }
195             else {
196 5         17 $paint->();
197             }
198             }
199              
200             # Unidenfied subpath element.
201             else {
202 0         0 croak("Unidenfied subpath element[$id] $op");
203             }
204              
205 111 100       8592 ( $cx, $cy ) = ( $x, $y ) unless $op eq "Z";
206             }
207              
208 36 100       152 $paint->() if $open;
209 36         1391 $self->_dbg( "- xo restore" );
210 36         170 $xo->restore;
211              
212             # Show collected control points.
213 36         1581 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 36         130 $self->css_pop;
227             }
228              
229 149     149 0 246 method curve ( @points ) {
  149         408  
  149         301  
  149         240  
230 149         473 $self->_dbg( "+ xo curve( %.2f,%.2f %.2f,%.2f %.2f,%.2f )", @points );
231 149         631 $self->xo->curve(@points);
232 149         30195 $self->_dbg( "-" );
233             }
234              
235 11     11 0 21 method elliptic_arc( $x1,$y1, $x2,$y2, $rx,$ry, %opts) {
  11         30  
  11         22  
  11         20  
  11         20  
  11         39  
  11         24  
  11         19  
  11         69  
  11         15  
236 11         808 require SVGPDF::Contrib::Bogen;
237              
238 11         84 SVGPDF::Contrib::Bogen::bogen_ellip
239             ( $self, $x1,$y1, $x2,$y2, $rx,$ry, %opts );
240             }
241              
242 6     6 0 10 method circular_arc( $x1,$y1, $x2,$y2, $r, %opts) {
  6         19  
  6         12  
  6         10  
  6         12  
  6         12  
  6         10  
  6         33  
  6         11  
243 6         52 require SVGPDF::Contrib::Bogen;
244              
245             SVGPDF::Contrib::Bogen::bogen
246             ( $self, $x1,$y1, $x2,$y2, $r,
247 6         31 $opts{move}, $opts{large}, $opts{dir} );
248             }
249              
250             1;