• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
find_points(const char str[],SkPoint value[],int count,bool isRelative,SkPoint * relative)62 static const char* find_points(const char str[], SkPoint value[], int count,
63                                bool isRelative, SkPoint* relative) {
64     str = SkParse::FindScalars(str, &value[0].fX, count * 2);
65     if (isRelative) {
66         for (int index = 0; index < count; index++) {
67             value[index].fX += relative->fX;
68             value[index].fY += relative->fY;
69         }
70     }
71     return str;
72 }
73 
find_scalar(const char str[],SkScalar * value,bool isRelative,SkScalar relative)74 static const char* find_scalar(const char str[], SkScalar* value,
75                                bool isRelative, SkScalar relative) {
76     str = SkParse::FindScalar(str, value);
77     if (!str) {
78         return nullptr;
79     }
80     if (isRelative) {
81         *value += relative;
82     }
83     str = skip_sep(str);
84     return str;
85 }
86 
87 // https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
88 //
89 // flag:
90 //    "0" | "1"
find_flag(const char str[],bool * value)91 static const char* find_flag(const char str[], bool* value) {
92     if (!str) {
93         return nullptr;
94     }
95     if (str[0] != '1' && str[0] != '0') {
96         return nullptr;
97     }
98     *value = str[0] != '0';
99     str = skip_sep(str + 1);
100     return str;
101 }
102 
FromSVGString(const char data[],SkPath * result)103 bool SkParsePath::FromSVGString(const char data[], SkPath* result) {
104     SkPath path;
105     SkPoint first = {0, 0};
106     SkPoint c = {0, 0};
107     SkPoint lastc = {0, 0};
108     SkPoint points[3];
109     char op = '\0';
110     char previousOp = '\0';
111     bool relative = false;
112     for (;;) {
113         if (!data) {
114             // Truncated data
115             return false;
116         }
117         data = skip_ws(data);
118         if (data[0] == '\0') {
119             break;
120         }
121         char ch = data[0];
122         if (is_digit(ch) || ch == '-' || ch == '+' || ch == '.') {
123             if (op == '\0' || op == 'Z') {
124                 return false;
125             }
126         } else if (is_sep(ch)) {
127             data = skip_sep(data);
128         } else {
129             op = ch;
130             relative = false;
131             if (is_lower(op)) {
132                 op = (char) to_upper(op);
133                 relative = true;
134             }
135             data++;
136             data = skip_sep(data);
137         }
138         switch (op) {
139             case 'M':
140                 data = find_points(data, points, 1, relative, &c);
141                 path.moveTo(points[0]);
142                 previousOp = '\0';
143                 op = 'L';
144                 c = points[0];
145                 break;
146             case 'L':
147                 data = find_points(data, points, 1, relative, &c);
148                 path.lineTo(points[0]);
149                 c = points[0];
150                 break;
151             case 'H': {
152                 SkScalar x;
153                 data = find_scalar(data, &x, relative, c.fX);
154                 path.lineTo(x, c.fY);
155                 c.fX = x;
156             } break;
157             case 'V': {
158                 SkScalar y;
159                 data = find_scalar(data, &y, relative, c.fY);
160                 path.lineTo(c.fX, y);
161                 c.fY = y;
162             } break;
163             case 'C':
164                 data = find_points(data, points, 3, relative, &c);
165                 goto cubicCommon;
166             case 'S':
167                 data = find_points(data, &points[1], 2, relative, &c);
168                 points[0] = c;
169                 if (previousOp == 'C' || previousOp == 'S') {
170                     points[0].fX -= lastc.fX - c.fX;
171                     points[0].fY -= lastc.fY - c.fY;
172                 }
173             cubicCommon:
174                 path.cubicTo(points[0], points[1], points[2]);
175                 lastc = points[1];
176                 c = points[2];
177                 break;
178             case 'Q':  // Quadratic Bezier Curve
179                 data = find_points(data, points, 2, relative, &c);
180                 goto quadraticCommon;
181             case 'T':
182                 data = find_points(data, &points[1], 1, relative, &c);
183                 points[0] = c;
184                 if (previousOp == 'Q' || previousOp == 'T') {
185                     points[0].fX -= lastc.fX - c.fX;
186                     points[0].fY -= lastc.fY - c.fY;
187                 }
188             quadraticCommon:
189                 path.quadTo(points[0], points[1]);
190                 lastc = points[0];
191                 c = points[1];
192                 break;
193             case 'A': {
194                 SkPoint radii;
195                 SkScalar angle;
196                 bool largeArc, sweep;
197                 if ((data = find_points(data, &radii, 1, false, nullptr))
198                         && (data = skip_sep(data))
199                         && (data = find_scalar(data, &angle, false, 0))
200                         && (data = skip_sep(data))
201                         && (data = find_flag(data, &largeArc))
202                         && (data = skip_sep(data))
203                         && (data = find_flag(data, &sweep))
204                         && (data = skip_sep(data))
205                         && (data = find_points(data, &points[0], 1, relative, &c))) {
206                     path.arcTo(radii, angle, (SkPath::ArcSize) largeArc,
207                             (SkPathDirection) !sweep, points[0]);
208                     path.getLastPt(&c);
209                 }
210                 } break;
211             case 'Z':
212                 path.close();
213                 c = first;
214                 break;
215             case '~': {
216                 SkPoint args[2];
217                 data = find_points(data, args, 2, false, nullptr);
218                 path.moveTo(args[0].fX, args[0].fY);
219                 path.lineTo(args[1].fX, args[1].fY);
220             } break;
221             default:
222                 return false;
223         }
224         if (previousOp == 0) {
225             first = c;
226         }
227         previousOp = op;
228     }
229     // we're good, go ahead and swap in the result
230     result->swap(path);
231     return true;
232 }
233 
234 ///////////////////////////////////////////////////////////////////////////////
235 
write_scalar(SkWStream * stream,SkScalar value)236 static void write_scalar(SkWStream* stream, SkScalar value) {
237     char buffer[64];
238     int len = snprintf(buffer, sizeof(buffer), "%g", value);
239     char* stop = buffer + len;
240     stream->write(buffer, stop - buffer);
241 }
242 
ToSVGString(const SkPath & path,PathEncoding encoding)243 SkString SkParsePath::ToSVGString(const SkPath& path, PathEncoding encoding) {
244     SkDynamicMemoryWStream  stream;
245 
246     SkPoint current_point{0,0};
247     const auto rel_selector = encoding == PathEncoding::Relative;
248 
249     const auto append_command = [&](char cmd, const SkPoint pts[], size_t count) {
250         // Use lower case cmds for relative encoding.
251         cmd += 32 * rel_selector;
252         stream.write(&cmd, 1);
253 
254         for (size_t i = 0; i < count; ++i) {
255             const auto pt = pts[i] - current_point;
256             if (i > 0) {
257                 stream.write(" ", 1);
258             }
259             write_scalar(&stream, pt.fX);
260             stream.write(" ", 1);
261             write_scalar(&stream, pt.fY);
262         }
263 
264         SkASSERT(count > 0);
265         // For relative encoding, track the current point (otherwise == origin).
266         current_point = pts[count - 1] * rel_selector;
267     };
268 
269     SkPath::Iter    iter(path, false);
270     SkPoint         pts[4];
271 
272     for (;;) {
273         switch (iter.next(pts)) {
274             case SkPath::kConic_Verb: {
275                 const SkScalar tol = SK_Scalar1 / 1024; // how close to a quad
276                 SkAutoConicToQuads quadder;
277                 const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), tol);
278                 for (int i = 0; i < quadder.countQuads(); ++i) {
279                     append_command('Q', &quadPts[i*2 + 1], 2);
280                 }
281             } break;
282            case SkPath::kMove_Verb:
283                 append_command('M', &pts[0], 1);
284                 break;
285             case SkPath::kLine_Verb:
286                 append_command('L', &pts[1], 1);
287                 break;
288             case SkPath::kQuad_Verb:
289                 append_command('Q', &pts[1], 2);
290                 break;
291             case SkPath::kCubic_Verb:
292                 append_command('C', &pts[1], 3);
293                 break;
294             case SkPath::kClose_Verb:
295                 stream.write("Z", 1);
296                 break;
297             case SkPath::kDone_Verb: {
298                 SkString str;
299                 str.resize(stream.bytesWritten());
300                 stream.copyTo(str.data());
301                 return str;
302             }
303         }
304     }
305 }
306