2 * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved.
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:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
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
25 #include "tvgSwCommon.h"
27 /************************************************************************/
28 /* Internal Class Implementation */
29 /************************************************************************/
31 static constexpr auto SW_STROKE_TAG_POINT = 1;
32 static constexpr auto SW_STROKE_TAG_CUBIC = 2;
33 static constexpr auto SW_STROKE_TAG_BEGIN = 4;
34 static constexpr auto SW_STROKE_TAG_END = 8;
36 static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
38 return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
42 static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
44 pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
45 pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
49 static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
51 auto maxOld = border->maxPts;
52 auto maxNew = border->ptsCnt + newPts;
54 if (maxNew <= maxOld) return;
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;
67 static void _borderClose(SwStrokeBorder* border, bool reverse)
69 auto start = border->start;
70 auto count = border->ptsCnt;
72 //Don't record empty paths!
73 if (count <= start + 1U) {
74 border->ptsCnt = start;
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];
83 auto pt1 = border->pts + start + 1;
84 auto pt2 = border->pts + count - 1;
95 auto tag1 = border->tags + start + 1;
96 auto tag2 = border->tags + count - 1;
107 border->tags[start] |= SW_STROKE_TAG_BEGIN;
108 border->tags[count - 1] |= SW_STROKE_TAG_END;
112 border->movable = false;
116 static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
118 _growBorder(border, 3);
120 auto pt = border->pts + border->ptsCnt;
121 auto tag = border->tags + border->ptsCnt;
127 tag[0] = SW_STROKE_TAG_CUBIC;
128 tag[1] = SW_STROKE_TAG_CUBIC;
129 tag[2] = SW_STROKE_TAG_POINT;
133 border->movable = false;
137 static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
139 constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
140 SwPoint a = {static_cast<SwCoord>(radius), 0};
141 mathRotate(a, angleStart);
145 auto total = angleDiff;
146 auto angle = angleStart;
147 auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
151 if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
152 else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
154 auto next = angle + step;
156 if (theta < 0) theta = -theta;
161 SwPoint b = {static_cast<SwCoord>(radius), 0};
166 //compute first and second control points
167 auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
169 SwPoint a2 = {static_cast<SwCoord>(length), 0};
170 mathRotate(a2, angle + rotate);
174 SwPoint b2 = {static_cast<SwCoord>(length), 0};
175 mathRotate(b2, next - rotate);
180 _borderCubicTo(border, a2, b2, b);
182 //process the rest of the arc?
190 static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
192 if (border->movable) {
194 border->pts[border->ptsCnt - 1] = to;
197 //don't add zero-length line_to
198 if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
200 _growBorder(border, 1);
201 border->pts[border->ptsCnt] = to;
202 border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
206 border->movable = movable;
210 static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
212 //close current open path if any?
213 if (border->start >= 0) _borderClose(border, false);
215 border->start = border->ptsCnt;
216 border->movable = false;
218 _borderLineTo(border, to, false);
222 static void _arcTo(SwStroke& stroke, int32_t side)
224 auto border = stroke.borders + side;
225 auto rotate = SIDE_TO_ROTATE(side);
226 auto total = mathDiff(stroke.angleIn, stroke.angleOut);
227 if (total == SW_ANGLE_PI) total = -rotate * 2;
229 _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
230 border->movable = false;
234 static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
236 constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
238 auto border = stroke.borders + side;
240 if (stroke.join == StrokeJoin::Round) {
241 _arcTo(stroke, side);
243 //this is a mitered (pointed) or beveled (truncated) corner
244 auto rotate = SIDE_TO_ROTATE(side);
245 auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false;
250 auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
251 if (theta == SW_ANGLE_PI) {
253 phi = stroke.angleIn;
256 phi = stroke.angleIn + theta + rotate;
259 thcos = mathCos(theta);
260 auto sigma = mathMultiply(MITER_LIMIT, thcos);
262 //is miter limit exceeded?
263 if (sigma < 0x10000L) bevel = true;
266 //this is a bevel (broken angle)
268 SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
269 mathRotate(delta, stroke.angleOut + rotate);
270 SCALE(stroke, delta);
271 delta += stroke.center;
272 border->movable = false;
273 _borderLineTo(border, delta, false);
274 //this is a miter (intersection)
276 auto length = mathDivide(stroke.width, thcos);
277 SwPoint delta = {static_cast<SwCoord>(length), 0};
278 mathRotate(delta, phi);
279 SCALE(stroke, delta);
280 delta += stroke.center;
281 _borderLineTo(border, delta, false);
283 /* Now add and end point
284 Only needed if not lineto (lineLength is zero for curves) */
285 if (lineLength == 0) {
286 delta = {static_cast<SwCoord>(stroke.width), 0};
287 mathRotate(delta, stroke.angleOut + rotate);
288 SCALE(stroke, delta);
289 delta += stroke.center;
290 _borderLineTo(border, delta, false);
297 static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
299 auto border = stroke.borders + side;
300 auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
302 bool intersect = false;
304 /* Only intersect borders if between two line_to's and both
305 lines are long enough (line length is zero fur curves). */
306 if (border->movable && lineLength > 0) {
307 //compute minimum required length of lines
308 SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
309 if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
312 auto rotate = SIDE_TO_ROTATE(side);
315 delta = {static_cast<SwCoord>(stroke.width), 0};
316 mathRotate(delta, stroke.angleOut + rotate);
317 SCALE(stroke, delta);
318 delta += stroke.center;
319 border->movable = false;
321 //compute median angle
322 auto phi = stroke.angleIn + theta;
323 auto thcos = mathCos(theta);
324 delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
325 mathRotate(delta, phi + rotate);
326 SCALE(stroke, delta);
327 delta += stroke.center;
330 _borderLineTo(border, delta, false);
334 void _processCorner(SwStroke& stroke, SwFixed lineLength)
336 auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
338 //no specific corner processing is required if the turn is 0
339 if (turn == 0) return;
341 //when we turn to the right, the inside side is 0
344 //otherwise, the inside is 1
345 if (turn < 0) inside = 1;
348 _inside(stroke, inside, lineLength);
350 //process the outside
351 _outside(stroke, 1 - inside, lineLength);
355 void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
357 SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
358 mathRotate(delta, startAngle + SW_ANGLE_PI2);
359 SCALE(stroke, delta);
361 auto pt = stroke.center + delta;
362 auto border = stroke.borders;
363 _borderMoveTo(border, pt);
365 pt = stroke.center - delta;
367 _borderMoveTo(border, pt);
369 /* Save angle, position and line length for last join
370 lineLength is zero for curves */
371 stroke.subPathAngle = startAngle;
372 stroke.firstPt = false;
373 stroke.subPathLineLength = lineLength;
377 static void _lineTo(SwStroke& stroke, const SwPoint& to)
379 auto delta = to - stroke.center;
381 //a zero-length lineto is a no-op; avoid creating a spurious corner
382 if (delta.zero()) return;
384 //compute length of line
385 auto lineLength = mathLength(delta);
386 auto angle = mathAtan(delta);
388 delta = {static_cast<SwCoord>(stroke.width), 0};
389 mathRotate(delta, angle + SW_ANGLE_PI2);
390 SCALE(stroke, delta);
392 //process corner if necessary
393 if (stroke.firstPt) {
394 /* This is the first segment of a subpath. We need to add a point to each border
395 at their respective starting point locations. */
396 _firstSubPath(stroke, angle, lineLength);
398 //process the current corner
399 stroke.angleOut = angle;
400 _processCorner(stroke, lineLength);
403 //now add a line segment to both the inside and outside paths
404 auto border = stroke.borders;
408 auto pt = to + delta;
410 //the ends of lineto borders are movable
411 _borderLineTo(border, pt, true);
420 stroke.angleIn = angle;
422 stroke.lineLength = lineLength;
426 static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
428 /* if all control points are coincident, this is a no-op;
429 avoid creating a spurious corner */
430 if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) {
435 SwPoint bezStack[37]; //TODO: static?
436 auto limit = bezStack + 32;
438 auto firstArc = true;
442 arc[3] = stroke.center;
444 while (arc >= bezStack) {
445 SwFixed angleIn, angleOut, angleMid;
447 //initialize with current direction
448 angleIn = angleOut = angleMid = stroke.angleIn;
450 if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
451 if (stroke.firstPt) stroke.angleIn = angleIn;
459 //process corner if necessary
460 if (stroke.firstPt) {
461 _firstSubPath(stroke, angleIn, 0);
463 stroke.angleOut = angleIn;
464 _processCorner(stroke, 0);
466 } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
467 //if the deviation from one arc to the next is too great add a round corner
468 stroke.center = arc[3];
469 stroke.angleOut = angleIn;
470 stroke.join = StrokeJoin::Round;
472 _processCorner(stroke, 0);
474 //reinstate line join style
475 stroke.join = stroke.joinSaved;
478 //the arc's angle is small enough; we can add it directly to each border
479 auto theta1 = mathDiff(angleIn, angleMid) / 2;
480 auto theta2 = mathDiff(angleMid, angleOut) / 2;
481 auto phi1 = mathMean(angleIn, angleMid);
482 auto phi2 = mathMean(angleMid, angleOut);
483 auto length1 = mathDivide(stroke.width, mathCos(theta1));
484 auto length2 = mathDivide(stroke.width, mathCos(theta2));
487 //compute direction of original arc
488 if (stroke.handleWideStrokes) {
489 alpha0 = mathAtan(arc[0] - arc[3]);
492 auto border = stroke.borders;
497 auto rotate = SIDE_TO_ROTATE(side);
499 //compute control points
500 SwPoint _ctrl1 = {static_cast<SwCoord>(length1), 0};
501 mathRotate(_ctrl1, phi1 + rotate);
502 SCALE(stroke, _ctrl1);
505 SwPoint _ctrl2 = {static_cast<SwCoord>(length2), 0};
506 mathRotate(_ctrl2, phi2 + rotate);
507 SCALE(stroke, _ctrl2);
511 SwPoint _end = {static_cast<SwCoord>(stroke.width), 0};
512 mathRotate(_end, angleOut + rotate);
516 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);
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) {
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);
535 SwPoint delta = {static_cast<SwCoord>(alen), 0};
536 mathRotate(delta, beta);
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);
545 //and then move to the endpoint
546 _borderLineTo(border, _end, false);
555 _borderCubicTo(border, _ctrl1, _ctrl2, _end);
560 stroke.angleIn = angleOut;
566 static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
568 if (stroke.cap == StrokeCap::Square) {
569 auto rotate = SIDE_TO_ROTATE(side);
570 auto border = stroke.borders + side;
572 SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
573 mathRotate(delta, angle);
574 SCALE(stroke, delta);
576 SwPoint delta2 = {static_cast<SwCoord>(stroke.width), 0};
577 mathRotate(delta2, angle + rotate);
578 SCALE(stroke, delta2);
579 delta += stroke.center + delta2;
581 _borderLineTo(border, delta, false);
583 delta = {static_cast<SwCoord>(stroke.width), 0};
584 mathRotate(delta, angle);
585 SCALE(stroke, delta);
587 delta2 = {static_cast<SwCoord>(stroke.width), 0};
588 mathRotate(delta2, angle - rotate);
589 SCALE(stroke, delta2);
590 delta += delta2 + stroke.center;
592 _borderLineTo(border, delta, false);
594 } else if (stroke.cap == StrokeCap::Round) {
596 stroke.angleIn = angle;
597 stroke.angleOut = angle + SW_ANGLE_PI;
598 _arcTo(stroke, side);
602 auto rotate = SIDE_TO_ROTATE(side);
603 auto border = stroke.borders + side;
605 SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
606 mathRotate(delta, angle + rotate);
607 SCALE(stroke, delta);
608 delta += stroke.center;
610 _borderLineTo(border, delta, false);
612 delta = {static_cast<SwCoord>(stroke.width), 0};
613 mathRotate(delta, angle - rotate);
614 SCALE(stroke, delta);
615 delta += stroke.center;
617 _borderLineTo(border, delta, false);
622 static void _addReverseLeft(SwStroke& stroke, bool opened)
624 auto right = stroke.borders + 0;
625 auto left = stroke.borders + 1;
626 auto newPts = left->ptsCnt - left->start;
628 if (newPts <= 0) return;
630 _growBorder(right, newPts);
632 auto dstPt = right->pts + right->ptsCnt;
633 auto dstTag = right->tags + right->ptsCnt;
634 auto srcPt = left->pts + left->ptsCnt - 1;
635 auto srcTag = left->tags + left->ptsCnt - 1;
637 while (srcPt >= left->pts + left->start) {
642 dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
644 //switch begin/end tags if necessary
645 auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
646 if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
647 dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
656 left->ptsCnt = left->start;
657 right->ptsCnt += newPts;
658 right->movable = false;
659 left->movable = false;
663 static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed)
665 /* We cannot process the first point because there is not enough
666 information regarding its corner/cap. Later, it will be processed
667 in the _endSubPath() */
669 stroke.firstPt = true;
671 stroke.closedSubPath = closed;
673 /* Determine if we need to check whether the border radius is greater
674 than the radius of curvature of a curve, to handle this case specially.
675 This is only required if bevel joins or butt caps may be created because
676 round & miter joins and round & square caps cover the nagative sector
677 created with wide strokes. */
678 if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt))
679 stroke.handleWideStrokes = true;
681 stroke.handleWideStrokes = false;
683 stroke.ptStartSubPath = to;
688 static void _endSubPath(SwStroke& stroke)
690 if (stroke.closedSubPath) {
691 //close the path if needed
692 if (stroke.center != stroke.ptStartSubPath)
693 _lineTo(stroke, stroke.ptStartSubPath);
696 stroke.angleOut = stroke.subPathAngle;
697 auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
699 //No specific corner processing is required if the turn is 0
702 //when we turn to the right, the inside is 0
705 //otherwise, the inside is 1
706 if (turn < 0) inside = 1;
708 _inside(stroke, inside, stroke.subPathLineLength); //inside
709 _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside
712 _borderClose(stroke.borders + 0, false);
713 _borderClose(stroke.borders + 1, true);
715 auto right = stroke.borders;
717 /* all right, this is an opened path, we need to add a cap between
718 right & left, add the reverse of left, then add a final cap
719 between left & right */
720 _addCap(stroke, stroke.angleIn, 0);
722 //add reversed points from 'left' to 'right'
723 _addReverseLeft(stroke, true);
725 //now add the final cap
726 stroke.center = stroke.ptStartSubPath;
727 _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
729 /* now end the right subpath accordingly. The left one is rewind
730 and deosn't need further processing */
731 _borderClose(right, false);
736 static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
738 auto count = border->ptsCnt;
739 auto tags = border->tags;
740 uint32_t _ptsCnt = 0;
741 uint32_t _cntrsCnt = 0;
745 if (tags[0] & SW_STROKE_TAG_BEGIN) {
746 if (inCntr) goto fail;
748 } else if (!inCntr) goto fail;
750 if (tags[0] & SW_STROKE_TAG_END) {
759 if (inCntr) goto fail;
762 cntrsCnt = _cntrsCnt;
772 static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
774 auto border = stroke.borders + side;
776 if (border->ptsCnt == 0) return; //invalid border
778 memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint));
780 auto cnt = border->ptsCnt;
781 auto src = border->tags;
782 auto tags = outline->types + outline->ptsCnt;
783 auto cntrs = outline->cntrs + outline->cntrsCnt;
784 auto idx = outline->ptsCnt;
788 if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
789 else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
791 //LOG: What type of stroke outline??
794 if (*src & SW_STROKE_TAG_END) {
804 outline->ptsCnt = outline->ptsCnt + border->ptsCnt;
808 /************************************************************************/
809 /* External Class Implementation */
810 /************************************************************************/
812 void strokeFree(SwStroke* stroke)
817 if (stroke->borders[0].pts) free(stroke->borders[0].pts);
818 if (stroke->borders[0].tags) free(stroke->borders[0].tags);
819 if (stroke->borders[1].pts) free(stroke->borders[1].pts);
820 if (stroke->borders[1].tags) free(stroke->borders[1].tags);
822 fillFree(stroke->fill);
823 stroke->fill = nullptr;
829 void strokeReset(SwStroke* stroke, const Shape* sdata, const Matrix* transform)
832 stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f));
833 stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f));
835 stroke->sx = stroke->sy = 1.0f;
838 stroke->width = HALF_STROKE(sdata->strokeWidth());
839 stroke->cap = sdata->strokeCap();
841 //Save line join: it can be temporarily changed when stroking curves...
842 stroke->joinSaved = stroke->join = sdata->strokeJoin();
844 stroke->borders[0].ptsCnt = 0;
845 stroke->borders[0].start = -1;
846 stroke->borders[1].ptsCnt = 0;
847 stroke->borders[1].start = -1;
851 bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
855 for (uint32_t i = 0; i < outline.cntrsCnt; ++i) {
856 auto last = outline.cntrs[i]; //index of last point in contour
857 auto limit = outline.pts + last;
865 auto start = outline.pts[first];
866 auto pt = outline.pts + first;
867 auto types = outline.types + first;
868 auto type = types[0];
870 //A contour cannot start with a cubic control point
871 if (type == SW_CURVE_TYPE_CUBIC) return false;
873 auto closed = outline.closed ? outline.closed[i]: false;
875 _beginSubPath(*stroke, start, closed);
881 //emit a signel line_to
882 if (types[0] == SW_CURVE_TYPE_POINT) {
883 _lineTo(*stroke, *pt);
886 if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false;
892 _cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
895 _cubicTo(*stroke, pt[-2], pt[-1], start);
901 if (!stroke->firstPt) _endSubPath(*stroke);
908 SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
910 uint32_t count1, count2, count3, count4;
912 _getCounts(stroke->borders + 0, count1, count2);
913 _getCounts(stroke->borders + 1, count3, count4);
915 auto ptsCnt = count1 + count3;
916 auto cntrsCnt = count2 + count4;
918 auto outline = mpoolReqStrokeOutline(mpool, tid);
919 if (outline->reservedPtsCnt < ptsCnt) {
920 outline->pts = static_cast<SwPoint*>(realloc(outline->pts, sizeof(SwPoint) * ptsCnt));
921 outline->types = static_cast<uint8_t*>(realloc(outline->types, sizeof(uint8_t) * ptsCnt));
922 outline->reservedPtsCnt = ptsCnt;
924 if (outline->reservedCntrsCnt < cntrsCnt) {
925 outline->cntrs = static_cast<uint32_t*>(realloc(outline->cntrs, sizeof(uint32_t) * cntrsCnt));
926 outline->reservedCntrsCnt = cntrsCnt;
929 _exportBorderOutline(*stroke, outline, 0); //left
930 _exportBorderOutline(*stroke, outline, 1); //right