1 /* libs/graphics/animator/SkSVGPath.cpp 2 ** 3 ** Copyright 2006, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 18 #include <ctype.h> 19 #include "SkDrawPath.h" 20 #include "SkParse.h" 21 #include "SkPoint.h" 22 #include "SkUtils.h" 23 #define QUADRATIC_APPROXIMATION 1 24 25 #if QUADRATIC_APPROXIMATION 26 //////////////////////////////////////////////////////////////////////////////////// 27 //functions to approximate a cubic using two quadratics 28 29 // midPt sets the first argument to be the midpoint of the other two 30 // it is used by quadApprox 31 static inline void midPt(SkPoint& dest,const SkPoint& a,const SkPoint& b) 32 { 33 dest.set(SkScalarAve(a.fX, b.fX),SkScalarAve(a.fY, b.fY)); 34 } 35 // quadApprox - makes an approximation, which we hope is faster 36 static void quadApprox(SkPath &fPath, const SkPoint &p0, const SkPoint &p1, const SkPoint &p2) 37 { 38 //divide the cubic up into two cubics, then convert them into quadratics 39 //define our points 40 SkPoint c,j,k,l,m,n,o,p,q, mid; 41 fPath.getLastPt(&c); 42 midPt(j, p0, c); 43 midPt(k, p0, p1); 44 midPt(l, p1, p2); 45 midPt(o, j, k); 46 midPt(p, k, l); 47 midPt(q, o, p); 48 //compute the first half 49 m.set(SkScalarHalf(3*j.fX - c.fX), SkScalarHalf(3*j.fY - c.fY)); 50 n.set(SkScalarHalf(3*o.fX -q.fX), SkScalarHalf(3*o.fY - q.fY)); 51 midPt(mid,m,n); 52 fPath.quadTo(mid,q); 53 c = q; 54 //compute the second half 55 m.set(SkScalarHalf(3*p.fX - c.fX), SkScalarHalf(3*p.fY - c.fY)); 56 n.set(SkScalarHalf(3*l.fX -p2.fX),SkScalarHalf(3*l.fY -p2.fY)); 57 midPt(mid,m,n); 58 fPath.quadTo(mid,p2); 59 } 60 #endif 61 62 63 static inline bool is_between(int c, int min, int max) 64 { 65 return (unsigned)(c - min) <= (unsigned)(max - min); 66 } 67 68 static inline bool is_ws(int c) 69 { 70 return is_between(c, 1, 32); 71 } 72 73 static inline bool is_digit(int c) 74 { 75 return is_between(c, '0', '9'); 76 } 77 78 static inline bool is_sep(int c) 79 { 80 return is_ws(c) || c == ','; 81 } 82 83 static const char* skip_ws(const char str[]) 84 { 85 SkASSERT(str); 86 while (is_ws(*str)) 87 str++; 88 return str; 89 } 90 91 static const char* skip_sep(const char str[]) 92 { 93 SkASSERT(str); 94 while (is_sep(*str)) 95 str++; 96 return str; 97 } 98 99 static const char* find_points(const char str[], SkPoint value[], int count, 100 bool isRelative, SkPoint* relative) 101 { 102 str = SkParse::FindScalars(str, &value[0].fX, count * 2); 103 if (isRelative) { 104 for (int index = 0; index < count; index++) { 105 value[index].fX += relative->fX; 106 value[index].fY += relative->fY; 107 } 108 } 109 return str; 110 } 111 112 static const char* find_scalar(const char str[], SkScalar* value, 113 bool isRelative, SkScalar relative) 114 { 115 str = SkParse::FindScalar(str, value); 116 if (isRelative) 117 *value += relative; 118 return str; 119 } 120 121 void SkDrawPath::parseSVG() { 122 fPath.reset(); 123 const char* data = d.c_str(); 124 SkPoint f = {0, 0}; 125 SkPoint c = {0, 0}; 126 SkPoint lastc = {0, 0}; 127 SkPoint points[3]; 128 char op = '\0'; 129 char previousOp = '\0'; 130 bool relative = false; 131 do { 132 data = skip_ws(data); 133 if (data[0] == '\0') 134 break; 135 char ch = data[0]; 136 if (is_digit(ch) || ch == '-' || ch == '+') { 137 if (op == '\0') 138 return; 139 } 140 else { 141 op = ch; 142 relative = false; 143 if (islower(op)) { 144 op = (char) toupper(op); 145 relative = true; 146 } 147 data++; 148 data = skip_sep(data); 149 } 150 switch (op) { 151 case 'M': 152 data = find_points(data, points, 1, relative, &c); 153 fPath.moveTo(points[0]); 154 op = 'L'; 155 c = points[0]; 156 break; 157 case 'L': 158 data = find_points(data, points, 1, relative, &c); 159 fPath.lineTo(points[0]); 160 c = points[0]; 161 break; 162 case 'H': { 163 SkScalar x; 164 data = find_scalar(data, &x, relative, c.fX); 165 fPath.lineTo(x, c.fY); 166 c.fX = x; 167 } 168 break; 169 case 'V': { 170 SkScalar y; 171 data = find_scalar(data, &y, relative, c.fY); 172 fPath.lineTo(c.fX, y); 173 c.fY = y; 174 } 175 break; 176 case 'C': 177 data = find_points(data, points, 3, relative, &c); 178 goto cubicCommon; 179 case 'S': 180 data = find_points(data, &points[1], 2, relative, &c); 181 points[0] = c; 182 if (previousOp == 'C' || previousOp == 'S') { 183 points[0].fX -= lastc.fX - c.fX; 184 points[0].fY -= lastc.fY - c.fY; 185 } 186 cubicCommon: 187 // if (data[0] == '\0') 188 // return; 189 #if QUADRATIC_APPROXIMATION 190 quadApprox(fPath, points[0], points[1], points[2]); 191 #else //this way just does a boring, slow old cubic 192 fPath.cubicTo(points[0], points[1], points[2]); 193 #endif 194 //if we are using the quadApprox, lastc is what it would have been if we had used 195 //cubicTo 196 lastc = points[1]; 197 c = points[2]; 198 break; 199 case 'Q': // Quadratic Bezier Curve 200 data = find_points(data, points, 2, relative, &c); 201 goto quadraticCommon; 202 case 'T': 203 data = find_points(data, &points[1], 1, relative, &c); 204 points[0] = points[1]; 205 if (previousOp == 'Q' || previousOp == 'T') { 206 points[0].fX = c.fX * 2 - lastc.fX; 207 points[0].fY = c.fY * 2 - lastc.fY; 208 } 209 quadraticCommon: 210 fPath.quadTo(points[0], points[1]); 211 lastc = points[0]; 212 c = points[1]; 213 break; 214 case 'Z': 215 fPath.close(); 216 #if 0 // !!! still a bug? 217 if (fPath.isEmpty() && (f.fX != 0 || f.fY != 0)) { 218 c.fX -= SkScalar.Epsilon; // !!! enough? 219 fPath.moveTo(c); 220 fPath.lineTo(f); 221 fPath.close(); 222 } 223 #endif 224 c = f; 225 op = '\0'; 226 break; 227 case '~': { 228 SkPoint args[2]; 229 data = find_points(data, args, 2, false, NULL); 230 fPath.moveTo(args[0].fX, args[0].fY); 231 fPath.lineTo(args[1].fX, args[1].fY); 232 } 233 break; 234 default: 235 SkASSERT(0); 236 return; 237 } 238 if (previousOp == 0) 239 f = c; 240 previousOp = op; 241 } while (data[0] > 0); 242 } 243 244