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