1 /*
2 * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
3
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:
10
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13
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
20 * SOFTWARE.
21 */
22
23 #include "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25
26 /*
27 * Copyright notice for the EFL:
28
29 * Copyright (C) EFL developers (see AUTHORS)
30
31 * All rights reserved.
32
33 * Redistribution and use in source and binary forms, with or without
34 * modification, are permitted provided that the following conditions are met:
35
36 * 1. Redistributions of source code must retain the above copyright
37 * notice, this list of conditions and the following disclaimer.
38 * 2. Redistributions in binary form must reproduce the above copyright
39 * notice, this list of conditions and the following disclaimer in the
40 * documentation and/or other materials provided with the distribution.
41
42 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
43 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
44 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
45 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
46 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
48 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
49 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
50 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
51 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 */
53
54 #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
55
56 #include <cstring>
57 #include <ctype.h>
58 #include "tvgMath.h"
59 #include "tvgShape.h"
60 #include "tvgSvgLoaderCommon.h"
61 #include "tvgSvgPath.h"
62 #include "tvgStr.h"
63
64 /************************************************************************/
65 /* Internal Class Implementation */
66 /************************************************************************/
67
_skipComma(const char * content)68 static char* _skipComma(const char* content)
69 {
70 while (*content && isspace(*content)) {
71 content++;
72 }
73 if (*content == ',') return (char*)content + 1;
74 return (char*)content;
75 }
76
77
_parseNumber(char ** content,float * number)78 static bool _parseNumber(char** content, float* number)
79 {
80 char* end = NULL;
81 *number = strToFloat(*content, &end);
82 //If the start of string is not number
83 if ((*content) == end) return false;
84 //Skip comma if any
85 *content = _skipComma(end);
86 return true;
87 }
88
89
_parseFlag(char ** content,int * number)90 static bool _parseFlag(char** content, int* number)
91 {
92 char* end = NULL;
93 if (*(*content) != '0' && *(*content) != '1') return false;
94 *number = *(*content) - '0';
95 *content += 1;
96 end = *content;
97 *content = _skipComma(end);
98
99 return true;
100 }
101
102
_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)103 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)
104 {
105 float cxp, cyp, cx, cy;
106 float sx, sy;
107 float cosPhi, sinPhi;
108 float dx2, dy2;
109 float x1p, y1p;
110 float x1p2, y1p2;
111 float rx2, ry2;
112 float lambda;
113 float c;
114 float at;
115 float theta1, deltaTheta;
116 float nat;
117 float delta, bcp;
118 float cosPhiRx, cosPhiRy;
119 float sinPhiRx, sinPhiRy;
120 float cosTheta1, sinTheta1;
121 int segments;
122
123 //Some helpful stuff is available here:
124 //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
125 sx = cur->x;
126 sy = cur->y;
127
128 //Correction of out-of-range radii, see F6.6.1 (step 2)
129 rx = fabsf(rx);
130 ry = fabsf(ry);
131
132 angle = deg2rad(angle);
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;
139 x1p2 = x1p * x1p;
140 y1p2 = y1p * y1p;
141 rx2 = rx * rx;
142 ry2 = ry * ry;
143 lambda = (x1p2 / rx2) + (y1p2 / ry2);
144
145 //Correction of out-of-range radii, see F6.6.2 (step 4)
146 if (lambda > 1.0f) {
147 //See F6.6.3
148 float lambdaRoot = sqrtf(lambda);
149
150 rx *= lambdaRoot;
151 ry *= lambdaRoot;
152 //Update rx2 and ry2
153 rx2 = rx * rx;
154 ry2 = ry * ry;
155 }
156
157 c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
158
159 //Check if there is no possible solution
160 //(i.e. we can't do a square root of a negative value)
161 if (c < 0.0f) {
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));
165 rx *= scale;
166 ry *= scale;
167 //Update rx2 and ry2
168 rx2 = rx * rx;
169 ry2 = ry * ry;
170
171 //Step 2 (F6.5.2) - simplified since c == 0.0
172 cxp = 0.0f;
173 cyp = 0.0f;
174 //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
175 cx = 0.0f;
176 cy = 0.0f;
177 } else {
178 //Complete c calculation
179 c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
180 //Inverse sign if Fa == Fs
181 if (largeArc == sweep) c = -c;
182
183 //Step 2 (F6.5.2)
184 cxp = c * (rx * y1p / ry);
185 cyp = c * (-ry * x1p / rx);
186
187 //Step 3 (F6.5.3 first part)
188 cx = cosPhi * cxp - sinPhi * cyp;
189 cy = sinPhi * cxp + cosPhi * cyp;
190 }
191
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;
195
196 //Step 4 (F6.5.4)
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 = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
201 theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
202
203 nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
204 deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
205
206 if (sweep) {
207 //Ensure delta theta < 0 or else add 360 degrees
208 if (deltaTheta < 0.0f) deltaTheta += 2.0f * MATH_PI;
209 } else {
210 //Ensure delta theta > 0 or else substract 360 degrees
211 if (deltaTheta > 0.0f) deltaTheta -= 2.0f * MATH_PI;
212 }
213
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 / MATH_PI2) + 1.0f);
219 delta = deltaTheta / segments;
220
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);
223
224 cosPhiRx = cosPhi * rx;
225 cosPhiRy = cosPhi * ry;
226 sinPhiRx = sinPhi * rx;
227 sinPhiRy = sinPhi * ry;
228
229 cosTheta1 = cosf(theta1);
230 sinTheta1 = sinf(theta1);
231
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);
238 Point p[3];
239
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);
243
244 //End point (for this segment)
245 ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
246 ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
247
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);
252 p[0] = {c1x, c1y};
253 p[1] = {c2x, c2y};
254 p[2] = {ex, ey};
255 pts->push(p[0]);
256 pts->push(p[1]);
257 pts->push(p[2]);
258 *curCtl = p[1];
259 *cur = p[2];
260
261 //Next start point is the current end point (same for angle)
262 sx = ex;
263 sy = ey;
264 theta1 = theta2;
265 //Avoid recomputations
266 cosTheta1 = cosTheta2;
267 sinTheta1 = sinTheta2;
268 }
269 }
270
_numberCount(char cmd)271 static int _numberCount(char cmd)
272 {
273 int count = 0;
274 switch (cmd) {
275 case 'M':
276 case 'm':
277 case 'L':
278 case 'l':
279 case 'T':
280 case 't': {
281 count = 2;
282 break;
283 }
284 case 'C':
285 case 'c':
286 case 'E':
287 case 'e': {
288 count = 6;
289 break;
290 }
291 case 'H':
292 case 'h':
293 case 'V':
294 case 'v': {
295 count = 1;
296 break;
297 }
298 case 'S':
299 case 's':
300 case 'Q':
301 case 'q': {
302 count = 4;
303 break;
304 }
305 case 'A':
306 case 'a': {
307 count = 7;
308 break;
309 }
310 default:
311 break;
312 }
313 return count;
314 }
315
316
_processCommand(Array<PathCommand> * cmds,Array<Point> * pts,char cmd,float * arr,int count,Point * cur,Point * curCtl,Point * startPoint,bool * isQuadratic,bool * closed)317 static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
318 {
319 switch (cmd) {
320 case 'm':
321 case 'l':
322 case 'c':
323 case 's':
324 case 'q':
325 case 't': {
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;
329 }
330 break;
331 }
332 case 'h': {
333 arr[0] = arr[0] + cur->x;
334 break;
335 }
336 case 'v': {
337 arr[0] = arr[0] + cur->y;
338 break;
339 }
340 case 'a': {
341 arr[5] = arr[5] + cur->x;
342 arr[6] = arr[6] + cur->y;
343 break;
344 }
345 default: {
346 break;
347 }
348 }
349
350 switch (cmd) {
351 case 'm':
352 case 'M': {
353 Point p = {arr[0], arr[1]};
354 cmds->push(PathCommand::MoveTo);
355 pts->push(p);
356 *cur = {arr[0], arr[1]};
357 *startPoint = {arr[0], arr[1]};
358 break;
359 }
360 case 'l':
361 case 'L': {
362 Point p = {arr[0], arr[1]};
363 cmds->push(PathCommand::LineTo);
364 pts->push(p);
365 *cur = {arr[0], arr[1]};
366 break;
367 }
368 case 'c':
369 case 'C': {
370 Point p[3];
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]};
375 pts->push(p[0]);
376 pts->push(p[1]);
377 pts->push(p[2]);
378 *curCtl = p[1];
379 *cur = p[2];
380 *isQuadratic = false;
381 break;
382 }
383 case 's':
384 case 'S': {
385 Point p[3], ctrl;
386 if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
387 !(*isQuadratic)) {
388 ctrl.x = 2 * cur->x - curCtl->x;
389 ctrl.y = 2 * cur->y - curCtl->y;
390 } else {
391 ctrl = *cur;
392 }
393 cmds->push(PathCommand::CubicTo);
394 p[0] = ctrl;
395 p[1] = {arr[0], arr[1]};
396 p[2] = {arr[2], arr[3]};
397 pts->push(p[0]);
398 pts->push(p[1]);
399 pts->push(p[2]);
400 *curCtl = p[1];
401 *cur = p[2];
402 *isQuadratic = false;
403 break;
404 }
405 case 'q':
406 case 'Q': {
407 Point p[3];
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]};
416 pts->push(p[0]);
417 pts->push(p[1]);
418 pts->push(p[2]);
419 *curCtl = {arr[0], arr[1]};
420 *cur = p[2];
421 *isQuadratic = true;
422 break;
423 }
424 case 't':
425 case 'T': {
426 Point p[3], ctrl;
427 if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
428 *isQuadratic) {
429 ctrl.x = 2 * cur->x - curCtl->x;
430 ctrl.y = 2 * cur->y - curCtl->y;
431 } else {
432 ctrl = *cur;
433 }
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]};
442 pts->push(p[0]);
443 pts->push(p[1]);
444 pts->push(p[2]);
445 *curCtl = {ctrl.x, ctrl.y};
446 *cur = p[2];
447 *isQuadratic = true;
448 break;
449 }
450 case 'h':
451 case 'H': {
452 Point p = {arr[0], cur->y};
453 cmds->push(PathCommand::LineTo);
454 pts->push(p);
455 cur->x = arr[0];
456 break;
457 }
458 case 'v':
459 case 'V': {
460 Point p = {cur->x, arr[0]};
461 cmds->push(PathCommand::LineTo);
462 pts->push(p);
463 cur->y = arr[0];
464 break;
465 }
466 case 'z':
467 case 'Z': {
468 cmds->push(PathCommand::Close);
469 *cur = *startPoint;
470 *closed = true;
471 break;
472 }
473 case 'a':
474 case 'A': {
475 if (tvg::zero(arr[0]) || tvg::zero(arr[1])) {
476 Point p = {arr[5], arr[6]};
477 cmds->push(PathCommand::LineTo);
478 pts->push(p);
479 *cur = {arr[5], arr[6]};
480 } else if (!tvg::equal(cur->x, arr[5]) || !tvg::equal(cur->y, arr[6])) {
481 _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]);
482 *cur = *curCtl = {arr[5], arr[6]};
483 *isQuadratic = false;
484 }
485 break;
486 }
487 default: {
488 return false;
489 }
490 }
491 return true;
492 }
493
494
_nextCommand(char * path,char * cmd,float * arr,int * count,bool * closed)495 static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* closed)
496 {
497 int large, sweep;
498
499 path = _skipComma(path);
500 if (isalpha(*path)) {
501 *cmd = *path;
502 path++;
503 *count = _numberCount(*cmd);
504 } else {
505 if (*cmd == 'm') *cmd = 'l';
506 else if (*cmd == 'M') *cmd = 'L';
507 else {
508 if (*closed) return nullptr;
509 }
510 }
511 if (*count == 7) {
512 //Special case for arc command
513 if (_parseNumber(&path, &arr[0])) {
514 if (_parseNumber(&path, &arr[1])) {
515 if (_parseNumber(&path, &arr[2])) {
516 if (_parseFlag(&path, &large)) {
517 if (_parseFlag(&path, &sweep)) {
518 if (_parseNumber(&path, &arr[5])) {
519 if (_parseNumber(&path, &arr[6])) {
520 arr[3] = (float)large;
521 arr[4] = (float)sweep;
522 return path;
523 }
524 }
525 }
526 }
527 }
528 }
529 }
530 *count = 0;
531 return NULL;
532 }
533 for (int i = 0; i < *count; i++) {
534 if (!_parseNumber(&path, &arr[i])) {
535 *count = 0;
536 return NULL;
537 }
538 path = _skipComma(path);
539 }
540 return path;
541 }
542
543
544 /************************************************************************/
545 /* External Class Implementation */
546 /************************************************************************/
547
548
svgPathToShape(const char * svgPath,Shape * shape)549 bool svgPathToShape(const char* svgPath, Shape* shape)
550 {
551 float numberArray[7];
552 int numberCount = 0;
553 Point cur = { 0, 0 };
554 Point curCtl = { 0, 0 };
555 Point startPoint = { 0, 0 };
556 char cmd = 0;
557 bool isQuadratic = false;
558 bool closed = false;
559 char* path = (char*)svgPath;
560
561 auto& pts = P(shape)->rs.path.pts;
562 auto& cmds = P(shape)->rs.path.cmds;
563 auto lastCmds = cmds.count;
564
565 while ((path[0] != '\0')) {
566 path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
567 if (!path) break;
568 closed = false;
569 if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
570 }
571
572 if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
573 return true;
574 }
575
576 #endif /* LV_USE_THORVG_INTERNAL */
577
578