1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "PathParser.h"
18
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <utils/Log.h>
22 #include <sstream>
23 #include <string>
24 #include <vector>
25
26 namespace android {
27 namespace uirenderer {
28
nextStart(const char * s,size_t length,size_t startIndex)29 static size_t nextStart(const char* s, size_t length, size_t startIndex) {
30 size_t index = startIndex;
31 while (index < length) {
32 char c = s[index];
33 // Note that 'e' or 'E' are not valid path commands, but could be
34 // used for floating point numbers' scientific notation.
35 // Therefore, when searching for next command, we should ignore 'e'
36 // and 'E'.
37 if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' &&
38 c != 'E') {
39 return index;
40 }
41 index++;
42 }
43 return index;
44 }
45
46 /**
47 * Calculate the position of the next comma or space or negative sign
48 * @param s the string to search
49 * @param start the position to start searching
50 * @param result the result of the extraction, including the position of the
51 * the starting position of next number, whether it is ending with a '-'.
52 */
extract(int * outEndPosition,bool * outEndWithNegOrDot,const char * s,int start,int end)53 static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start,
54 int end) {
55 // Now looking for ' ', ',', '.' or '-' from the start.
56 int currentIndex = start;
57 bool foundSeparator = false;
58 *outEndWithNegOrDot = false;
59 bool secondDot = false;
60 bool isExponential = false;
61 for (; currentIndex < end; currentIndex++) {
62 bool isPrevExponential = isExponential;
63 isExponential = false;
64 char currentChar = s[currentIndex];
65 switch (currentChar) {
66 case ' ':
67 case ',':
68 foundSeparator = true;
69 break;
70 case '-':
71 // The negative sign following a 'e' or 'E' is not a separator.
72 if (currentIndex != start && !isPrevExponential) {
73 foundSeparator = true;
74 *outEndWithNegOrDot = true;
75 }
76 break;
77 case '.':
78 if (!secondDot) {
79 secondDot = true;
80 } else {
81 // This is the second dot, and it is considered as a separator.
82 foundSeparator = true;
83 *outEndWithNegOrDot = true;
84 }
85 break;
86 case 'e':
87 case 'E':
88 isExponential = true;
89 break;
90 }
91 if (foundSeparator) {
92 break;
93 }
94 }
95 // In the case where nothing is found, we put the end position to the end of
96 // our extract range. Otherwise, end position will be where separator is found.
97 *outEndPosition = currentIndex;
98 }
99
parseFloat(PathParser::ParseResult * result,const char * startPtr,size_t expectedLength)100 static float parseFloat(PathParser::ParseResult* result, const char* startPtr,
101 size_t expectedLength) {
102 char* endPtr = NULL;
103 float currentValue = strtof(startPtr, &endPtr);
104 if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
105 result->failureOccurred = true;
106 result->failureMessage = "Float out of range: ";
107 result->failureMessage.append(startPtr, expectedLength);
108 }
109 if (currentValue == 0 && endPtr == startPtr) {
110 // No conversion is done.
111 result->failureOccurred = true;
112 result->failureMessage = "Float format error when parsing: ";
113 result->failureMessage.append(startPtr, expectedLength);
114 }
115 return currentValue;
116 }
117
118 /**
119 * Parse the floats in the string.
120 *
121 * @param s the string containing a command and list of floats
122 * @return true on success
123 */
getFloats(std::vector<float> * outPoints,PathParser::ParseResult * result,const char * pathStr,int start,int end)124 static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
125 const char* pathStr, int start, int end) {
126 if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
127 return;
128 }
129 int startPosition = start + 1;
130 int endPosition = start;
131
132 // The startPosition should always be the first character of the
133 // current number, and endPosition is the character after the current
134 // number.
135 while (startPosition < end) {
136 bool endWithNegOrDot;
137 extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
138
139 if (startPosition < endPosition) {
140 float currentValue = parseFloat(result, &pathStr[startPosition], end - startPosition);
141 if (result->failureOccurred) {
142 return;
143 }
144 outPoints->push_back(currentValue);
145 }
146
147 if (endWithNegOrDot) {
148 // Keep the '-' or '.' sign with next number.
149 startPosition = endPosition;
150 } else {
151 startPosition = endPosition + 1;
152 }
153 }
154 return;
155 }
156
validateVerbAndPoints(char verb,size_t points,PathParser::ParseResult * result)157 void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::ParseResult* result) {
158 size_t numberOfPointsExpected = -1;
159 switch (verb) {
160 case 'z':
161 case 'Z':
162 numberOfPointsExpected = 0;
163 break;
164 case 'm':
165 case 'l':
166 case 't':
167 case 'M':
168 case 'L':
169 case 'T':
170 numberOfPointsExpected = 2;
171 break;
172 case 'h':
173 case 'v':
174 case 'H':
175 case 'V':
176 numberOfPointsExpected = 1;
177 break;
178 case 'c':
179 case 'C':
180 numberOfPointsExpected = 6;
181 break;
182 case 's':
183 case 'q':
184 case 'S':
185 case 'Q':
186 numberOfPointsExpected = 4;
187 break;
188 case 'a':
189 case 'A':
190 numberOfPointsExpected = 7;
191 break;
192 default:
193 result->failureOccurred = true;
194 result->failureMessage += verb;
195 result->failureMessage += " is not a valid verb. ";
196 return;
197 }
198 if (numberOfPointsExpected == 0 && points == 0) {
199 return;
200 }
201 if (numberOfPointsExpected > 0 && points % numberOfPointsExpected == 0) {
202 return;
203 }
204
205 result->failureOccurred = true;
206 result->failureMessage += verb;
207 result->failureMessage += " needs to be followed by ";
208 if (numberOfPointsExpected > 0) {
209 result->failureMessage += "a multiple of ";
210 }
211 result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " +
212 std::to_string(points) + " float(s) are found. ";
213 }
214
getPathDataFromAsciiString(PathData * data,ParseResult * result,const char * pathStr,size_t strLen)215 void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
216 const char* pathStr, size_t strLen) {
217 if (pathStr == NULL) {
218 result->failureOccurred = true;
219 result->failureMessage = "Path string cannot be NULL.";
220 return;
221 }
222
223 size_t start = 0;
224 // Skip leading spaces.
225 while (isspace(pathStr[start]) && start < strLen) {
226 start++;
227 }
228 if (start == strLen) {
229 result->failureOccurred = true;
230 result->failureMessage = "Path string cannot be empty.";
231 return;
232 }
233 size_t end = start + 1;
234
235 while (end < strLen) {
236 end = nextStart(pathStr, strLen, end);
237 std::vector<float> points;
238 getFloats(&points, result, pathStr, start, end);
239 validateVerbAndPoints(pathStr[start], points.size(), result);
240 if (result->failureOccurred) {
241 // If either verb or points is not valid, return immediately.
242 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
243 " of path: " + pathStr;
244 return;
245 }
246 data->verbs.push_back(pathStr[start]);
247 data->verbSizes.push_back(points.size());
248 data->points.insert(data->points.end(), points.begin(), points.end());
249 start = end;
250 end++;
251 }
252
253 if ((end - start) == 1 && start < strLen) {
254 validateVerbAndPoints(pathStr[start], 0, result);
255 if (result->failureOccurred) {
256 // If either verb or points is not valid, return immediately.
257 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
258 " of path: " + pathStr;
259 return;
260 }
261 data->verbs.push_back(pathStr[start]);
262 data->verbSizes.push_back(0);
263 }
264 }
265
dump(const PathData & data)266 void PathParser::dump(const PathData& data) {
267 // Print out the path data.
268 size_t start = 0;
269 for (size_t i = 0; i < data.verbs.size(); i++) {
270 std::ostringstream os;
271 os << data.verbs[i];
272 os << ", verb size: " << data.verbSizes[i];
273 for (size_t j = 0; j < data.verbSizes[i]; j++) {
274 os << " " << data.points[start + j];
275 }
276 start += data.verbSizes[i];
277 ALOGD("%s", os.str().c_str());
278 }
279
280 std::ostringstream os;
281 for (size_t i = 0; i < data.points.size(); i++) {
282 os << data.points[i] << ", ";
283 }
284 ALOGD("points are : %s", os.str().c_str());
285 }
286
parseAsciiStringForSkPath(SkPath * skPath,ParseResult * result,const char * pathStr,size_t strLen)287 void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr,
288 size_t strLen) {
289 PathData pathData;
290 getPathDataFromAsciiString(&pathData, result, pathStr, strLen);
291 if (result->failureOccurred) {
292 return;
293 }
294 // Check if there is valid data coming out of parsing the string.
295 if (pathData.verbs.size() == 0) {
296 result->failureOccurred = true;
297 result->failureMessage = "No verbs found in the string for pathData: ";
298 result->failureMessage += pathStr;
299 return;
300 }
301 VectorDrawableUtils::verbsToPath(skPath, pathData);
302 return;
303 }
304
305 } // namespace uirenderer
306 } // namespace android
307