@@ -518,7 +518,7 @@ protected void parsePath() {
518518 c == 'S' || c == 's' ||
519519 c == 'Q' || c == 'q' || // quadratic beziers
520520 c == 'T' || c == 't' ||
521- // c == 'A' || c == 'a' || // elliptical arc
521+ c == 'A' || c == 'a' || // elliptical arc
522522 c == 'Z' || c == 'z' || // closepath
523523 c == ',' ) {
524524 separate = true ;
@@ -816,6 +816,40 @@ protected void parsePath() {
816816 }
817817 break ;
818818
819+ // A - elliptical arc to (absolute)
820+ case 'A' : {
821+ float rx = PApplet .parseFloat (pathTokens [i + 1 ]);
822+ float ry = PApplet .parseFloat (pathTokens [i + 2 ]);
823+ float angle = PApplet .parseFloat (pathTokens [i + 3 ]);
824+ boolean fa = PApplet .parseFloat (pathTokens [i + 4 ]) != 0 ;
825+ boolean fs = PApplet .parseFloat (pathTokens [i + 5 ]) != 0 ;
826+ float endX = PApplet .parseFloat (pathTokens [i + 6 ]);
827+ float endY = PApplet .parseFloat (pathTokens [i + 7 ]);
828+ parsePathArcto (cx , cy , rx , ry , angle , fa , fs , endX , endY );
829+ cx = endX ;
830+ cy = endY ;
831+ i += 8 ;
832+ prevCurve = true ;
833+ }
834+ break ;
835+
836+ // a - elliptical arc to (relative)
837+ case 'a' : {
838+ float rx = PApplet .parseFloat (pathTokens [i + 1 ]);
839+ float ry = PApplet .parseFloat (pathTokens [i + 2 ]);
840+ float angle = PApplet .parseFloat (pathTokens [i + 3 ]);
841+ boolean fa = PApplet .parseFloat (pathTokens [i + 4 ]) != 0 ;
842+ boolean fs = PApplet .parseFloat (pathTokens [i + 5 ]) != 0 ;
843+ float endX = cx + PApplet .parseFloat (pathTokens [i + 6 ]);
844+ float endY = cy + PApplet .parseFloat (pathTokens [i + 7 ]);
845+ parsePathArcto (cx , cy , rx , ry , angle , fa , fs , endX , endY );
846+ cx = endX ;
847+ cy = endY ;
848+ i += 8 ;
849+ prevCurve = true ;
850+ }
851+ break ;
852+
819853 case 'Z' :
820854 case 'z' :
821855 // since closing the path, the 'current' point needs
@@ -924,6 +958,93 @@ private void parsePathQuadto(float cx, float cy,
924958 }
925959
926960
961+ // Approximates elliptical arc by several bezier segments.
962+ // Meets SVG standard requirements from:
963+ // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
964+ // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
965+ // Based on arc to bezier curve equations from:
966+ // http://www.spaceroots.org/documents/ellipse/node22.html
967+ private void parsePathArcto (float x1 , float y1 ,
968+ float rx , float ry ,
969+ float angle ,
970+ boolean fa , boolean fs ,
971+ float x2 , float y2 ) {
972+ if (x1 == x2 && y1 == y2 ) return ;
973+ if (rx == 0 || ry == 0 ) { parsePathLineto (x2 , y2 ); return ; }
974+
975+ rx = PApplet .abs (rx ); ry = PApplet .abs (ry );
976+
977+ float phi = PApplet .radians (((angle % 360 ) + 360 ) % 360 );
978+ float cosPhi = PApplet .cos (phi ), sinPhi = PApplet .sin (phi );
979+
980+ float x1r = ( cosPhi * (x1 - x2 ) + sinPhi * (y1 - y2 )) / 2 ;
981+ float y1r = (-sinPhi * (x1 - x2 ) + cosPhi * (y1 - y2 )) / 2 ;
982+
983+ float cxr , cyr ;
984+ {
985+ float A = (x1r *x1r ) / (rx *rx ) + (y1r *y1r ) / (ry *ry );
986+ if (A > 1 ) {
987+ // No solution, scale ellipse up according to SVG standard
988+ float sqrtA = PApplet .sqrt (A );
989+ rx *= sqrtA ; cxr = 0 ;
990+ ry *= sqrtA ; cyr = 0 ;
991+ } else {
992+ float k = ((fa == fs ) ? -1f : 1f ) *
993+ PApplet .sqrt ((rx *rx * ry *ry ) / ((rx *rx * y1r *y1r ) + (ry *ry * x1r *x1r )) - 1f );
994+ cxr = k * rx * y1r / ry ;
995+ cyr = -k * ry * x1r / rx ;
996+ }
997+ }
998+
999+ float cx = cosPhi * cxr - sinPhi * cyr + (x1 + x2 ) / 2 ;
1000+ float cy = sinPhi * cxr + cosPhi * cyr + (y1 + y2 ) / 2 ;
1001+
1002+ float phi1 , phiDelta ;
1003+ {
1004+ float sx = ( x1r - cxr ) / rx , sy = ( y1r - cyr ) / ry ;
1005+ float tx = (-x1r - cxr ) / rx , ty = (-y1r - cyr ) / ry ;
1006+ phi1 = PApplet .atan2 (sy , sx );
1007+ phiDelta = (((PApplet .atan2 (ty , tx ) - phi1 ) % TWO_PI ) + TWO_PI ) % TWO_PI ;
1008+ if (!fs ) phiDelta -= TWO_PI ;
1009+ }
1010+
1011+ // One segment can not cover more that PI, less than PI/2 is
1012+ // recommended to avoid visible inaccuracies caused by rounding errors
1013+ int segmentCount = PApplet .ceil (PApplet .abs (phiDelta ) / TWO_PI * 4 );
1014+
1015+ float inc = phiDelta / segmentCount ;
1016+ float a = PApplet .sin (inc ) *
1017+ (PApplet .sqrt (4 + 3 * PApplet .sq (PApplet .tan (inc / 2 ))) - 1 ) / 3 ;
1018+
1019+ float sinPhi1 = PApplet .sin (phi1 ), cosPhi1 = PApplet .cos (phi1 );
1020+
1021+ float p1x = x1 ;
1022+ float p1y = y1 ;
1023+ float relq1x = a * (-rx * cosPhi * sinPhi1 - ry * sinPhi * cosPhi1 );
1024+ float relq1y = a * (-rx * sinPhi * sinPhi1 + ry * cosPhi * cosPhi1 );
1025+
1026+ for (int i = 0 ; i < segmentCount ; i ++) {
1027+ float eta = phi1 + (i + 1 ) * inc ;
1028+ float sinEta = PApplet .sin (eta ), cosEta = PApplet .cos (eta );
1029+
1030+ float p2x = cx + rx * cosPhi * cosEta - ry * sinPhi * sinEta ;
1031+ float p2y = cy + rx * sinPhi * cosEta + ry * cosPhi * sinEta ;
1032+ float relq2x = a * (-rx * cosPhi * sinEta - ry * sinPhi * cosEta );
1033+ float relq2y = a * (-rx * sinPhi * sinEta + ry * cosPhi * cosEta );
1034+
1035+ if (i == segmentCount - 1 ) { p2x = x2 ; p2y = y2 ; }
1036+
1037+ parsePathCode (BEZIER_VERTEX );
1038+ parsePathVertex (p1x + relq1x , p1y + relq1y );
1039+ parsePathVertex (p2x - relq2x , p2y - relq2y );
1040+ parsePathVertex (p2x , p2y );
1041+
1042+ p1x = p2x ; relq1x = relq2x ;
1043+ p1y = p2y ; relq1y = relq2y ;
1044+ }
1045+ }
1046+
1047+
9271048 /**
9281049 * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D
9291050 * is rotated relative to the SVG definition, so parameters are rearranged
0 commit comments