blob: 61d06c2697aa1e0ad07179b8a57ff71fe02b4281 [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
Doris Liu1e67f082015-11-12 15:57:45 -080019#include <errno.h>
John Reck1bcacfd2017-11-03 10:12:19 -070020#include <stdlib.h>
Doris Liu30bcf692015-11-04 14:56:24 -080021#include <utils/Log.h>
22#include <sstream>
Doris Liu30bcf692015-11-04 14:56:24 -080023#include <string>
24#include <vector>
25
26namespace android {
27namespace uirenderer {
28
29static size_t nextStart(const char* s, size_t length, size_t startIndex) {
30 size_t index = startIndex;
31 while (index < length) {
32 char c = s[index];
33 // Note that 'e' or 'E' are not valid path commands, but could be
34 // used for floating point numbers' scientific notation.
35 // Therefore, when searching for next command, we should ignore 'e'
36 // and 'E'.
John Reck1bcacfd2017-11-03 10:12:19 -070037 if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' &&
38 c != 'E') {
Doris Liu30bcf692015-11-04 14:56:24 -080039 return index;
40 }
41 index++;
42 }
43 return index;
44}
45
46/**
47 * Calculate the position of the next comma or space or negative sign
48 * @param s the string to search
49 * @param start the position to start searching
50 * @param result the result of the extraction, including the position of the
51 * the starting position of next number, whether it is ending with a '-'.
52 */
John Reck1bcacfd2017-11-03 10:12:19 -070053static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start,
54 int end) {
Doris Liu30bcf692015-11-04 14:56:24 -080055 // Now looking for ' ', ',', '.' or '-' from the start.
56 int currentIndex = start;
57 bool foundSeparator = false;
58 *outEndWithNegOrDot = false;
59 bool secondDot = false;
60 bool isExponential = false;
61 for (; currentIndex < end; currentIndex++) {
62 bool isPrevExponential = isExponential;
63 isExponential = false;
64 char currentChar = s[currentIndex];
65 switch (currentChar) {
John Reck1bcacfd2017-11-03 10:12:19 -070066 case ' ':
67 case ',':
Doris Liu30bcf692015-11-04 14:56:24 -080068 foundSeparator = true;
John Reck1bcacfd2017-11-03 10:12:19 -070069 break;
70 case '-':
71 // The negative sign following a 'e' or 'E' is not a separator.
72 if (currentIndex != start && !isPrevExponential) {
73 foundSeparator = true;
74 *outEndWithNegOrDot = true;
75 }
76 break;
77 case '.':
78 if (!secondDot) {
79 secondDot = true;
80 } else {
81 // This is the second dot, and it is considered as a separator.
82 foundSeparator = true;
83 *outEndWithNegOrDot = true;
84 }
85 break;
86 case 'e':
87 case 'E':
88 isExponential = true;
89 break;
Doris Liu30bcf692015-11-04 14:56:24 -080090 }
91 if (foundSeparator) {
92 break;
93 }
94 }
95 // In the case where nothing is found, we put the end position to the end of
96 // our extract range. Otherwise, end position will be where separator is found.
97 *outEndPosition = currentIndex;
98}
99
John Reck1bcacfd2017-11-03 10:12:19 -0700100static float parseFloat(PathParser::ParseResult* result, const char* startPtr,
101 size_t expectedLength) {
Doris Liu1e67f082015-11-12 15:57:45 -0800102 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
Doris Liu30bcf692015-11-04 14:56:24 -0800118/**
Doris Liu1e67f082015-11-12 15:57:45 -0800119 * 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,
John Reck1bcacfd2017-11-03 10:12:19 -0700125 const char* pathStr, int start, int end) {
Doris Liu30bcf692015-11-04 14:56:24 -0800126 if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
127 return;
128 }
129 int startPosition = start + 1;
130 int endPosition = start;
131
132 // The startPosition should always be the first character of the
133 // current number, and endPosition is the character after the current
134 // number.
135 while (startPosition < end) {
136 bool endWithNegOrDot;
137 extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
138
139 if (startPosition < endPosition) {
John Reck1bcacfd2017-11-03 10:12:19 -0700140 float currentValue = parseFloat(result, &pathStr[startPosition], end - startPosition);
Doris Liu1e67f082015-11-12 15:57:45 -0800141 if (result->failureOccurred) {
142 return;
143 }
144 outPoints->push_back(currentValue);
Doris Liu30bcf692015-11-04 14:56:24 -0800145 }
146
147 if (endWithNegOrDot) {
148 // Keep the '-' or '.' sign with next number.
149 startPosition = endPosition;
150 } else {
151 startPosition = endPosition + 1;
152 }
153 }
Doris Liu1e67f082015-11-12 15:57:45 -0800154 return;
Doris Liu30bcf692015-11-04 14:56:24 -0800155}
156
Doris Liu4ad0e142018-03-23 18:33:45 -0700157void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::ParseResult* result) {
158 size_t numberOfPointsExpected = -1;
159 switch (verb) {
160 case 'z':
161 case 'Z':
162 numberOfPointsExpected = 0;
163 break;
164 case 'm':
165 case 'l':
166 case 't':
167 case 'M':
168 case 'L':
169 case 'T':
170 numberOfPointsExpected = 2;
171 break;
172 case 'h':
173 case 'v':
174 case 'H':
175 case 'V':
176 numberOfPointsExpected = 1;
177 break;
178 case 'c':
179 case 'C':
180 numberOfPointsExpected = 6;
181 break;
182 case 's':
183 case 'q':
184 case 'S':
185 case 'Q':
186 numberOfPointsExpected = 4;
187 break;
188 case 'a':
189 case 'A':
190 numberOfPointsExpected = 7;
191 break;
192 default:
193 result->failureOccurred = true;
194 result->failureMessage += verb;
195 result->failureMessage += " is not a valid verb. ";
196 return;
197 }
198 if (numberOfPointsExpected == 0 && points == 0) {
199 return;
200 }
201 if (numberOfPointsExpected > 0 && points % numberOfPointsExpected == 0) {
202 return;
203 }
204
205 result->failureOccurred = true;
206 result->failureMessage += verb;
207 result->failureMessage += " needs to be followed by ";
208 if (numberOfPointsExpected > 0) {
209 result->failureMessage += "a multiple of ";
210 }
John Recke170fb62018-05-07 08:12:07 -0700211 result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " +
212 std::to_string(points) + " float(s) are found. ";
Doris Liu0a1a5162016-04-07 15:03:11 -0700213}
214
Doris Liub35da392016-04-12 11:06:23 -0700215void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
John Reck1bcacfd2017-11-03 10:12:19 -0700216 const char* pathStr, size_t strLen) {
Doris Liu30bcf692015-11-04 14:56:24 -0800217 if (pathStr == NULL) {
Doris Liu1e67f082015-11-12 15:57:45 -0800218 result->failureOccurred = true;
219 result->failureMessage = "Path string cannot be NULL.";
Doris Liu30bcf692015-11-04 14:56:24 -0800220 return;
221 }
222
223 size_t start = 0;
Doris Liub35da392016-04-12 11:06:23 -0700224 // Skip leading spaces.
225 while (isspace(pathStr[start]) && start < strLen) {
226 start++;
227 }
228 if (start == strLen) {
229 result->failureOccurred = true;
230 result->failureMessage = "Path string cannot be empty.";
231 return;
232 }
233 size_t end = start + 1;
Doris Liu30bcf692015-11-04 14:56:24 -0800234
235 while (end < strLen) {
236 end = nextStart(pathStr, strLen, end);
237 std::vector<float> points;
Doris Liu1e67f082015-11-12 15:57:45 -0800238 getFloats(&points, result, pathStr, start, end);
Doris Liu4ad0e142018-03-23 18:33:45 -0700239 validateVerbAndPoints(pathStr[start], points.size(), result);
Doris Liu1e67f082015-11-12 15:57:45 -0800240 if (result->failureOccurred) {
Doris Liu4ad0e142018-03-23 18:33:45 -0700241 // If either verb or points is not valid, return immediately.
John Recke170fb62018-05-07 08:12:07 -0700242 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
243 " of path: " + pathStr;
Doris Liu1e67f082015-11-12 15:57:45 -0800244 return;
245 }
Doris Liu30bcf692015-11-04 14:56:24 -0800246 data->verbs.push_back(pathStr[start]);
247 data->verbSizes.push_back(points.size());
248 data->points.insert(data->points.end(), points.begin(), points.end());
249 start = end;
250 end++;
251 }
252
Doris Liu1e67f082015-11-12 15:57:45 -0800253 if ((end - start) == 1 && start < strLen) {
Doris Liu4ad0e142018-03-23 18:33:45 -0700254 validateVerbAndPoints(pathStr[start], 0, result);
255 if (result->failureOccurred) {
256 // If either verb or points is not valid, return immediately.
John Recke170fb62018-05-07 08:12:07 -0700257 result->failureMessage += "Failure occurred at position " + std::to_string(start) +
258 " of path: " + pathStr;
Doris Liu0a1a5162016-04-07 15:03:11 -0700259 return;
260 }
Doris Liu30bcf692015-11-04 14:56:24 -0800261 data->verbs.push_back(pathStr[start]);
262 data->verbSizes.push_back(0);
263 }
Doris Liu30bcf692015-11-04 14:56:24 -0800264}
265
266void PathParser::dump(const PathData& data) {
267 // Print out the path data.
268 size_t start = 0;
269 for (size_t i = 0; i < data.verbs.size(); i++) {
270 std::ostringstream os;
271 os << data.verbs[i];
Doris Liu1e67f082015-11-12 15:57:45 -0800272 os << ", verb size: " << data.verbSizes[i];
Doris Liu30bcf692015-11-04 14:56:24 -0800273 for (size_t j = 0; j < data.verbSizes[i]; j++) {
274 os << " " << data.points[start + j];
275 }
276 start += data.verbSizes[i];
277 ALOGD("%s", os.str().c_str());
278 }
279
280 std::ostringstream os;
281 for (size_t i = 0; i < data.points.size(); i++) {
282 os << data.points[i] << ", ";
283 }
284 ALOGD("points are : %s", os.str().c_str());
285}
286
John Reck1bcacfd2017-11-03 10:12:19 -0700287void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr,
288 size_t strLen) {
Doris Liu30bcf692015-11-04 14:56:24 -0800289 PathData pathData;
Doris Liub35da392016-04-12 11:06:23 -0700290 getPathDataFromAsciiString(&pathData, result, pathStr, strLen);
Doris Liu1e67f082015-11-12 15:57:45 -0800291 if (result->failureOccurred) {
292 return;
293 }
Doris Liucdd23f92015-11-11 14:31:13 -0800294 // Check if there is valid data coming out of parsing the string.
295 if (pathData.verbs.size() == 0) {
Doris Liu1e67f082015-11-12 15:57:45 -0800296 result->failureOccurred = true;
Doris Liu0a1a5162016-04-07 15:03:11 -0700297 result->failureMessage = "No verbs found in the string for pathData: ";
298 result->failureMessage += pathStr;
Doris Liu1e67f082015-11-12 15:57:45 -0800299 return;
Doris Liucdd23f92015-11-11 14:31:13 -0800300 }
Doris Liu804618d2015-11-16 22:48:34 -0800301 VectorDrawableUtils::verbsToPath(skPath, pathData);
Doris Liu1e67f082015-11-12 15:57:45 -0800302 return;
Doris Liu30bcf692015-11-04 14:56:24 -0800303}
304
Chris Blume7b8a8082018-11-30 15:51:58 -0800305} // namespace uirenderer
306} // namespace android