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 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 */ 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 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 */ 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 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 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 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 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