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 <utils/Log.h> 23 #include <sstream> 24 #include <stdlib.h> 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)) 40 && c != 'e' && 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, int end) { 56 // Now looking for ' ', ',', '.' or '-' from the start. 57 int currentIndex = start; 58 bool foundSeparator = false; 59 *outEndWithNegOrDot = false; 60 bool secondDot = false; 61 bool isExponential = false; 62 for (; currentIndex < end; currentIndex++) { 63 bool isPrevExponential = isExponential; 64 isExponential = false; 65 char currentChar = s[currentIndex]; 66 switch (currentChar) { 67 case ' ': 68 case ',': 69 foundSeparator = true; 70 break; 71 case '-': 72 // The negative sign following a 'e' or 'E' is not a separator. 73 if (currentIndex != start && !isPrevExponential) { 74 foundSeparator = true; 75 *outEndWithNegOrDot = true; 76 } 77 break; 78 case '.': 79 if (!secondDot) { 80 secondDot = true; 81 } else { 82 // This is the second dot, and it is considered as a separator. 83 foundSeparator = true; 84 *outEndWithNegOrDot = true; 85 } 86 break; 87 case 'e': 88 case 'E': 89 isExponential = true; 90 break; 91 } 92 if (foundSeparator) { 93 break; 94 } 95 } 96 // In the case where nothing is found, we put the end position to the end of 97 // our extract range. Otherwise, end position will be where separator is found. 98 *outEndPosition = currentIndex; 99 } 100 101 static float parseFloat(PathParser::ParseResult* result, const char* startPtr, 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 */ 124 static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result, 125 const char* pathStr, int start, int end) { 126 127 if (pathStr[start] == 'z' || pathStr[start] == 'Z') { 128 return; 129 } 130 int startPosition = start + 1; 131 int endPosition = start; 132 133 // The startPosition should always be the first character of the 134 // current number, and endPosition is the character after the current 135 // number. 136 while (startPosition < end) { 137 bool endWithNegOrDot; 138 extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end); 139 140 if (startPosition < endPosition) { 141 float currentValue = parseFloat(result, &pathStr[startPosition], 142 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 bool PathParser::isVerbValid(char verb) { 160 verb = tolower(verb); 161 return verb == 'a' || verb == 'c' || verb == 'h' || verb == 'l' || verb == 'm' || verb == 'q' 162 || verb == 's' || verb == 't' || verb == 'v' || verb == 'z'; 163 } 164 165 void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, 166 const char* pathStr, size_t strLen) { 167 if (pathStr == NULL) { 168 result->failureOccurred = true; 169 result->failureMessage = "Path string cannot be NULL."; 170 return; 171 } 172 173 size_t start = 0; 174 // Skip leading spaces. 175 while (isspace(pathStr[start]) && start < strLen) { 176 start++; 177 } 178 if (start == strLen) { 179 result->failureOccurred = true; 180 result->failureMessage = "Path string cannot be empty."; 181 return; 182 } 183 size_t end = start + 1; 184 185 while (end < strLen) { 186 end = nextStart(pathStr, strLen, end); 187 std::vector<float> points; 188 getFloats(&points, result, pathStr, start, end); 189 if (!isVerbValid(pathStr[start])) { 190 result->failureOccurred = true; 191 result->failureMessage = "Invalid pathData. Failure occurred at position " 192 + std::to_string(start) + " of path: " + pathStr; 193 } 194 // If either verb or points is not valid, return immediately. 195 if (result->failureOccurred) { 196 return; 197 } 198 data->verbs.push_back(pathStr[start]); 199 data->verbSizes.push_back(points.size()); 200 data->points.insert(data->points.end(), points.begin(), points.end()); 201 start = end; 202 end++; 203 } 204 205 if ((end - start) == 1 && start < strLen) { 206 if (!isVerbValid(pathStr[start])) { 207 result->failureOccurred = true; 208 result->failureMessage = "Invalid pathData. Failure occurred at position " 209 + std::to_string(start) + " of path: " + pathStr; 210 return; 211 } 212 data->verbs.push_back(pathStr[start]); 213 data->verbSizes.push_back(0); 214 } 215 } 216 217 void PathParser::dump(const PathData& data) { 218 // Print out the path data. 219 size_t start = 0; 220 for (size_t i = 0; i < data.verbs.size(); i++) { 221 std::ostringstream os; 222 os << data.verbs[i]; 223 os << ", verb size: " << data.verbSizes[i]; 224 for (size_t j = 0; j < data.verbSizes[i]; j++) { 225 os << " " << data.points[start + j]; 226 } 227 start += data.verbSizes[i]; 228 ALOGD("%s", os.str().c_str()); 229 } 230 231 std::ostringstream os; 232 for (size_t i = 0; i < data.points.size(); i++) { 233 os << data.points[i] << ", "; 234 } 235 ALOGD("points are : %s", os.str().c_str()); 236 } 237 238 void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) { 239 PathData pathData; 240 getPathDataFromAsciiString(&pathData, result, pathStr, strLen); 241 if (result->failureOccurred) { 242 return; 243 } 244 // Check if there is valid data coming out of parsing the string. 245 if (pathData.verbs.size() == 0) { 246 result->failureOccurred = true; 247 result->failureMessage = "No verbs found in the string for pathData: "; 248 result->failureMessage += pathStr; 249 return; 250 } 251 VectorDrawableUtils::verbsToPath(skPath, pathData); 252 return; 253 } 254 255 }; // namespace uirenderer 256 }; //namespace android 257