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