VectorDrawable native rendering - Step 1 of MANY
Implement path parsing from string to skia path in native. The parsing
contains two main stages:
1) Parse string into a list of nodes that contains one operation (such
as move) and a vector of floats as params for that operation.
2) Interpret the operations defined in the nodes into SkPath operations,
and create a skia path
Also provided unit test for parsing a string path into a list of nodes,
and then to a skia path.
Change-Id: I0ce13df5e3bb90987dcdc80fe8b039af175ad2e2
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
new file mode 100644
index 0000000..e8ed8a1
--- /dev/null
+++ b/libs/hwui/PathParser.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 <utils/Log.h>
+#include <sstream>
+#include <stdlib.h>
+#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;
+}
+
+/**
+* Parse the floats in the string.
+* This is an optimized version of parseFloat(s.split(",|\\s"));
+*
+* @param s the string containing a command and list of floats
+* @return array of floats
+*/
+static void getFloats(std::vector<float>* outPoints, 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) {
+ outPoints->push_back(strtof(&pathStr[startPosition], NULL));
+ }
+
+ if (endWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
+ startPosition = endPosition;
+ } else {
+ startPosition = endPosition + 1;
+ }
+ }
+}
+
+void PathParser::getPathDataFromString(PathData* data, const char* pathStr, size_t strLen) {
+ if (pathStr == NULL) {
+ return;
+ }
+
+ size_t start = 0;
+ size_t end = 1;
+
+ while (end < strLen) {
+ end = nextStart(pathStr, strLen, end);
+ std::vector<float> points;
+ getFloats(&points, pathStr, start, end);
+ 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 && pathStr[start] != '\0') {
+ data->verbs.push_back(pathStr[start]);
+ data->verbSizes.push_back(0);
+ }
+
+ int i = 0;
+ while(pathStr[i] != '\0') {
+ i++;
+ }
+
+}
+
+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];
+ 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::parseStringForSkPath(SkPath* skPath, const char* pathStr, size_t strLen) {
+ PathData pathData;
+ getPathDataFromString(&pathData, pathStr, strLen);
+ VectorDrawablePath::verbsToPath(skPath, &pathData);
+}
+
+}; // namespace uirenderer
+}; //namespace android