/*
* 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 <errno.h>
#include <stdlib.h>
#include <utils/Log.h>
#include <sstream>
#include <string>
#include <vector>
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<float>* 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<float> 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