xref: /aosp_15_r20/external/skia/src/utils/SkParsePath.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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