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