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