307 lines
10 KiB
C++
307 lines
10 KiB
C++
/*
|
|
* 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 <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
|