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) + " floats. However, " + 214 std::to_string(points) + " float(s) are found. "; 215 } 216 217 void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, 218 const char* pathStr, size_t strLen) { 219 if (pathStr == NULL) { 220 result->failureOccurred = true; 221 result->failureMessage = "Path string cannot be NULL."; 222 return; 223 } 224 225 size_t start = 0; 226 // Skip leading spaces. 227 while (isspace(pathStr[start]) && start < strLen) { 228 start++; 229 } 230 if (start == strLen) { 231 result->failureOccurred = true; 232 result->failureMessage = "Path string cannot be empty."; 233 return; 234 } 235 size_t end = start + 1; 236 237 while (end < strLen) { 238 end = nextStart(pathStr, strLen, end); 239 std::vector<float> points; 240 getFloats(&points, result, pathStr, start, end); 241 validateVerbAndPoints(pathStr[start], points.size(), result); 242 if (result->failureOccurred) { 243 // If either verb or points is not valid, return immediately. 244 result->failureMessage += "Failure occurred at position " + std::to_string(start) + 245 " of path: " + pathStr; 246 return; 247 } 248 data->verbs.push_back(pathStr[start]); 249 data->verbSizes.push_back(points.size()); 250 data->points.insert(data->points.end(), points.begin(), points.end()); 251 start = end; 252 end++; 253 } 254 255 if ((end - start) == 1 && start < strLen) { 256 validateVerbAndPoints(pathStr[start], 0, result); 257 if (result->failureOccurred) { 258 // If either verb or points is not valid, return immediately. 259 result->failureMessage += "Failure occurred at position " + std::to_string(start) + 260 " of path: " + pathStr; 261 return; 262 } 263 data->verbs.push_back(pathStr[start]); 264 data->verbSizes.push_back(0); 265 } 266 } 267 268 void PathParser::dump(const PathData& data) { 269 // Print out the path data. 270 size_t start = 0; 271 for (size_t i = 0; i < data.verbs.size(); i++) { 272 std::ostringstream os; 273 os << data.verbs[i]; 274 os << ", verb size: " << data.verbSizes[i]; 275 for (size_t j = 0; j < data.verbSizes[i]; j++) { 276 os << " " << data.points[start + j]; 277 } 278 start += data.verbSizes[i]; 279 ALOGD("%s", os.str().c_str()); 280 } 281 282 std::ostringstream os; 283 for (size_t i = 0; i < data.points.size(); i++) { 284 os << data.points[i] << ", "; 285 } 286 ALOGD("points are : %s", os.str().c_str()); 287 } 288 289 void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, 290 size_t strLen) { 291 PathData pathData; 292 getPathDataFromAsciiString(&pathData, result, pathStr, strLen); 293 if (result->failureOccurred) { 294 return; 295 } 296 // Check if there is valid data coming out of parsing the string. 297 if (pathData.verbs.size() == 0) { 298 result->failureOccurred = true; 299 result->failureMessage = "No verbs found in the string for pathData: "; 300 result->failureMessage += pathStr; 301 return; 302 } 303 VectorDrawableUtils::verbsToPath(skPath, pathData); 304 return; 305 } 306 307 } // namespace uirenderer 308 } // namespace android 309