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 #include <string.h>
27 #include <math.h>
28 #include "tvgSwCommon.h"
29 
30 /************************************************************************/
31 /* Internal Class Implementation                                        */
32 /************************************************************************/
33 
34 static constexpr auto SW_STROKE_TAG_POINT = 1;
35 static constexpr auto SW_STROKE_TAG_CUBIC = 2;
36 static constexpr auto SW_STROKE_TAG_BEGIN = 4;
37 static constexpr auto SW_STROKE_TAG_END = 8;
38 
SIDE_TO_ROTATE(const int32_t s)39 static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
40 {
41     return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
42 }
43 
44 
SCALE(const SwStroke & stroke,SwPoint & pt)45 static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
46 {
47     pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
48     pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
49 }
50 
51 
_growBorder(SwStrokeBorder * border,uint32_t newPts)52 static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
53 {
54     auto maxOld = border->maxPts;
55     auto maxNew = border->ptsCnt + newPts;
56 
57     if (maxNew <= maxOld) return;
58 
59     auto maxCur = maxOld;
60 
61     while (maxCur < maxNew)
62         maxCur += (maxCur >> 1) + 16;
63     //OPTIMIZE: use mempool!
64     border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
65     border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
66     border->maxPts = maxCur;
67 }
68 
69 
_borderClose(SwStrokeBorder * border,bool reverse)70 static void _borderClose(SwStrokeBorder* border, bool reverse)
71 {
72     auto start = border->start;
73     auto count = border->ptsCnt;
74 
75     //Don't record empty paths!
76     if (count <= start + 1U) {
77         border->ptsCnt = start;
78     } else {
79         /* Copy the last point to the start of this sub-path,
80            since it contains the adjusted starting coordinates */
81         border->ptsCnt = --count;
82         border->pts[start] = border->pts[count];
83 
84         if (reverse) {
85             //reverse the points
86             auto pt1 = border->pts + start + 1;
87             auto pt2 = border->pts + count - 1;
88 
89             while (pt1 < pt2) {
90                 auto tmp = *pt1;
91                 *pt1 = *pt2;
92                 *pt2 = tmp;
93                 ++pt1;
94                 --pt2;
95             }
96 
97             //reverse the tags
98             auto tag1 = border->tags + start + 1;
99             auto tag2 = border->tags + count - 1;
100 
101             while (tag1 < tag2) {
102                 auto tmp = *tag1;
103                 *tag1 = *tag2;
104                 *tag2 = tmp;
105                 ++tag1;
106                 --tag2;
107             }
108         }
109 
110         border->tags[start] |= SW_STROKE_TAG_BEGIN;
111         border->tags[count - 1] |=  SW_STROKE_TAG_END;
112     }
113 
114     border->start = -1;
115     border->movable = false;
116 }
117 
118 
_borderCubicTo(SwStrokeBorder * border,const SwPoint & ctrl1,const SwPoint & ctrl2,const SwPoint & to)119 static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
120 {
121     _growBorder(border, 3);
122 
123     auto pt = border->pts + border->ptsCnt;
124     auto tag = border->tags + border->ptsCnt;
125 
126     pt[0] = ctrl1;
127     pt[1] = ctrl2;
128     pt[2] = to;
129 
130     tag[0] = SW_STROKE_TAG_CUBIC;
131     tag[1] = SW_STROKE_TAG_CUBIC;
132     tag[2] = SW_STROKE_TAG_POINT;
133 
134     border->ptsCnt += 3;
135     border->movable = false;
136 }
137 
138 
_borderArcTo(SwStrokeBorder * border,const SwPoint & center,SwFixed radius,SwFixed angleStart,SwFixed angleDiff,SwStroke & stroke)139 static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
140 {
141     constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
142     SwPoint a = {static_cast<SwCoord>(radius), 0};
143     mathRotate(a, angleStart);
144     SCALE(stroke, a);
145     a += center;
146 
147     auto total = angleDiff;
148     auto angle = angleStart;
149     auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
150 
151     while (total != 0) {
152         auto step = total;
153         if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
154         else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
155 
156         auto next = angle + step;
157         auto theta = step;
158         if (theta < 0) theta = -theta;
159 
160         theta >>= 1;
161 
162         //compute end point
163         SwPoint b = {static_cast<SwCoord>(radius), 0};
164         mathRotate(b, next);
165         SCALE(stroke, b);
166         b += center;
167 
168         //compute first and second control points
169         auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
170 
171         SwPoint a2 = {static_cast<SwCoord>(length), 0};
172         mathRotate(a2, angle + rotate);
173         SCALE(stroke, a2);
174         a2 += a;
175 
176         SwPoint b2 = {static_cast<SwCoord>(length), 0};
177         mathRotate(b2, next - rotate);
178         SCALE(stroke, b2);
179         b2 += b;
180 
181         //add cubic arc
182         _borderCubicTo(border, a2, b2, b);
183 
184         //process the rest of the arc?
185         a = b;
186         total -= step;
187         angle = next;
188     }
189 }
190 
191 
_borderLineTo(SwStrokeBorder * border,const SwPoint & to,bool movable)192 static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
193 {
194     if (border->movable) {
195         //move last point
196         border->pts[border->ptsCnt - 1] = to;
197     } else {
198         //don't add zero-length line_to
199         if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
200 
201         _growBorder(border, 1);
202         border->pts[border->ptsCnt] = to;
203         border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
204         border->ptsCnt += 1;
205     }
206 
207     border->movable = movable;
208 }
209 
210 
_borderMoveTo(SwStrokeBorder * border,SwPoint & to)211 static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
212 {
213     //close current open path if any?
214     if (border->start >= 0) _borderClose(border, false);
215 
216     border->start = border->ptsCnt;
217     border->movable = false;
218 
219     _borderLineTo(border, to, false);
220 }
221 
222 
_arcTo(SwStroke & stroke,int32_t side)223 static void _arcTo(SwStroke& stroke, int32_t side)
224 {
225     auto border = stroke.borders + side;
226     auto rotate = SIDE_TO_ROTATE(side);
227     auto total = mathDiff(stroke.angleIn, stroke.angleOut);
228     if (total == SW_ANGLE_PI) total = -rotate * 2;
229 
230     _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
231     border->movable = false;
232 }
233 
234 
_outside(SwStroke & stroke,int32_t side,SwFixed lineLength)235 static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
236 {
237     auto border = stroke.borders + side;
238 
239     if (stroke.join == StrokeJoin::Round) {
240         _arcTo(stroke, side);
241     } else {
242         //this is a mitered (pointed) or beveled (truncated) corner
243         auto rotate = SIDE_TO_ROTATE(side);
244         auto bevel = stroke.join == StrokeJoin::Bevel;
245         SwFixed phi = 0;
246         SwFixed thcos = 0;
247 
248         if (!bevel) {
249             auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
250             if (theta == SW_ANGLE_PI) {
251                 theta = rotate;
252                 phi = stroke.angleIn;
253             } else {
254                 theta /= 2;
255                 phi = stroke.angleIn + theta + rotate;
256             }
257 
258             thcos = mathCos(theta);
259             auto sigma = mathMultiply(stroke.miterlimit, thcos);
260 
261             //is miter limit exceeded?
262             if (sigma < 0x10000L) bevel = true;
263         }
264 
265         //this is a bevel (broken angle)
266         if (bevel) {
267             SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
268             mathRotate(delta, stroke.angleOut + rotate);
269             SCALE(stroke, delta);
270             delta += stroke.center;
271             border->movable = false;
272             _borderLineTo(border, delta, false);
273         //this is a miter (intersection)
274         } else {
275             auto length = mathDivide(stroke.width, thcos);
276             SwPoint delta = {static_cast<SwCoord>(length), 0};
277             mathRotate(delta, phi);
278             SCALE(stroke, delta);
279             delta += stroke.center;
280             _borderLineTo(border, delta, false);
281 
282             /* Now add and end point
283                Only needed if not lineto (lineLength is zero for curves) */
284             if (lineLength == 0) {
285                 delta = {static_cast<SwCoord>(stroke.width), 0};
286                 mathRotate(delta, stroke.angleOut + rotate);
287                 SCALE(stroke, delta);
288                 delta += stroke.center;
289                 _borderLineTo(border, delta, false);
290             }
291         }
292     }
293 }
294 
295 
_inside(SwStroke & stroke,int32_t side,SwFixed lineLength)296 static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
297 {
298     auto border = stroke.borders + side;
299     auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
300     SwPoint delta;
301     bool intersect = false;
302 
303     /* Only intersect borders if between two line_to's and both
304        lines are long enough (line length is zero for curves). */
305     if (border->movable && lineLength > 0) {
306         //compute minimum required length of lines
307         SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
308         if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
309     }
310 
311     auto rotate = SIDE_TO_ROTATE(side);
312 
313     if (!intersect) {
314         delta = {static_cast<SwCoord>(stroke.width), 0};
315         mathRotate(delta, stroke.angleOut + rotate);
316         SCALE(stroke, delta);
317         delta += stroke.center;
318         border->movable = false;
319     } else {
320         //compute median angle
321         auto phi = stroke.angleIn + theta;
322         auto thcos = mathCos(theta);
323         delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
324         mathRotate(delta, phi + rotate);
325         SCALE(stroke, delta);
326         delta += stroke.center;
327     }
328 
329     _borderLineTo(border, delta, false);
330 }
331 
332 
_processCorner(SwStroke & stroke,SwFixed lineLength)333 void _processCorner(SwStroke& stroke, SwFixed lineLength)
334 {
335     auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
336 
337     //no specific corner processing is required if the turn is 0
338     if (turn == 0) return;
339 
340     //when we turn to the right, the inside side is 0
341     int32_t inside = 0;
342 
343     //otherwise, the inside is 1
344     if (turn < 0) inside = 1;
345 
346     //process the inside
347     _inside(stroke, inside, lineLength);
348 
349     //process the outside
350     _outside(stroke, 1 - inside, lineLength);
351 }
352 
353 
_firstSubPath(SwStroke & stroke,SwFixed startAngle,SwFixed lineLength)354 void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
355 {
356     SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
357     mathRotate(delta, startAngle + SW_ANGLE_PI2);
358     SCALE(stroke, delta);
359 
360     auto pt = stroke.center + delta;
361     auto border = stroke.borders;
362     _borderMoveTo(border, pt);
363 
364     pt = stroke.center - delta;
365     ++border;
366     _borderMoveTo(border, pt);
367 
368     /* Save angle, position and line length for last join
369        lineLength is zero for curves */
370     stroke.subPathAngle = startAngle;
371     stroke.firstPt = false;
372     stroke.subPathLineLength = lineLength;
373 }
374 
375 
_lineTo(SwStroke & stroke,const SwPoint & to)376 static void _lineTo(SwStroke& stroke, const SwPoint& to)
377 {
378     auto delta = to - stroke.center;
379 
380     //a zero-length lineto is a no-op; avoid creating a spurious corner
381     if (delta.zero()) return;
382 
383     /* The lineLength is used to determine the intersection of strokes outlines.
384        The scale needs to be reverted since the stroke width has not been scaled.
385        An alternative option is to scale the width of the stroke properly by
386        calculating the mixture of the sx/sy rating on the stroke direction. */
387     delta.x = static_cast<SwCoord>(delta.x / stroke.sx);
388     delta.y = static_cast<SwCoord>(delta.y / stroke.sy);
389     auto lineLength = mathLength(delta);
390     auto angle = mathAtan(delta);
391 
392     delta = {static_cast<SwCoord>(stroke.width), 0};
393     mathRotate(delta, angle + SW_ANGLE_PI2);
394     SCALE(stroke, delta);
395 
396     //process corner if necessary
397     if (stroke.firstPt) {
398         /* This is the first segment of a subpath. We need to add a point to each border
399         at their respective starting point locations. */
400         _firstSubPath(stroke, angle, lineLength);
401     } else {
402         //process the current corner
403         stroke.angleOut = angle;
404         _processCorner(stroke, lineLength);
405     }
406 
407     //now add a line segment to both the inside and outside paths
408     auto border = stroke.borders;
409     auto side = 1;
410 
411     while (side >= 0) {
412         auto pt = to + delta;
413 
414         //the ends of lineto borders are movable
415         _borderLineTo(border, pt, true);
416 
417         delta.x = -delta.x;
418         delta.y = -delta.y;
419 
420         --side;
421         ++border;
422     }
423 
424     stroke.angleIn = angle;
425     stroke.center = to;
426     stroke.lineLength = lineLength;
427 }
428 
429 
_cubicTo(SwStroke & stroke,const SwPoint & ctrl1,const SwPoint & ctrl2,const SwPoint & to)430 static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
431 {
432     SwPoint bezStack[37];   //TODO: static?
433     auto limit = bezStack + 32;
434     auto arc = bezStack;
435     auto firstArc = true;
436     arc[0] = to;
437     arc[1] = ctrl2;
438     arc[2] = ctrl1;
439     arc[3] = stroke.center;
440 
441     while (arc >= bezStack) {
442         SwFixed angleIn, angleOut, angleMid;
443 
444         //initialize with current direction
445         angleIn = angleOut = angleMid = stroke.angleIn;
446 
447         auto valid = mathCubicAngle(arc, angleIn, angleMid, angleOut);
448 
449         //valid size
450         if (valid > 0 && arc < limit) {
451             if (stroke.firstPt) stroke.angleIn = angleIn;
452             mathSplitCubic(arc);
453             arc += 3;
454             continue;
455         }
456 
457         //ignoreable size
458         if (valid < 0 && arc == bezStack) {
459             stroke.center = to;
460             return;
461         }
462 
463         //small size
464         if (firstArc) {
465             firstArc = false;
466             //process corner if necessary
467             if (stroke.firstPt) {
468                 _firstSubPath(stroke, angleIn, 0);
469             } else {
470                 stroke.angleOut = angleIn;
471                 _processCorner(stroke, 0);
472             }
473         } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
474             //if the deviation from one arc to the next is too great add a round corner
475             stroke.center = arc[3];
476             stroke.angleOut = angleIn;
477             stroke.join = StrokeJoin::Round;
478 
479             _processCorner(stroke, 0);
480 
481             //reinstate line join style
482             stroke.join = stroke.joinSaved;
483         }
484 
485         //the arc's angle is small enough; we can add it directly to each border
486         auto theta1 = mathDiff(angleIn, angleMid) / 2;
487         auto theta2 = mathDiff(angleMid, angleOut) / 2;
488         auto phi1 = mathMean(angleIn, angleMid);
489         auto phi2 = mathMean(angleMid, angleOut);
490         auto length1 = mathDivide(stroke.width, mathCos(theta1));
491         auto length2 = mathDivide(stroke.width, mathCos(theta2));
492         SwFixed alpha0 = 0;
493 
494         //compute direction of original arc
495         if (stroke.handleWideStrokes) {
496             alpha0 = mathAtan(arc[0] - arc[3]);
497         }
498 
499         auto border = stroke.borders;
500         int32_t side = 0;
501 
502         while (side < 2) {
503             auto rotate = SIDE_TO_ROTATE(side);
504 
505             //compute control points
506             SwPoint _ctrl1 = {static_cast<SwCoord>(length1), 0};
507             mathRotate(_ctrl1, phi1 + rotate);
508             SCALE(stroke, _ctrl1);
509             _ctrl1 += arc[2];
510 
511             SwPoint _ctrl2 = {static_cast<SwCoord>(length2), 0};
512             mathRotate(_ctrl2, phi2 + rotate);
513             SCALE(stroke, _ctrl2);
514             _ctrl2 += arc[1];
515 
516             //compute end point
517             SwPoint _end = {static_cast<SwCoord>(stroke.width), 0};
518             mathRotate(_end, angleOut + rotate);
519             SCALE(stroke, _end);
520             _end += arc[0];
521 
522             if (stroke.handleWideStrokes) {
523                 /* determine whether the border radius is greater than the radius of
524                    curvature of the original arc */
525                 auto _start = border->pts[border->ptsCnt - 1];
526                 auto alpha1 = mathAtan(_end - _start);
527 
528                 //is the direction of the border arc opposite to that of the original arc?
529                 if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) {
530 
531                     //use the sine rule to find the intersection point
532                     auto beta = mathAtan(arc[3] - _start);
533                     auto gamma = mathAtan(arc[0] - _end);
534                     auto bvec = _end - _start;
535                     auto blen = mathLength(bvec);
536                     auto sinA = abs(mathSin(alpha1 - gamma));
537                     auto sinB = abs(mathSin(beta - gamma));
538                     auto alen = mathMulDiv(blen, sinA, sinB);
539 
540                     SwPoint delta = {static_cast<SwCoord>(alen), 0};
541                     mathRotate(delta, beta);
542                     delta += _start;
543 
544                     //circumnavigate the negative sector backwards
545                     border->movable = false;
546                     _borderLineTo(border, delta, false);
547                     _borderLineTo(border, _end, false);
548                     _borderCubicTo(border, _ctrl2, _ctrl1, _start);
549 
550                     //and then move to the endpoint
551                     _borderLineTo(border, _end, false);
552 
553                     ++side;
554                     ++border;
555                     continue;
556                 }
557             }
558             _borderCubicTo(border, _ctrl1, _ctrl2, _end);
559             ++side;
560             ++border;
561         }
562         arc -= 3;
563         stroke.angleIn = angleOut;
564     }
565     stroke.center = to;
566 }
567 
568 
_addCap(SwStroke & stroke,SwFixed angle,int32_t side)569 static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
570 {
571     if (stroke.cap == StrokeCap::Square) {
572         auto rotate = SIDE_TO_ROTATE(side);
573         auto border = stroke.borders + side;
574 
575         SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
576         mathRotate(delta, angle);
577         SCALE(stroke, delta);
578 
579         SwPoint delta2 = {static_cast<SwCoord>(stroke.width), 0};
580         mathRotate(delta2, angle + rotate);
581         SCALE(stroke, delta2);
582         delta += stroke.center + delta2;
583 
584         _borderLineTo(border, delta, false);
585 
586         delta = {static_cast<SwCoord>(stroke.width), 0};
587         mathRotate(delta, angle);
588         SCALE(stroke, delta);
589 
590         delta2 = {static_cast<SwCoord>(stroke.width), 0};
591         mathRotate(delta2, angle - rotate);
592         SCALE(stroke, delta2);
593         delta += delta2 + stroke.center;
594 
595         _borderLineTo(border, delta, false);
596 
597     } else if (stroke.cap == StrokeCap::Round) {
598 
599         stroke.angleIn = angle;
600         stroke.angleOut = angle + SW_ANGLE_PI;
601         _arcTo(stroke, side);
602         return;
603 
604     } else {  //Butt
605         auto rotate = SIDE_TO_ROTATE(side);
606         auto border = stroke.borders + side;
607 
608         SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
609         mathRotate(delta, angle + rotate);
610         SCALE(stroke, delta);
611         delta += stroke.center;
612 
613         _borderLineTo(border, delta, false);
614 
615         delta = {static_cast<SwCoord>(stroke.width), 0};
616         mathRotate(delta, angle - rotate);
617         SCALE(stroke, delta);
618         delta += stroke.center;
619 
620         _borderLineTo(border, delta, false);
621     }
622 }
623 
624 
_addReverseLeft(SwStroke & stroke,bool opened)625 static void _addReverseLeft(SwStroke& stroke, bool opened)
626 {
627     auto right = stroke.borders + 0;
628     auto left = stroke.borders + 1;
629     auto newPts = left->ptsCnt - left->start;
630 
631     if (newPts <= 0) return;
632 
633     _growBorder(right, newPts);
634 
635     auto dstPt = right->pts + right->ptsCnt;
636     auto dstTag = right->tags + right->ptsCnt;
637     auto srcPt = left->pts + left->ptsCnt - 1;
638     auto srcTag = left->tags + left->ptsCnt - 1;
639 
640     while (srcPt >= left->pts + left->start) {
641         *dstPt = *srcPt;
642         *dstTag = *srcTag;
643 
644         if (opened) {
645              dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
646         } else {
647             //switch begin/end tags if necessary
648             auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
649             if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
650               dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
651         }
652         --srcPt;
653         --srcTag;
654         ++dstPt;
655         ++dstTag;
656     }
657 
658     left->ptsCnt = left->start;
659     right->ptsCnt += newPts;
660     right->movable = false;
661     left->movable = false;
662 }
663 
664 
_beginSubPath(SwStroke & stroke,const SwPoint & to,bool closed)665 static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed)
666 {
667     /* We cannot process the first point because there is not enough
668        information regarding its corner/cap. Later, it will be processed
669        in the _endSubPath() */
670 
671     stroke.firstPt = true;
672     stroke.center = to;
673     stroke.closedSubPath = closed;
674 
675     /* Determine if we need to check whether the border radius is greater
676        than the radius of curvature of a curve, to handle this case specially.
677        This is only required if bevel joins or butt caps may be created because
678        round & miter joins and round & square caps cover the negative sector
679        created with wide strokes. */
680     if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt))
681         stroke.handleWideStrokes = true;
682     else
683         stroke.handleWideStrokes = false;
684 
685     stroke.ptStartSubPath = to;
686     stroke.angleIn = 0;
687 }
688 
689 
_endSubPath(SwStroke & stroke)690 static void _endSubPath(SwStroke& stroke)
691 {
692     if (stroke.closedSubPath) {
693         //close the path if needed
694         if (stroke.center != stroke.ptStartSubPath)
695             _lineTo(stroke, stroke.ptStartSubPath);
696 
697         //process the corner
698         stroke.angleOut = stroke.subPathAngle;
699         auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
700 
701         //No specific corner processing is required if the turn is 0
702         if (turn != 0) {
703             //when we turn to the right, the inside is 0
704             int32_t inside = 0;
705 
706             //otherwise, the inside is 1
707             if (turn < 0) inside = 1;
708 
709             _inside(stroke, inside, stroke.subPathLineLength);        //inside
710             _outside(stroke, 1 - inside, stroke.subPathLineLength);   //outside
711         }
712 
713         _borderClose(stroke.borders + 0, false);
714         _borderClose(stroke.borders + 1, true);
715     } else {
716         auto right = stroke.borders;
717 
718         /* all right, this is an opened path, we need to add a cap between
719            right & left, add the reverse of left, then add a final cap
720            between left & right */
721         _addCap(stroke, stroke.angleIn, 0);
722 
723         //add reversed points from 'left' to 'right'
724         _addReverseLeft(stroke, true);
725 
726         //now add the final cap
727         stroke.center = stroke.ptStartSubPath;
728         _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
729 
730         /* now end the right subpath accordingly. The left one is rewind
731            and doesn't need further processing */
732         _borderClose(right, false);
733     }
734 }
735 
736 
_getCounts(SwStrokeBorder * border,uint32_t & ptsCnt,uint32_t & cntrsCnt)737 static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
738 {
739     auto count = border->ptsCnt;
740     auto tags = border->tags;
741     uint32_t _ptsCnt = 0;
742     uint32_t _cntrsCnt = 0;
743     bool inCntr = false;
744 
745     while (count > 0) {
746         if (tags[0] & SW_STROKE_TAG_BEGIN) {
747             if (inCntr) goto fail;
748             inCntr = true;
749         } else if (!inCntr) goto fail;
750 
751         if (tags[0] & SW_STROKE_TAG_END) {
752             inCntr = false;
753             ++_cntrsCnt;
754         }
755         --count;
756         ++_ptsCnt;
757         ++tags;
758     }
759 
760     if (inCntr) goto fail;
761 
762     ptsCnt = _ptsCnt;
763     cntrsCnt = _cntrsCnt;
764 
765     return;
766 
767 fail:
768     ptsCnt = 0;
769     cntrsCnt = 0;
770 }
771 
772 
_exportBorderOutline(const SwStroke & stroke,SwOutline * outline,uint32_t side)773 static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
774 {
775     auto border = stroke.borders + side;
776     if (border->ptsCnt == 0) return;
777 
778     memcpy(outline->pts.data + outline->pts.count, border->pts, border->ptsCnt * sizeof(SwPoint));
779 
780     auto cnt = border->ptsCnt;
781     auto src = border->tags;
782     auto tags = outline->types.data + outline->types.count;
783     auto idx = outline->pts.count;
784 
785     while (cnt > 0) {
786         if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
787         else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
788         else TVGERR("SW_ENGINE", "Invalid stroke tag was given! = %d", *src);
789         if (*src & SW_STROKE_TAG_END) outline->cntrs.push(idx);
790         ++src;
791         ++tags;
792         ++idx;
793         --cnt;
794     }
795     outline->pts.count += border->ptsCnt;
796     outline->types.count += border->ptsCnt;
797 }
798 
799 
800 /************************************************************************/
801 /* External Class Implementation                                        */
802 /************************************************************************/
803 
strokeFree(SwStroke * stroke)804 void strokeFree(SwStroke* stroke)
805 {
806     if (!stroke) return;
807 
808     //free borders
809     if (stroke->borders[0].pts) free(stroke->borders[0].pts);
810     if (stroke->borders[0].tags) free(stroke->borders[0].tags);
811     if (stroke->borders[1].pts) free(stroke->borders[1].pts);
812     if (stroke->borders[1].tags) free(stroke->borders[1].tags);
813 
814     fillFree(stroke->fill);
815     stroke->fill = nullptr;
816 
817     free(stroke);
818 }
819 
820 
strokeReset(SwStroke * stroke,const RenderShape * rshape,const Matrix & transform)821 void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix& transform)
822 {
823     stroke->sx = sqrtf(powf(transform.e11, 2.0f) + powf(transform.e21, 2.0f));
824     stroke->sy = sqrtf(powf(transform.e12, 2.0f) + powf(transform.e22, 2.0f));
825     stroke->width = HALF_STROKE(rshape->strokeWidth());
826     stroke->cap = rshape->strokeCap();
827     stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
828 
829     //Save line join: it can be temporarily changed when stroking curves...
830     stroke->joinSaved = stroke->join = rshape->strokeJoin();
831 
832     stroke->borders[0].ptsCnt = 0;
833     stroke->borders[0].start = -1;
834     stroke->borders[1].ptsCnt = 0;
835     stroke->borders[1].start = -1;
836 }
837 
838 
strokeParseOutline(SwStroke * stroke,const SwOutline & outline)839 bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
840 {
841     uint32_t first = 0;
842     uint32_t i = 0;
843 
844     for (auto cntr = outline.cntrs.begin(); cntr < outline.cntrs.end(); ++cntr, ++i) {
845         auto last = *cntr;           //index of last point in contour
846         auto limit = outline.pts.data + last;
847 
848         //Skip empty points
849         if (last <= first) {
850             first = last + 1;
851             continue;
852         }
853 
854         auto start = outline.pts[first];
855         auto pt = outline.pts.data + first;
856         auto types = outline.types.data + first;
857         auto type = types[0];
858 
859         //A contour cannot start with a cubic control point
860         if (type == SW_CURVE_TYPE_CUBIC) return false;
861         ++types;
862 
863         auto closed =  outline.closed.data ? outline.closed.data[i]: false;
864 
865         _beginSubPath(*stroke, start, closed);
866 
867         while (pt < limit) {
868             //emit a single line_to
869             if (types[0] == SW_CURVE_TYPE_POINT) {
870                 ++pt;
871                 ++types;
872                 _lineTo(*stroke, *pt);
873             //types cubic
874             } else {
875                 pt += 3;
876                 types += 3;
877                 if (pt <= limit) _cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
878                 else if (pt - 1 == limit) _cubicTo(*stroke, pt[-2], pt[-1], start);
879                 else goto close;
880             }
881         }
882     close:
883         if (!stroke->firstPt) _endSubPath(*stroke);
884         first = last + 1;
885     }
886     return true;
887 }
888 
889 
strokeExportOutline(SwStroke * stroke,SwMpool * mpool,unsigned tid)890 SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
891 {
892     uint32_t count1, count2, count3, count4;
893 
894     _getCounts(stroke->borders + 0, count1, count2);
895     _getCounts(stroke->borders + 1, count3, count4);
896 
897     auto ptsCnt = count1 + count3;
898     auto cntrsCnt = count2 + count4;
899 
900     auto outline = mpoolReqStrokeOutline(mpool, tid);
901     outline->pts.reserve(ptsCnt);
902     outline->types.reserve(ptsCnt);
903     outline->cntrs.reserve(cntrsCnt);
904 
905     _exportBorderOutline(*stroke, outline, 0);  //left
906     _exportBorderOutline(*stroke, outline, 1);  //right
907 
908     return outline;
909 }
910 
911 #endif /* LV_USE_THORVG_INTERNAL */
912 
913