2 * Copyright (c) 2020 - 2022 Samsung Electronics Co., Ltd. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * Copyright notice for the EFL:
26 * Copyright (C) EFL developers (see AUTHORS)
28 * All rights reserved.
30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions are met:
33 * 1. Redistributions of source code must retain the above copyright
34 * notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright
36 * notice, this list of conditions and the following disclaimer in the
37 * documentation and/or other materials provided with the distribution.
39 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
40 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
41 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
42 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
45 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
46 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
47 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
48 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
57 #include "tvgSvgLoaderCommon.h"
58 #include "tvgSvgPath.h"
59 #include "tvgSvgUtil.h"
61 /************************************************************************/
62 /* Internal Class Implementation */
63 /************************************************************************/
65 static char* _skipComma(const char* content)
67 while (*content && isspace(*content)) {
70 if (*content == ',') return (char*)content + 1;
71 return (char*)content;
75 static bool _parseNumber(char** content, float* number)
78 *number = svgUtilStrtof(*content, &end);
79 //If the start of string is not number
80 if ((*content) == end) return false;
82 *content = _skipComma(end);
87 static bool _parseFlag(char** content, int* number)
90 if (*(*content) != '0' && *(*content) != '1') return false;
91 *number = *(*content) - '0';
94 *content = _skipComma(end);
100 void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
102 float cxp, cyp, cx, cy;
104 float cosPhi, sinPhi;
112 float theta1, deltaTheta;
115 float cosPhiRx, cosPhiRy;
116 float sinPhiRx, sinPhiRy;
117 float cosTheta1, sinTheta1;
120 //Some helpful stuff is available here:
121 //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
125 //If start and end points are identical, then no arc is drawn
126 if ((fabsf(x - sx) < (1.0f / 256.0f)) && (fabsf(y - sy) < (1.0f / 256.0f))) return;
128 //Correction of out-of-range radii, see F6.6.1 (step 2)
132 angle = angle * M_PI / 180.0f;
133 cosPhi = cosf(angle);
134 sinPhi = sinf(angle);
135 dx2 = (sx - x) / 2.0f;
136 dy2 = (sy - y) / 2.0f;
137 x1p = cosPhi * dx2 + sinPhi * dy2;
138 y1p = cosPhi * dy2 - sinPhi * dx2;
143 lambda = (x1p2 / rx2) + (y1p2 / ry2);
145 //Correction of out-of-range radii, see F6.6.2 (step 4)
148 float lambdaRoot = sqrtf(lambda);
157 c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
159 //Check if there is no possible solution
160 //(i.e. we can't do a square root of a negative value)
162 //Scale uniformly until we have a single solution
163 //(see F6.2) i.e. when c == 0.0
164 float scale = sqrtf(1.0f - c / (rx2 * ry2));
171 //Step 2 (F6.5.2) - simplified since c == 0.0
174 //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
178 //Complete c calculation
179 c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
180 //Inverse sign if Fa == Fs
181 if (largeArc == sweep) c = -c;
184 cxp = c * (rx * y1p / ry);
185 cyp = c * (-ry * x1p / rx);
187 //Step 3 (F6.5.3 first part)
188 cx = cosPhi * cxp - sinPhi * cyp;
189 cy = sinPhi * cxp + cosPhi * cyp;
192 //Step 3 (F6.5.3 second part) we now have the center point of the ellipse
193 cx += (sx + x) / 2.0f;
194 cy += (sy + y) / 2.0f;
197 //We dont' use arccos (as per w3c doc), see
198 //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
199 //Note: atan2 (0.0, 1.0) == 0.0
200 at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
201 theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at;
203 nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
204 deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at;
207 //Ensure delta theta < 0 or else add 360 degrees
208 if (deltaTheta < 0.0f) deltaTheta += (float)(2.0f * M_PI);
210 //Ensure delta theta > 0 or else substract 360 degrees
211 if (deltaTheta > 0.0f) deltaTheta -= (float)(2.0f * M_PI);
214 //Add several cubic bezier to approximate the arc
215 //(smaller than 90 degrees)
216 //We add one extra segment because we want something
217 //Smaller than 90deg (i.e. not 90 itself)
218 segments = static_cast<int>(fabsf(deltaTheta / float(M_PI_2)) + 1.0f);
219 delta = deltaTheta / segments;
221 //http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
222 bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
224 cosPhiRx = cosPhi * rx;
225 cosPhiRy = cosPhi * ry;
226 sinPhiRx = sinPhi * rx;
227 sinPhiRy = sinPhi * ry;
229 cosTheta1 = cosf(theta1);
230 sinTheta1 = sinf(theta1);
232 for (int i = 0; i < segments; ++i) {
233 //End angle (for this segment) = current + delta
234 float c1x, c1y, ex, ey, c2x, c2y;
235 float theta2 = theta1 + delta;
236 float cosTheta2 = cosf(theta2);
237 float sinTheta2 = sinf(theta2);
240 //First control point (based on start point sx,sy)
241 c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
242 c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
244 //End point (for this segment)
245 ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
246 ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
248 //Second control point (based on end point ex,ey)
249 c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
250 c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
251 cmds->push(PathCommand::CubicTo);
261 //Next start point is the current end point (same for angle)
265 //Avoid recomputations
266 cosTheta1 = cosTheta2;
267 sinTheta1 = sinTheta2;
271 static int _numberCount(char cmd)
317 static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic)
326 for (int i = 0; i < count - 1; i += 2) {
327 arr[i] = arr[i] + cur->x;
328 arr[i + 1] = arr[i + 1] + cur->y;
333 arr[0] = arr[0] + cur->x;
337 arr[0] = arr[0] + cur->y;
341 arr[5] = arr[5] + cur->x;
342 arr[6] = arr[6] + cur->y;
353 Point p = {arr[0], arr[1]};
354 cmds->push(PathCommand::MoveTo);
356 *cur = {arr[0], arr[1]};
357 *startPoint = {arr[0], arr[1]};
362 Point p = {arr[0], arr[1]};
363 cmds->push(PathCommand::LineTo);
365 *cur = {arr[0], arr[1]};
371 cmds->push(PathCommand::CubicTo);
372 p[0] = {arr[0], arr[1]};
373 p[1] = {arr[2], arr[3]};
374 p[2] = {arr[4], arr[5]};
380 *isQuadratic = false;
386 if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
388 ctrl.x = 2 * cur->x - curCtl->x;
389 ctrl.y = 2 * cur->y - curCtl->y;
393 cmds->push(PathCommand::CubicTo);
395 p[1] = {arr[0], arr[1]};
396 p[2] = {arr[2], arr[3]};
402 *isQuadratic = false;
408 float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0);
409 float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0);
410 float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0);
411 float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0);
412 cmds->push(PathCommand::CubicTo);
413 p[0] = {ctrl_x0, ctrl_y0};
414 p[1] = {ctrl_x1, ctrl_y1};
415 p[2] = {arr[2], arr[3]};
419 *curCtl = {arr[0], arr[1]};
427 if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
429 ctrl.x = 2 * cur->x - curCtl->x;
430 ctrl.y = 2 * cur->y - curCtl->y;
434 float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0);
435 float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0);
436 float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0);
437 float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0);
438 cmds->push(PathCommand::CubicTo);
439 p[0] = {ctrl_x0, ctrl_y0};
440 p[1] = {ctrl_x1, ctrl_y1};
441 p[2] = {arr[0], arr[1]};
445 *curCtl = {ctrl.x, ctrl.y};
452 Point p = {arr[0], cur->y};
453 cmds->push(PathCommand::LineTo);
460 Point p = {cur->x, arr[0]};
461 cmds->push(PathCommand::LineTo);
468 cmds->push(PathCommand::Close);
474 _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]);
475 *cur = *curCtl = {arr[5], arr[6]};
476 *isQuadratic = false;
487 static char* _nextCommand(char* path, char* cmd, float* arr, int* count)
491 path = _skipComma(path);
492 if (isalpha(*path)) {
495 *count = _numberCount(*cmd);
497 if (*cmd == 'm') *cmd = 'l';
498 else if (*cmd == 'M') *cmd = 'L';
501 //Special case for arc command
502 if (_parseNumber(&path, &arr[0])) {
503 if (_parseNumber(&path, &arr[1])) {
504 if (_parseNumber(&path, &arr[2])) {
505 if (_parseFlag(&path, &large)) {
506 if (_parseFlag(&path, &sweep)) {
507 if (_parseNumber(&path, &arr[5])) {
508 if (_parseNumber(&path, &arr[6])) {
509 arr[3] = (float)large;
510 arr[4] = (float)sweep;
522 for (int i = 0; i < *count; i++) {
523 if (!_parseNumber(&path, &arr[i])) {
527 path = _skipComma(path);
533 /************************************************************************/
534 /* External Class Implementation */
535 /************************************************************************/
538 bool svgPathToTvgPath(const char* svgPath, Array<PathCommand>& cmds, Array<Point>& pts)
540 float numberArray[7];
542 Point cur = { 0, 0 };
543 Point curCtl = { 0, 0 };
544 Point startPoint = { 0, 0 };
546 bool isQuadratic = false;
547 char* path = (char*)svgPath;
550 curLocale = setlocale(LC_NUMERIC, NULL);
551 if (curLocale) curLocale = strdup(curLocale);
552 setlocale(LC_NUMERIC, "POSIX");
554 while ((path[0] != '\0')) {
555 path = _nextCommand(path, &cmd, numberArray, &numberCount);
557 if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic)) break;
560 setlocale(LC_NUMERIC, curLocale);
561 if (curLocale) free(curLocale);