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