1 2/* 3 * Copyright 2011 Google Inc. 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#include "SkParse.h" 9#include "SkParsePath.h" 10 11static inline bool is_between(int c, int min, int max) { 12 return (unsigned)(c - min) <= (unsigned)(max - min); 13} 14 15static inline bool is_ws(int c) { 16 return is_between(c, 1, 32); 17} 18 19static inline bool is_digit(int c) { 20 return is_between(c, '0', '9'); 21} 22 23static inline bool is_sep(int c) { 24 return is_ws(c) || c == ','; 25} 26 27static inline bool is_lower(int c) { 28 return is_between(c, 'a', 'z'); 29} 30 31static inline int to_upper(int c) { 32 return c - 'a' + 'A'; 33} 34 35static const char* skip_ws(const char str[]) { 36 SkASSERT(str); 37 while (is_ws(*str)) 38 str++; 39 return str; 40} 41 42static const char* skip_sep(const char str[]) { 43 SkASSERT(str); 44 while (is_sep(*str)) 45 str++; 46 return str; 47} 48 49static const char* find_points(const char str[], SkPoint value[], int count, 50 bool isRelative, SkPoint* relative) { 51 str = SkParse::FindScalars(str, &value[0].fX, count * 2); 52 if (isRelative) { 53 for (int index = 0; index < count; index++) { 54 value[index].fX += relative->fX; 55 value[index].fY += relative->fY; 56 } 57 } 58 return str; 59} 60 61static const char* find_scalar(const char str[], SkScalar* value, 62 bool isRelative, SkScalar relative) { 63 str = SkParse::FindScalar(str, value); 64 if (isRelative) { 65 *value += relative; 66 } 67 return str; 68} 69 70bool SkParsePath::FromSVGString(const char data[], SkPath* result) { 71 SkPath path; 72 SkPoint f = {0, 0}; 73 SkPoint c = {0, 0}; 74 SkPoint lastc = {0, 0}; 75 SkPoint points[3]; 76 char op = '\0'; 77 char previousOp = '\0'; 78 bool relative = false; 79 for (;;) { 80 data = skip_ws(data); 81 if (data[0] == '\0') { 82 break; 83 } 84 char ch = data[0]; 85 if (is_digit(ch) || ch == '-' || ch == '+') { 86 if (op == '\0') { 87 return false; 88 } 89 } else if (is_sep(ch)) { 90 data = skip_sep(data); 91 } else { 92 op = ch; 93 relative = false; 94 if (is_lower(op)) { 95 op = (char) to_upper(op); 96 relative = true; 97 } 98 data++; 99 data = skip_sep(data); 100 } 101 switch (op) { 102 case 'M': 103 data = find_points(data, points, 1, relative, &c); 104 path.moveTo(points[0]); 105 op = 'L'; 106 c = points[0]; 107 break; 108 case 'L': 109 data = find_points(data, points, 1, relative, &c); 110 path.lineTo(points[0]); 111 c = points[0]; 112 break; 113 case 'H': { 114 SkScalar x; 115 data = find_scalar(data, &x, relative, c.fX); 116 path.lineTo(x, c.fY); 117 c.fX = x; 118 } break; 119 case 'V': { 120 SkScalar y; 121 data = find_scalar(data, &y, relative, c.fY); 122 path.lineTo(c.fX, y); 123 c.fY = y; 124 } break; 125 case 'C': 126 data = find_points(data, points, 3, relative, &c); 127 goto cubicCommon; 128 case 'S': 129 data = find_points(data, &points[1], 2, relative, &c); 130 points[0] = c; 131 if (previousOp == 'C' || previousOp == 'S') { 132 points[0].fX -= lastc.fX - c.fX; 133 points[0].fY -= lastc.fY - c.fY; 134 } 135 cubicCommon: 136 path.cubicTo(points[0], points[1], points[2]); 137 lastc = points[1]; 138 c = points[2]; 139 break; 140 case 'Q': // Quadratic Bezier Curve 141 data = find_points(data, points, 2, relative, &c); 142 goto quadraticCommon; 143 case 'T': 144 data = find_points(data, &points[1], 1, relative, &c); 145 points[0] = points[1]; 146 if (previousOp == 'Q' || previousOp == 'T') { 147 points[0].fX = c.fX * 2 - lastc.fX; 148 points[0].fY = c.fY * 2 - lastc.fY; 149 } 150 quadraticCommon: 151 path.quadTo(points[0], points[1]); 152 lastc = points[0]; 153 c = points[1]; 154 break; 155 case 'Z': 156 path.close(); 157#if 0 // !!! still a bug? 158 if (fPath.isEmpty() && (f.fX != 0 || f.fY != 0)) { 159 c.fX -= SkScalar.Epsilon; // !!! enough? 160 fPath.moveTo(c); 161 fPath.lineTo(f); 162 fPath.close(); 163 } 164#endif 165 c = f; 166 op = '\0'; 167 break; 168 case '~': { 169 SkPoint args[2]; 170 data = find_points(data, args, 2, false, NULL); 171 path.moveTo(args[0].fX, args[0].fY); 172 path.lineTo(args[1].fX, args[1].fY); 173 } break; 174 default: 175 return false; 176 } 177 if (previousOp == 0) { 178 f = c; 179 } 180 previousOp = op; 181 } 182 // we're good, go ahead and swap in the result 183 result->swap(path); 184 return true; 185} 186 187/////////////////////////////////////////////////////////////////////////////// 188 189#include "SkGeometry.h" 190#include "SkString.h" 191#include "SkStream.h" 192 193static void write_scalar(SkWStream* stream, SkScalar value) { 194 char buffer[64]; 195#ifdef SK_BUILD_FOR_WIN32 196 int len = _snprintf(buffer, sizeof(buffer), "%g", value); 197#else 198 int len = snprintf(buffer, sizeof(buffer), "%g", value); 199#endif 200 char* stop = buffer + len; 201 stream->write(buffer, stop - buffer); 202} 203 204static void append_scalars(SkWStream* stream, char verb, const SkScalar data[], 205 int count) { 206 stream->write(&verb, 1); 207 write_scalar(stream, data[0]); 208 for (int i = 1; i < count; i++) { 209 stream->write(" ", 1); 210 write_scalar(stream, data[i]); 211 } 212} 213 214void SkParsePath::ToSVGString(const SkPath& path, SkString* str) { 215 SkDynamicMemoryWStream stream; 216 217 SkPath::Iter iter(path, false); 218 SkPoint pts[4]; 219 220 for (;;) { 221 switch (iter.next(pts, false)) { 222 case SkPath::kConic_Verb: { 223 const SkScalar tol = SK_Scalar1 / 1024; // how close to a quad 224 SkAutoConicToQuads quadder; 225 const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), tol); 226 for (int i = 0; i < quadder.countQuads(); ++i) { 227 append_scalars(&stream, 'Q', &quadPts[i*2 + 1].fX, 4); 228 } 229 } break; 230 case SkPath::kMove_Verb: 231 append_scalars(&stream, 'M', &pts[0].fX, 2); 232 break; 233 case SkPath::kLine_Verb: 234 append_scalars(&stream, 'L', &pts[1].fX, 2); 235 break; 236 case SkPath::kQuad_Verb: 237 append_scalars(&stream, 'Q', &pts[1].fX, 4); 238 break; 239 case SkPath::kCubic_Verb: 240 append_scalars(&stream, 'C', &pts[1].fX, 6); 241 break; 242 case SkPath::kClose_Verb: 243 stream.write("Z", 1); 244 break; 245 case SkPath::kDone_Verb: 246 str->resize(stream.getOffset()); 247 stream.copyTo(str->writable_str()); 248 return; 249 } 250 } 251} 252