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