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