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
28namespace android {
29namespace uirenderer {
30
31static 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 */
55static 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
101static 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 */
124static 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
159bool 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
165void 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
217void 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
238void 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