/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "PathParser.h" #include "jni.h" #include #include #include #include #include #include namespace android { namespace uirenderer { static size_t nextStart(const char* s, size_t length, size_t startIndex) { size_t index = startIndex; while (index < length) { char c = s[index]; // Note that 'e' or 'E' are not valid path commands, but could be // used for floating point numbers' scientific notation. // Therefore, when searching for next command, we should ignore 'e' // and 'E'. if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' && c != 'E') { return index; } index++; } return index; } /** * Calculate the position of the next comma or space or negative sign * @param s the string to search * @param start the position to start searching * @param result the result of the extraction, including the position of the * the starting position of next number, whether it is ending with a '-'. */ static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) { // Now looking for ' ', ',', '.' or '-' from the start. int currentIndex = start; bool foundSeparator = false; *outEndWithNegOrDot = false; bool secondDot = false; bool isExponential = false; for (; currentIndex < end; currentIndex++) { bool isPrevExponential = isExponential; isExponential = false; char currentChar = s[currentIndex]; switch (currentChar) { case ' ': case ',': foundSeparator = true; break; case '-': // The negative sign following a 'e' or 'E' is not a separator. if (currentIndex != start && !isPrevExponential) { foundSeparator = true; *outEndWithNegOrDot = true; } break; case '.': if (!secondDot) { secondDot = true; } else { // This is the second dot, and it is considered as a separator. foundSeparator = true; *outEndWithNegOrDot = true; } break; case 'e': case 'E': isExponential = true; break; } if (foundSeparator) { break; } } // In the case where nothing is found, we put the end position to the end of // our extract range. Otherwise, end position will be where separator is found. *outEndPosition = currentIndex; } static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) { char* endPtr = NULL; float currentValue = strtof(startPtr, &endPtr); if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) { result->failureOccurred = true; result->failureMessage = "Float out of range: "; result->failureMessage.append(startPtr, expectedLength); } if (currentValue == 0 && endPtr == startPtr) { // No conversion is done. result->failureOccurred = true; result->failureMessage = "Float format error when parsing: "; result->failureMessage.append(startPtr, expectedLength); } return currentValue; } /** * Parse the floats in the string. * * @param s the string containing a command and list of floats * @return true on success */ static void getFloats(std::vector* outPoints, PathParser::ParseResult* result, const char* pathStr, int start, int end) { if (pathStr[start] == 'z' || pathStr[start] == 'Z') { return; } int startPosition = start + 1; int endPosition = start; // The startPosition should always be the first character of the // current number, and endPosition is the character after the current // number. while (startPosition < end) { bool endWithNegOrDot; extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end); if (startPosition < endPosition) { float currentValue = parseFloat(result, &pathStr[startPosition], end - startPosition); if (result->failureOccurred) { return; } outPoints->push_back(currentValue); } if (endWithNegOrDot) { // Keep the '-' or '.' sign with next number. startPosition = endPosition; } else { startPosition = endPosition + 1; } } return; } void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::ParseResult* result) { size_t numberOfPointsExpected = -1; switch (verb) { case 'z': case 'Z': numberOfPointsExpected = 0; break; case 'm': case 'l': case 't': case 'M': case 'L': case 'T': numberOfPointsExpected = 2; break; case 'h': case 'v': case 'H': case 'V': numberOfPointsExpected = 1; break; case 'c': case 'C': numberOfPointsExpected = 6; break; case 's': case 'q': case 'S': case 'Q': numberOfPointsExpected = 4; break; case 'a': case 'A': numberOfPointsExpected = 7; break; default: result->failureOccurred = true; result->failureMessage += verb; result->failureMessage += " is not a valid verb. "; return; } if (numberOfPointsExpected == 0 && points == 0) { return; } if (numberOfPointsExpected > 0 && points % numberOfPointsExpected == 0) { return; } result->failureOccurred = true; result->failureMessage += verb; result->failureMessage += " needs to be followed by "; if (numberOfPointsExpected > 0) { result->failureMessage += "a multiple of "; } result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " + std::to_string(points) + " float(s) are found. "; } void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, const char* pathStr, size_t strLen) { if (pathStr == NULL) { result->failureOccurred = true; result->failureMessage = "Path string cannot be NULL."; return; } size_t start = 0; // Skip leading spaces. while (isspace(pathStr[start]) && start < strLen) { start++; } if (start == strLen) { result->failureOccurred = true; result->failureMessage = "Path string cannot be empty."; return; } size_t end = start + 1; while (end < strLen) { end = nextStart(pathStr, strLen, end); std::vector points; getFloats(&points, result, pathStr, start, end); validateVerbAndPoints(pathStr[start], points.size(), result); if (result->failureOccurred) { // If either verb or points is not valid, return immediately. result->failureMessage += "Failure occurred at position " + std::to_string(start) + " of path: " + pathStr; return; } data->verbs.push_back(pathStr[start]); data->verbSizes.push_back(points.size()); data->points.insert(data->points.end(), points.begin(), points.end()); start = end; end++; } if ((end - start) == 1 && start < strLen) { validateVerbAndPoints(pathStr[start], 0, result); if (result->failureOccurred) { // If either verb or points is not valid, return immediately. result->failureMessage += "Failure occurred at position " + std::to_string(start) + " of path: " + pathStr; return; } data->verbs.push_back(pathStr[start]); data->verbSizes.push_back(0); } } void PathParser::dump(const PathData& data) { // Print out the path data. size_t start = 0; for (size_t i = 0; i < data.verbs.size(); i++) { std::ostringstream os; os << data.verbs[i]; os << ", verb size: " << data.verbSizes[i]; for (size_t j = 0; j < data.verbSizes[i]; j++) { os << " " << data.points[start + j]; } start += data.verbSizes[i]; ALOGD("%s", os.str().c_str()); } std::ostringstream os; for (size_t i = 0; i < data.points.size(); i++) { os << data.points[i] << ", "; } ALOGD("points are : %s", os.str().c_str()); } void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) { PathData pathData; getPathDataFromAsciiString(&pathData, result, pathStr, strLen); if (result->failureOccurred) { return; } // Check if there is valid data coming out of parsing the string. if (pathData.verbs.size() == 0) { result->failureOccurred = true; result->failureMessage = "No verbs found in the string for pathData: "; result->failureMessage += pathStr; return; } VectorDrawableUtils::verbsToPath(skPath, pathData); return; } } // namespace uirenderer } // namespace android