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
8 #include "include/core/SkPath.h"
9 #include "include/core/SkPoint.h"
10 #include "include/core/SkScalar.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkString.h"
13 #include "include/core/SkTypes.h"
14 #include "include/utils/SkParse.h"
15 #include "include/utils/SkParsePath.h"
16 #include "src/core/SkGeometry.h"
17
18 #include <cstdio>
19
20 enum class SkPathDirection;
21
is_between(int c,int min,int max)22 static inline bool is_between(int c, int min, int max) {
23 return (unsigned)(c - min) <= (unsigned)(max - min);
24 }
25
is_ws(int c)26 static inline bool is_ws(int c) {
27 return is_between(c, 1, 32);
28 }
29
is_digit(int c)30 static inline bool is_digit(int c) {
31 return is_between(c, '0', '9');
32 }
33
is_sep(int c)34 static inline bool is_sep(int c) {
35 return is_ws(c) || c == ',';
36 }
37
is_lower(int c)38 static inline bool is_lower(int c) {
39 return is_between(c, 'a', 'z');
40 }
41
to_upper(int c)42 static inline int to_upper(int c) {
43 return c - 'a' + 'A';
44 }
45
skip_ws(const char str[])46 static const char* skip_ws(const char str[]) {
47 SkASSERT(str);
48 while (is_ws(*str))
49 str++;
50 return str;
51 }
52
skip_sep(const char str[])53 static const char* skip_sep(const char str[]) {
54 if (!str) {
55 return nullptr;
56 }
57 while (is_sep(*str))
58 str++;
59 return str;
60 }
61
62 // If unable to read count points from str into value, this will return nullptr
63 // to signal the failure. Otherwise, it will return the next offset to read from.
find_points(const char str[],SkPoint value[],int count,bool isRelative,SkPoint * relative)64 static const char* find_points(const char str[], SkPoint value[], int count,
65 bool isRelative, SkPoint* relative) {
66 str = SkParse::FindScalars(str, &value[0].fX, count * 2);
67 if (isRelative) {
68 for (int index = 0; index < count; index++) {
69 value[index].fX += relative->fX;
70 value[index].fY += relative->fY;
71 }
72 }
73 return str;
74 }
75
76 // If unable to read a scalar from str into value, this will return nullptr
77 // to signal the failure. Otherwise, it will return the next offset to read from.
find_scalar(const char str[],SkScalar * value,bool isRelative,SkScalar relative)78 static const char* find_scalar(const char str[], SkScalar* value,
79 bool isRelative, SkScalar relative) {
80 str = SkParse::FindScalar(str, value);
81 if (!str) {
82 return nullptr;
83 }
84 if (isRelative) {
85 *value += relative;
86 }
87 str = skip_sep(str);
88 return str;
89 }
90
91 // https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
92 //
93 // flag:
94 // "0" | "1"
find_flag(const char str[],bool * value)95 static const char* find_flag(const char str[], bool* value) {
96 if (!str) {
97 return nullptr;
98 }
99 if (str[0] != '1' && str[0] != '0') {
100 return nullptr;
101 }
102 *value = str[0] != '0';
103 str = skip_sep(str + 1);
104 return str;
105 }
106
FromSVGString(const char data[],SkPath * result)107 bool SkParsePath::FromSVGString(const char data[], SkPath* result) {
108 // We will write all data to this local path and only write it
109 // to result if the whole parsing succeeds.
110 SkPath path;
111 SkPoint first = {0, 0};
112 SkPoint c = {0, 0};
113 SkPoint lastc = {0, 0};
114 // We will use find_points and find_scalar to read into these.
115 // There might not be enough data to fill them, so to avoid
116 // MSAN warnings about using uninitialized bytes, we initialize
117 // them there.
118 SkPoint points[3] = {};
119 SkScalar scratch = 0;
120 char op = '\0';
121 char previousOp = '\0';
122 bool relative = false;
123 for (;;) {
124 if (!data) {
125 // Truncated data
126 return false;
127 }
128 data = skip_ws(data);
129 if (data[0] == '\0') {
130 break;
131 }
132 char ch = data[0];
133 if (is_digit(ch) || ch == '-' || ch == '+' || ch == '.') {
134 if (op == '\0' || op == 'Z') {
135 return false;
136 }
137 } else if (is_sep(ch)) {
138 data = skip_sep(data);
139 } else {
140 op = ch;
141 relative = false;
142 if (is_lower(op)) {
143 op = (char) to_upper(op);
144 relative = true;
145 }
146 data++;
147 data = skip_sep(data);
148 }
149 switch (op) {
150 case 'M': // Move
151 data = find_points(data, points, 1, relative, &c);
152 // find_points might have failed, so this might be the
153 // previous point. However, data will be set to nullptr
154 // if it failed, so we will check this at the top of the loop.
155 path.moveTo(points[0]);
156 previousOp = '\0';
157 op = 'L';
158 c = points[0];
159 break;
160 case 'L': // Line
161 data = find_points(data, points, 1, relative, &c);
162 path.lineTo(points[0]);
163 c = points[0];
164 break;
165 case 'H': // Horizontal Line
166 data = find_scalar(data, &scratch, relative, c.fX);
167 // Similarly, if there wasn't a scalar to read, data will
168 // be set to nullptr and this lineTo is bogus but will
169 // be ultimately ignored when the next time through the loop
170 // detects that and bails out.
171 path.lineTo(scratch, c.fY);
172 c.fX = scratch;
173 break;
174 case 'V': // Vertical Line
175 data = find_scalar(data, &scratch, relative, c.fY);
176 path.lineTo(c.fX, scratch);
177 c.fY = scratch;
178 break;
179 case 'C': // Cubic Bezier Curve
180 data = find_points(data, points, 3, relative, &c);
181 goto cubicCommon;
182 case 'S': // Continued "Smooth" Cubic Bezier Curve
183 data = find_points(data, &points[1], 2, relative, &c);
184 points[0] = c;
185 if (previousOp == 'C' || previousOp == 'S') {
186 points[0].fX -= lastc.fX - c.fX;
187 points[0].fY -= lastc.fY - c.fY;
188 }
189 cubicCommon:
190 path.cubicTo(points[0], points[1], points[2]);
191 lastc = points[1];
192 c = points[2];
193 break;
194 case 'Q': // Quadratic Bezier Curve
195 data = find_points(data, points, 2, relative, &c);
196 goto quadraticCommon;
197 case 'T': // Continued Quadratic Bezier Curve
198 data = find_points(data, &points[1], 1, relative, &c);
199 points[0] = c;
200 if (previousOp == 'Q' || previousOp == 'T') {
201 points[0].fX -= lastc.fX - c.fX;
202 points[0].fY -= lastc.fY - c.fY;
203 }
204 quadraticCommon:
205 path.quadTo(points[0], points[1]);
206 lastc = points[0];
207 c = points[1];
208 break;
209 case 'A': { // Arc (Elliptical)
210 SkPoint radii;
211 SkScalar angle;
212 bool largeArc, sweep;
213 if ((data = find_points(data, &radii, 1, false, nullptr))
214 && (data = skip_sep(data))
215 && (data = find_scalar(data, &angle, false, 0))
216 && (data = skip_sep(data))
217 && (data = find_flag(data, &largeArc))
218 && (data = skip_sep(data))
219 && (data = find_flag(data, &sweep))
220 && (data = skip_sep(data))
221 && (data = find_points(data, &points[0], 1, relative, &c))) {
222 path.arcTo(radii, angle, (SkPath::ArcSize) largeArc,
223 (SkPathDirection) !sweep, points[0]);
224 path.getLastPt(&c);
225 }
226 } break;
227 case 'Z': // Close Path
228 path.close();
229 c = first;
230 break;
231 default:
232 return false;
233 }
234 if (previousOp == 0) {
235 first = c;
236 }
237 previousOp = op;
238 }
239 // we're good, go ahead and swap in the result
240 result->swap(path);
241 return true;
242 }
243
244 ///////////////////////////////////////////////////////////////////////////////
245
ToSVGString(const SkPath & path,PathEncoding encoding)246 SkString SkParsePath::ToSVGString(const SkPath& path, PathEncoding encoding) {
247 SkDynamicMemoryWStream stream;
248
249 SkPoint current_point{0,0};
250 const auto rel_selector = encoding == PathEncoding::Relative;
251
252 const auto append_command = [&](char cmd, const SkPoint pts[], size_t count) {
253 // Use lower case cmds for relative encoding.
254 cmd += 32 * rel_selector;
255 stream.write(&cmd, 1);
256
257 for (size_t i = 0; i < count; ++i) {
258 const auto pt = pts[i] - current_point;
259 if (i > 0) {
260 stream.write(" ", 1);
261 }
262 stream.writeScalarAsText(pt.fX);
263 stream.write(" ", 1);
264 stream.writeScalarAsText(pt.fY);
265 }
266
267 SkASSERT(count > 0);
268 // For relative encoding, track the current point (otherwise == origin).
269 current_point = pts[count - 1] * rel_selector;
270 };
271
272 SkPath::Iter iter(path, false);
273 SkPoint pts[4];
274
275 for (;;) {
276 switch (iter.next(pts)) {
277 case SkPath::kConic_Verb: {
278 const SkScalar tol = SK_Scalar1 / 1024; // how close to a quad
279 SkAutoConicToQuads quadder;
280 const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), tol);
281 for (int i = 0; i < quadder.countQuads(); ++i) {
282 append_command('Q', &quadPts[i*2 + 1], 2);
283 }
284 } break;
285 case SkPath::kMove_Verb:
286 append_command('M', &pts[0], 1);
287 break;
288 case SkPath::kLine_Verb:
289 append_command('L', &pts[1], 1);
290 break;
291 case SkPath::kQuad_Verb:
292 append_command('Q', &pts[1], 2);
293 break;
294 case SkPath::kCubic_Verb:
295 append_command('C', &pts[1], 3);
296 break;
297 case SkPath::kClose_Verb:
298 stream.write("Z", 1);
299 break;
300 case SkPath::kDone_Verb: {
301 SkString str;
302 str.resize(stream.bytesWritten());
303 stream.copyTo(str.data());
304 return str;
305 }
306 }
307 }
308 }
309