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