blob: ad599e9ec316f9a37a70f312ecee837f06e27668 [file] [log] [blame]
Doris Liu30bcf692015-11-04 14:56:24 -08001/*
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
Doris Liu1e67f082015-11-12 15:57:45 -080021#include <errno.h>
John Reck1bcacfd2017-11-03 10:12:19 -070022#include <stdlib.h>
Doris Liu30bcf692015-11-04 14:56:24 -080023#include <utils/Log.h>
24#include <sstream>
Doris Liu30bcf692015-11-04 14:56:24 -080025#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'.
John Reck1bcacfd2017-11-03 10:12:19 -070039 if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' &&
40 c != 'E') {
Doris Liu30bcf692015-11-04 14:56:24 -080041 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 */
John Reck1bcacfd2017-11-03 10:12:19 -070055static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start,
56 int end) {
Doris Liu30bcf692015-11-04 14:56:24 -080057 // Now looking for ' ', ',', '.' or '-' from the start.
58 int currentIndex = start;
59 bool foundSeparator = false;
60 *outEndWithNegOrDot = false;
61 bool secondDot = false;
62 bool isExponential = false;
63 for (; currentIndex < end; currentIndex++) {
64 bool isPrevExponential = isExponential;
65 isExponential = false;
66 char currentChar = s[currentIndex];
67 switch (currentChar) {
John Reck1bcacfd2017-11-03 10:12:19 -070068 case ' ':
69 case ',':
Doris Liu30bcf692015-11-04 14:56:24 -080070 foundSeparator = true;
John Reck1bcacfd2017-11-03 10:12:19 -070071 break;
72 case '-':
73 // The negative sign following a 'e' or 'E' is not a separator.
74 if (currentIndex != start && !isPrevExponential) {
75 foundSeparator = true;
76 *outEndWithNegOrDot = true;
77 }
78 break;
79 case '.':
80 if (!secondDot) {
81 secondDot = true;
82 } else {
83 // This is the second dot, and it is considered as a separator.
84 foundSeparator = true;
85 *outEndWithNegOrDot = true;
86 }
87 break;
88 case 'e':
89 case 'E':
90 isExponential = true;
91 break;
Doris Liu30bcf692015-11-04 14:56:24 -080092 }
93 if (foundSeparator) {
94 break;
95 }
96 }
97 // In the case where nothing is found, we put the end position to the end of
98 // our extract range. Otherwise, end position will be where separator is found.
99 *outEndPosition = currentIndex;
100}
101
John Reck1bcacfd2017-11-03 10:12:19 -0700102static float parseFloat(PathParser::ParseResult* result, const char* startPtr,
103 size_t expectedLength) {
Doris Liu1e67f082015-11-12 15:57:45 -0800104 char* endPtr = NULL;
105 float currentValue = strtof(startPtr, &endPtr);
106 if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
107 result->failureOccurred = true;
108 result->failureMessage = "Float out of range: ";
109 result->failureMessage.append(startPtr, expectedLength);
110 }
111 if (currentValue == 0 && endPtr == startPtr) {
112 // No conversion is done.
113 result->failureOccurred = true;
114 result->failureMessage = "Float format error when parsing: ";
115 result->failureMessage.append(startPtr, expectedLength);
116 }
117 return currentValue;
118}
119
Doris Liu30bcf692015-11-04 14:56:24 -0800120/**
Doris Liu1e67f082015-11-12 15:57:45 -0800121 * Parse the floats in the string.
122 *
123 * @param s the string containing a command and list of floats
124 * @return true on success
125 */
126static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
John Reck1bcacfd2017-11-03 10:12:19 -0700127 const char* pathStr, int start, int end) {
Doris Liu30bcf692015-11-04 14:56:24 -0800128 if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
129 return;
130 }
131 int startPosition = start + 1;
132 int endPosition = start;
133
134 // The startPosition should always be the first character of the
135 // current number, and endPosition is the character after the current
136 // number.
137 while (startPosition < end) {
138 bool endWithNegOrDot;
139 extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
140
141 if (startPosition < endPosition) {
John Reck1bcacfd2017-11-03 10:12:19 -0700142 float currentValue = parseFloat(result, &pathStr[startPosition], end - startPosition);
Doris Liu1e67f082015-11-12 15:57:45 -0800143 if (result->failureOccurred) {
144 return;
145 }
146 outPoints->push_back(currentValue);
Doris Liu30bcf692015-11-04 14:56:24 -0800147 }
148
149 if (endWithNegOrDot) {
150 // Keep the '-' or '.' sign with next number.
151 startPosition = endPosition;
152 } else {
153 startPosition = endPosition + 1;
154 }
155 }
Doris Liu1e67f082015-11-12 15:57:45 -0800156 return;
Doris Liu30bcf692015-11-04 14:56:24 -0800157}
158
Doris Liu4ad0e142018-03-23 18:33:45 -0700159void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::ParseResult* result) {
160 size_t numberOfPointsExpected = -1;
161 switch (verb) {
162 case 'z':
163 case 'Z':
164 numberOfPointsExpected = 0;
165 break;
166 case 'm':
167 case 'l':
168 case 't':
169 case 'M':
170 case 'L':
171 case 'T':
172 numberOfPointsExpected = 2;
173 break;
174 case 'h':
175 case 'v':
176 case 'H':
177 case 'V':
178 numberOfPointsExpected = 1;
179 break;
180 case 'c':
181 case 'C':
182 numberOfPointsExpected = 6;
183 break;
184 case 's':
185 case 'q':
186 case 'S':
187 case 'Q':
188 numberOfPointsExpected = 4;
189 break;
190 case 'a':
191 case 'A':
192 numberOfPointsExpected = 7;
193 break;
194 default:
195 result->failureOccurred = true;
196 result->failureMessage += verb;
197 result->failureMessage += " is not a valid verb. ";
198 return;
199 }
200 if (numberOfPointsExpected == 0 && points == 0) {
201 return;
202 }
203 if (numberOfPointsExpected > 0 && points % numberOfPointsExpected == 0) {
204 return;
205 }
206
207 result->failureOccurred = true;
208 result->failureMessage += verb;
209 result->failureMessage += " needs to be followed by ";
210 if (numberOfPointsExpected > 0) {
211 result->failureMessage += "a multiple of ";
212 }
John Recke170fb62018-05-07 08:12:07 -0700213 result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " +
214 std::to_string(points) + " float(s) are found. ";
Doris Liu0a1a5162016-04-07 15:03:11 -0700215}
216
Doris Liub35da392016-04-12 11:06:23 -0700217void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
John Reck1bcacfd2017-11-03 10:12:19 -0700218 const char* pathStr, size_t strLen) {
Doris Liu30bcf692015-11-04 14:56:24 -0800219 if (pathStr == NULL) {
Doris Liu1e67f082015-11-12 15:57:45 -0800220 result->failureOccurred = true;
221 result->failureMessage = "Path string cannot be NULL.";
Doris Liu30bcf692015-11-04 14:56:24 -0800222 return;
223 }
224
225 size_t start = 0;
Doris Liub35da392016-04-12 11:06:23 -0700226 // Skip leading spaces.
227 while (isspace(pathStr[start]) && start < strLen) {
228 start++;
229 }
230 if (start == strLen) {
231 result->failureOccurred = true;
232 result->failureMessage = "Path string cannot be empty.";
233 return;
234 }
235 size_t end = start + 1;
Doris Liu30bcf692015-11-04 14:56:24 -0800236
237 while (end < strLen) {
238 end = nextStart(pathStr, strLen, end);
239 std::vector<float> points;
Doris Liu1e67f082015-11-12 15:57:45 -0800240 getFloats(&points, result, pathStr, start, end);
Doris Liu4ad0e142018-03-23 18:33:45 -0700241 validateVerbAndPoints(pathStr[start], points.size(), result);
Doris Liu1e67f082015-11-12 15:57:45 -0800242 if (result->failureOccurred) {
Doris Liu4ad0e142018-03-23 18:33:45 -0700243 // If either verb or points is not valid, return immediately.
John Recke170fb62018-05-07 08:12:07 -0700244 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
245 " of path: " + pathStr;
Doris Liu1e67f082015-11-12 15:57:45 -0800246 return;
247 }
Doris Liu30bcf692015-11-04 14:56:24 -0800248 data->verbs.push_back(pathStr[start]);
249 data->verbSizes.push_back(points.size());
250 data->points.insert(data->points.end(), points.begin(), points.end());
251 start = end;
252 end++;
253 }
254
Doris Liu1e67f082015-11-12 15:57:45 -0800255 if ((end - start) == 1 && start < strLen) {
Doris Liu4ad0e142018-03-23 18:33:45 -0700256 validateVerbAndPoints(pathStr[start], 0, result);
257 if (result->failureOccurred) {
258 // If either verb or points is not valid, return immediately.
John Recke170fb62018-05-07 08:12:07 -0700259 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
260 " of path: " + pathStr;
Doris Liu0a1a5162016-04-07 15:03:11 -0700261 return;
262 }
Doris Liu30bcf692015-11-04 14:56:24 -0800263 data->verbs.push_back(pathStr[start]);
264 data->verbSizes.push_back(0);
265 }
Doris Liu30bcf692015-11-04 14:56:24 -0800266}
267
268void PathParser::dump(const PathData& data) {
269 // Print out the path data.
270 size_t start = 0;
271 for (size_t i = 0; i < data.verbs.size(); i++) {
272 std::ostringstream os;
273 os << data.verbs[i];
Doris Liu1e67f082015-11-12 15:57:45 -0800274 os << ", verb size: " << data.verbSizes[i];
Doris Liu30bcf692015-11-04 14:56:24 -0800275 for (size_t j = 0; j < data.verbSizes[i]; j++) {
276 os << " " << data.points[start + j];
277 }
278 start += data.verbSizes[i];
279 ALOGD("%s", os.str().c_str());
280 }
281
282 std::ostringstream os;
283 for (size_t i = 0; i < data.points.size(); i++) {
284 os << data.points[i] << ", ";
285 }
286 ALOGD("points are : %s", os.str().c_str());
287}
288
John Reck1bcacfd2017-11-03 10:12:19 -0700289void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr,
290 size_t strLen) {
Doris Liu30bcf692015-11-04 14:56:24 -0800291 PathData pathData;
Doris Liub35da392016-04-12 11:06:23 -0700292 getPathDataFromAsciiString(&pathData, result, pathStr, strLen);
Doris Liu1e67f082015-11-12 15:57:45 -0800293 if (result->failureOccurred) {
294 return;
295 }
Doris Liucdd23f92015-11-11 14:31:13 -0800296 // Check if there is valid data coming out of parsing the string.
297 if (pathData.verbs.size() == 0) {
Doris Liu1e67f082015-11-12 15:57:45 -0800298 result->failureOccurred = true;
Doris Liu0a1a5162016-04-07 15:03:11 -0700299 result->failureMessage = "No verbs found in the string for pathData: ";
300 result->failureMessage += pathStr;
Doris Liu1e67f082015-11-12 15:57:45 -0800301 return;
Doris Liucdd23f92015-11-11 14:31:13 -0800302 }
Doris Liu804618d2015-11-16 22:48:34 -0800303 VectorDrawableUtils::verbsToPath(skPath, pathData);
Doris Liu1e67f082015-11-12 15:57:45 -0800304 return;
Doris Liu30bcf692015-11-04 14:56:24 -0800305}
306
John Reck1bcacfd2017-11-03 10:12:19 -0700307}; // namespace uirenderer
308}; // namespace android