summaryrefslogtreecommitdiff
path: root/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp')
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp932
1 files changed, 932 insertions, 0 deletions
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp
new file mode 100644
index 0000000000..c0cfc1be26
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp
@@ -0,0 +1,932 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <string.h>
+#include <math.h>
+#include "tvgSwCommon.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static constexpr auto SW_STROKE_TAG_POINT = 1;
+static constexpr auto SW_STROKE_TAG_CUBIC = 2;
+static constexpr auto SW_STROKE_TAG_BEGIN = 4;
+static constexpr auto SW_STROKE_TAG_END = 8;
+
+static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
+{
+ return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
+}
+
+
+static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
+{
+ pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
+ pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
+}
+
+
+static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
+{
+ auto maxOld = border->maxPts;
+ auto maxNew = border->ptsCnt + newPts;
+
+ if (maxNew <= maxOld) return;
+
+ auto maxCur = maxOld;
+
+ while (maxCur < maxNew)
+ maxCur += (maxCur >> 1) + 16;
+ //OPTIMIZE: use mempool!
+ border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
+ border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
+ border->maxPts = maxCur;
+}
+
+
+static void _borderClose(SwStrokeBorder* border, bool reverse)
+{
+ auto start = border->start;
+ auto count = border->ptsCnt;
+
+ //Don't record empty paths!
+ if (count <= start + 1U) {
+ border->ptsCnt = start;
+ } else {
+ /* Copy the last point to the start of this sub-path,
+ since it contains the adjusted starting coordinates */
+ border->ptsCnt = --count;
+ border->pts[start] = border->pts[count];
+
+ if (reverse) {
+ //reverse the points
+ auto pt1 = border->pts + start + 1;
+ auto pt2 = border->pts + count - 1;
+
+ while (pt1 < pt2) {
+ auto tmp = *pt1;
+ *pt1 = *pt2;
+ *pt2 = tmp;
+ ++pt1;
+ --pt2;
+ }
+
+ //reverse the tags
+ auto tag1 = border->tags + start + 1;
+ auto tag2 = border->tags + count - 1;
+
+ while (tag1 < tag2) {
+ auto tmp = *tag1;
+ *tag1 = *tag2;
+ *tag2 = tmp;
+ ++tag1;
+ --tag2;
+ }
+ }
+
+ border->tags[start] |= SW_STROKE_TAG_BEGIN;
+ border->tags[count - 1] |= SW_STROKE_TAG_END;
+ }
+
+ border->start = -1;
+ border->movable = false;
+}
+
+
+static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
+{
+ _growBorder(border, 3);
+
+ auto pt = border->pts + border->ptsCnt;
+ auto tag = border->tags + border->ptsCnt;
+
+ pt[0] = ctrl1;
+ pt[1] = ctrl2;
+ pt[2] = to;
+
+ tag[0] = SW_STROKE_TAG_CUBIC;
+ tag[1] = SW_STROKE_TAG_CUBIC;
+ tag[2] = SW_STROKE_TAG_POINT;
+
+ border->ptsCnt += 3;
+
+ border->movable = false;
+}
+
+
+static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
+{
+ constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
+ SwPoint a = {static_cast<SwCoord>(radius), 0};
+ mathRotate(a, angleStart);
+ SCALE(stroke, a);
+ a += center;
+
+ auto total = angleDiff;
+ auto angle = angleStart;
+ auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
+
+ while (total != 0) {
+ auto step = total;
+ if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
+ else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
+
+ auto next = angle + step;
+ auto theta = step;
+ if (theta < 0) theta = -theta;
+
+ theta >>= 1;
+
+ //compute end point
+ SwPoint b = {static_cast<SwCoord>(radius), 0};
+ mathRotate(b, next);
+ SCALE(stroke, b);
+ b += center;
+
+ //compute first and second control points
+ auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
+
+ SwPoint a2 = {static_cast<SwCoord>(length), 0};
+ mathRotate(a2, angle + rotate);
+ SCALE(stroke, a2);
+ a2 += a;
+
+ SwPoint b2 = {static_cast<SwCoord>(length), 0};
+ mathRotate(b2, next - rotate);
+ SCALE(stroke, b2);
+ b2 += b;
+
+ //add cubic arc
+ _borderCubicTo(border, a2, b2, b);
+
+ //process the rest of the arc?
+ a = b;
+ total -= step;
+ angle = next;
+ }
+}
+
+
+static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
+{
+ if (border->movable) {
+ //move last point
+ border->pts[border->ptsCnt - 1] = to;
+ } else {
+
+ //don't add zero-length line_to
+ if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
+
+ _growBorder(border, 1);
+ border->pts[border->ptsCnt] = to;
+ border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
+ border->ptsCnt += 1;
+ }
+
+ border->movable = movable;
+}
+
+
+static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
+{
+ //close current open path if any?
+ if (border->start >= 0) _borderClose(border, false);
+
+ border->start = border->ptsCnt;
+ border->movable = false;
+
+ _borderLineTo(border, to, false);
+}
+
+
+static void _arcTo(SwStroke& stroke, int32_t side)
+{
+ auto border = stroke.borders + side;
+ auto rotate = SIDE_TO_ROTATE(side);
+ auto total = mathDiff(stroke.angleIn, stroke.angleOut);
+ if (total == SW_ANGLE_PI) total = -rotate * 2;
+
+ _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
+ border->movable = false;
+}
+
+
+static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
+{
+ constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
+
+ auto border = stroke.borders + side;
+
+ if (stroke.join == StrokeJoin::Round) {
+ _arcTo(stroke, side);
+ } else {
+ //this is a mitered (pointed) or beveled (truncated) corner
+ auto rotate = SIDE_TO_ROTATE(side);
+ auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false;
+ SwFixed phi = 0;
+ SwFixed thcos = 0;
+
+ if (!bevel) {
+ auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
+ if (theta == SW_ANGLE_PI) {
+ theta = rotate;
+ phi = stroke.angleIn;
+ } else {
+ theta /= 2;
+ phi = stroke.angleIn + theta + rotate;
+ }
+
+ thcos = mathCos(theta);
+ auto sigma = mathMultiply(MITER_LIMIT, thcos);
+
+ //is miter limit exceeded?
+ if (sigma < 0x10000L) bevel = true;
+ }
+
+ //this is a bevel (broken angle)
+ if (bevel) {
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ border->movable = false;
+ _borderLineTo(border, delta, false);
+ //this is a miter (intersection)
+ } else {
+ auto length = mathDivide(stroke.width, thcos);
+ SwPoint delta = {static_cast<SwCoord>(length), 0};
+ mathRotate(delta, phi);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ _borderLineTo(border, delta, false);
+
+ /* Now add and end point
+ Only needed if not lineto (lineLength is zero for curves) */
+ if (lineLength == 0) {
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ _borderLineTo(border, delta, false);
+ }
+ }
+ }
+}
+
+
+static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
+{
+ auto border = stroke.borders + side;
+ auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
+ SwPoint delta;
+ bool intersect = false;
+
+ /* Only intersect borders if between two line_to's and both
+ lines are long enough (line length is zero fur curves). */
+ if (border->movable && lineLength > 0) {
+ //compute minimum required length of lines
+ SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
+ if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
+ }
+
+ auto rotate = SIDE_TO_ROTATE(side);
+
+ if (!intersect) {
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ border->movable = false;
+ } else {
+ //compute median angle
+ auto phi = stroke.angleIn + theta;
+ auto thcos = mathCos(theta);
+ delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
+ mathRotate(delta, phi + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ }
+
+ _borderLineTo(border, delta, false);
+}
+
+
+void _processCorner(SwStroke& stroke, SwFixed lineLength)
+{
+ auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
+
+ //no specific corner processing is required if the turn is 0
+ if (turn == 0) return;
+
+ //when we turn to the right, the inside side is 0
+ int32_t inside = 0;
+
+ //otherwise, the inside is 1
+ if (turn < 0) inside = 1;
+
+ //process the inside
+ _inside(stroke, inside, lineLength);
+
+ //process the outside
+ _outside(stroke, 1 - inside, lineLength);
+}
+
+
+void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
+{
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, startAngle + SW_ANGLE_PI2);
+ SCALE(stroke, delta);
+
+ auto pt = stroke.center + delta;
+ auto border = stroke.borders;
+ _borderMoveTo(border, pt);
+
+ pt = stroke.center - delta;
+ ++border;
+ _borderMoveTo(border, pt);
+
+ /* Save angle, position and line length for last join
+ lineLength is zero for curves */
+ stroke.subPathAngle = startAngle;
+ stroke.firstPt = false;
+ stroke.subPathLineLength = lineLength;
+}
+
+
+static void _lineTo(SwStroke& stroke, const SwPoint& to)
+{
+ auto delta = to - stroke.center;
+
+ //a zero-length lineto is a no-op; avoid creating a spurious corner
+ if (delta.zero()) return;
+
+ //compute length of line
+ auto lineLength = mathLength(delta);
+ auto angle = mathAtan(delta);
+
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle + SW_ANGLE_PI2);
+ SCALE(stroke, delta);
+
+ //process corner if necessary
+ if (stroke.firstPt) {
+ /* This is the first segment of a subpath. We need to add a point to each border
+ at their respective starting point locations. */
+ _firstSubPath(stroke, angle, lineLength);
+ } else {
+ //process the current corner
+ stroke.angleOut = angle;
+ _processCorner(stroke, lineLength);
+ }
+
+ //now add a line segment to both the inside and outside paths
+ auto border = stroke.borders;
+ auto side = 1;
+
+ while (side >= 0) {
+ auto pt = to + delta;
+
+ //the ends of lineto borders are movable
+ _borderLineTo(border, pt, true);
+
+ delta.x = -delta.x;
+ delta.y = -delta.y;
+
+ --side;
+ ++border;
+ }
+
+ stroke.angleIn = angle;
+ stroke.center = to;
+ stroke.lineLength = lineLength;
+}
+
+
+static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
+{
+ /* if all control points are coincident, this is a no-op;
+ avoid creating a spurious corner */
+ if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) {
+ stroke.center = to;
+ return;
+ }
+
+ SwPoint bezStack[37]; //TODO: static?
+ auto limit = bezStack + 32;
+ auto arc = bezStack;
+ auto firstArc = true;
+ arc[0] = to;
+ arc[1] = ctrl2;
+ arc[2] = ctrl1;
+ arc[3] = stroke.center;
+
+ while (arc >= bezStack) {
+ SwFixed angleIn, angleOut, angleMid;
+
+ //initialize with current direction
+ angleIn = angleOut = angleMid = stroke.angleIn;
+
+ if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
+ if (stroke.firstPt) stroke.angleIn = angleIn;
+ mathSplitCubic(arc);
+ arc += 3;
+ continue;
+ }
+
+ if (firstArc) {
+ firstArc = false;
+ //process corner if necessary
+ if (stroke.firstPt) {
+ _firstSubPath(stroke, angleIn, 0);
+ } else {
+ stroke.angleOut = angleIn;
+ _processCorner(stroke, 0);
+ }
+ } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
+ //if the deviation from one arc to the next is too great add a round corner
+ stroke.center = arc[3];
+ stroke.angleOut = angleIn;
+ stroke.join = StrokeJoin::Round;
+
+ _processCorner(stroke, 0);
+
+ //reinstate line join style
+ stroke.join = stroke.joinSaved;
+ }
+
+ //the arc's angle is small enough; we can add it directly to each border
+ auto theta1 = mathDiff(angleIn, angleMid) / 2;
+ auto theta2 = mathDiff(angleMid, angleOut) / 2;
+ auto phi1 = mathMean(angleIn, angleMid);
+ auto phi2 = mathMean(angleMid, angleOut);
+ auto length1 = mathDivide(stroke.width, mathCos(theta1));
+ auto length2 = mathDivide(stroke.width, mathCos(theta2));
+ SwFixed alpha0 = 0;
+
+ //compute direction of original arc
+ if (stroke.handleWideStrokes) {
+ alpha0 = mathAtan(arc[0] - arc[3]);
+ }
+
+ auto border = stroke.borders;
+ int32_t side = 0;
+
+ while (side <= 1)
+ {
+ auto rotate = SIDE_TO_ROTATE(side);
+
+ //compute control points
+ SwPoint _ctrl1 = {static_cast<SwCoord>(length1), 0};
+ mathRotate(_ctrl1, phi1 + rotate);
+ SCALE(stroke, _ctrl1);
+ _ctrl1 += arc[2];
+
+ SwPoint _ctrl2 = {static_cast<SwCoord>(length2), 0};
+ mathRotate(_ctrl2, phi2 + rotate);
+ SCALE(stroke, _ctrl2);
+ _ctrl2 += arc[1];
+
+ //compute end point
+ SwPoint _end = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(_end, angleOut + rotate);
+ SCALE(stroke, _end);
+ _end += arc[0];
+
+ if (stroke.handleWideStrokes) {
+
+ /* determine whether the border radius is greater than the radius of
+ curvature of the original arc */
+ auto _start = border->pts[border->ptsCnt - 1];
+ auto alpha1 = mathAtan(_end - _start);
+
+ //is the direction of the border arc opposite to that of the original arc?
+ if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) {
+
+ //use the sine rule to find the intersection point
+ auto beta = mathAtan(arc[3] - _start);
+ auto gamma = mathAtan(arc[0] - _end);
+ auto bvec = _end - _start;
+ auto blen = mathLength(bvec);
+ auto sinA = abs(mathSin(alpha1 - gamma));
+ auto sinB = abs(mathSin(beta - gamma));
+ auto alen = mathMulDiv(blen, sinA, sinB);
+
+ SwPoint delta = {static_cast<SwCoord>(alen), 0};
+ mathRotate(delta, beta);
+ delta += _start;
+
+ //circumnavigate the negative sector backwards
+ border->movable = false;
+ _borderLineTo(border, delta, false);
+ _borderLineTo(border, _end, false);
+ _borderCubicTo(border, _ctrl2, _ctrl1, _start);
+
+ //and then move to the endpoint
+ _borderLineTo(border, _end, false);
+
+ ++side;
+ ++border;
+ continue;
+ }
+
+ //else fall through
+ }
+ _borderCubicTo(border, _ctrl1, _ctrl2, _end);
+ ++side;
+ ++border;
+ }
+ arc -= 3;
+ stroke.angleIn = angleOut;
+ }
+ stroke.center = to;
+}
+
+
+static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
+{
+ if (stroke.cap == StrokeCap::Square) {
+ auto rotate = SIDE_TO_ROTATE(side);
+ auto border = stroke.borders + side;
+
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle);
+ SCALE(stroke, delta);
+
+ SwPoint delta2 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta2, angle + rotate);
+ SCALE(stroke, delta2);
+ delta += stroke.center + delta2;
+
+ _borderLineTo(border, delta, false);
+
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle);
+ SCALE(stroke, delta);
+
+ delta2 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta2, angle - rotate);
+ SCALE(stroke, delta2);
+ delta += delta2 + stroke.center;
+
+ _borderLineTo(border, delta, false);
+
+ } else if (stroke.cap == StrokeCap::Round) {
+
+ stroke.angleIn = angle;
+ stroke.angleOut = angle + SW_ANGLE_PI;
+ _arcTo(stroke, side);
+ return;
+
+ } else { //Butt
+ auto rotate = SIDE_TO_ROTATE(side);
+ auto border = stroke.borders + side;
+
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+
+ _borderLineTo(border, delta, false);
+
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle - rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+
+ _borderLineTo(border, delta, false);
+ }
+}
+
+
+static void _addReverseLeft(SwStroke& stroke, bool opened)
+{
+ auto right = stroke.borders + 0;
+ auto left = stroke.borders + 1;
+ auto newPts = left->ptsCnt - left->start;
+
+ if (newPts <= 0) return;
+
+ _growBorder(right, newPts);
+
+ auto dstPt = right->pts + right->ptsCnt;
+ auto dstTag = right->tags + right->ptsCnt;
+ auto srcPt = left->pts + left->ptsCnt - 1;
+ auto srcTag = left->tags + left->ptsCnt - 1;
+
+ while (srcPt >= left->pts + left->start) {
+ *dstPt = *srcPt;
+ *dstTag = *srcTag;
+
+ if (opened) {
+ dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
+ } else {
+ //switch begin/end tags if necessary
+ auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
+ if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
+ dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
+ }
+
+ --srcPt;
+ --srcTag;
+ ++dstPt;
+ ++dstTag;
+ }
+
+ left->ptsCnt = left->start;
+ right->ptsCnt += newPts;
+ right->movable = false;
+ left->movable = false;
+}
+
+
+static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed)
+{
+ /* We cannot process the first point because there is not enough
+ information regarding its corner/cap. Later, it will be processed
+ in the _endSubPath() */
+
+ stroke.firstPt = true;
+ stroke.center = to;
+ stroke.closedSubPath = closed;
+
+ /* Determine if we need to check whether the border radius is greater
+ than the radius of curvature of a curve, to handle this case specially.
+ This is only required if bevel joins or butt caps may be created because
+ round & miter joins and round & square caps cover the nagative sector
+ created with wide strokes. */
+ if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt))
+ stroke.handleWideStrokes = true;
+ else
+ stroke.handleWideStrokes = false;
+
+ stroke.ptStartSubPath = to;
+ stroke.angleIn = 0;
+}
+
+
+static void _endSubPath(SwStroke& stroke)
+{
+ if (stroke.closedSubPath) {
+ //close the path if needed
+ if (stroke.center != stroke.ptStartSubPath)
+ _lineTo(stroke, stroke.ptStartSubPath);
+
+ //process the corner
+ stroke.angleOut = stroke.subPathAngle;
+ auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
+
+ //No specific corner processing is required if the turn is 0
+ if (turn != 0) {
+
+ //when we turn to the right, the inside is 0
+ int32_t inside = 0;
+
+ //otherwise, the inside is 1
+ if (turn < 0) inside = 1;
+
+ _inside(stroke, inside, stroke.subPathLineLength); //inside
+ _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside
+ }
+
+ _borderClose(stroke.borders + 0, false);
+ _borderClose(stroke.borders + 1, true);
+ } else {
+ auto right = stroke.borders;
+
+ /* all right, this is an opened path, we need to add a cap between
+ right & left, add the reverse of left, then add a final cap
+ between left & right */
+ _addCap(stroke, stroke.angleIn, 0);
+
+ //add reversed points from 'left' to 'right'
+ _addReverseLeft(stroke, true);
+
+ //now add the final cap
+ stroke.center = stroke.ptStartSubPath;
+ _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
+
+ /* now end the right subpath accordingly. The left one is rewind
+ and deosn't need further processing */
+ _borderClose(right, false);
+ }
+}
+
+
+static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
+{
+ auto count = border->ptsCnt;
+ auto tags = border->tags;
+ uint32_t _ptsCnt = 0;
+ uint32_t _cntrsCnt = 0;
+ bool inCntr = false;
+
+ while (count > 0) {
+ if (tags[0] & SW_STROKE_TAG_BEGIN) {
+ if (inCntr) goto fail;
+ inCntr = true;
+ } else if (!inCntr) goto fail;
+
+ if (tags[0] & SW_STROKE_TAG_END) {
+ inCntr = false;
+ ++_cntrsCnt;
+ }
+ --count;
+ ++_ptsCnt;
+ ++tags;
+ }
+
+ if (inCntr) goto fail;
+
+ ptsCnt = _ptsCnt;
+ cntrsCnt = _cntrsCnt;
+
+ return;
+
+fail:
+ ptsCnt = 0;
+ cntrsCnt = 0;
+}
+
+
+static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
+{
+ auto border = stroke.borders + side;
+
+ if (border->ptsCnt == 0) return; //invalid border
+
+ memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint));
+
+ auto cnt = border->ptsCnt;
+ auto src = border->tags;
+ auto tags = outline->types + outline->ptsCnt;
+ auto cntrs = outline->cntrs + outline->cntrsCnt;
+ uint16_t idx = outline->ptsCnt;
+
+ while (cnt > 0) {
+
+ if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
+ else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
+ else {
+ //LOG: What type of stroke outline??
+ }
+
+ if (*src & SW_STROKE_TAG_END) {
+ *cntrs = idx;
+ ++cntrs;
+ ++outline->cntrsCnt;
+ }
+ ++src;
+ ++tags;
+ ++idx;
+ --cnt;
+ }
+ outline->ptsCnt = outline->ptsCnt + border->ptsCnt;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void strokeFree(SwStroke* stroke)
+{
+ if (!stroke) return;
+
+ //free borders
+ if (stroke->borders[0].pts) free(stroke->borders[0].pts);
+ if (stroke->borders[0].tags) free(stroke->borders[0].tags);
+ if (stroke->borders[1].pts) free(stroke->borders[1].pts);
+ if (stroke->borders[1].tags) free(stroke->borders[1].tags);
+
+ fillFree(stroke->fill);
+ stroke->fill = nullptr;
+
+ free(stroke);
+}
+
+
+void strokeReset(SwStroke* stroke, const Shape* sdata, const Matrix* transform)
+{
+ if (transform) {
+ stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f));
+ stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f));
+ } else {
+ stroke->sx = stroke->sy = 1.0f;
+ }
+
+ stroke->width = HALF_STROKE(sdata->strokeWidth());
+ stroke->cap = sdata->strokeCap();
+
+ //Save line join: it can be temporarily changed when stroking curves...
+ stroke->joinSaved = stroke->join = sdata->strokeJoin();
+
+ stroke->borders[0].ptsCnt = 0;
+ stroke->borders[0].start = -1;
+ stroke->borders[1].ptsCnt = 0;
+ stroke->borders[1].start = -1;
+}
+
+
+bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
+{
+ uint32_t first = 0;
+
+ for (uint32_t i = 0; i < outline.cntrsCnt; ++i) {
+ auto last = outline.cntrs[i]; //index of last point in contour
+ auto limit = outline.pts + last;
+
+ //Skip empty points
+ if (last <= first) {
+ first = last + 1;
+ continue;
+ }
+
+ auto start = outline.pts[first];
+ auto pt = outline.pts + first;
+ auto types = outline.types + first;
+ auto type = types[0];
+
+ //A contour cannot start with a cubic control point
+ if (type == SW_CURVE_TYPE_CUBIC) return false;
+
+ auto closed = outline.closed ? outline.closed[i]: false;
+
+ _beginSubPath(*stroke, start, closed);
+
+ while (pt < limit) {
+ ++pt;
+ ++types;
+
+ //emit a signel line_to
+ if (types[0] == SW_CURVE_TYPE_POINT) {
+ _lineTo(*stroke, *pt);
+ //types cubic
+ } else {
+ if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false;
+
+ pt += 2;
+ types += 2;
+
+ if (pt <= limit) {
+ _cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
+ continue;
+ }
+ _cubicTo(*stroke, pt[-2], pt[-1], start);
+ goto close;
+ }
+ }
+
+ close:
+ if (!stroke->firstPt) _endSubPath(*stroke);
+ first = last + 1;
+ }
+ return true;
+}
+
+
+SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
+{
+ uint32_t count1, count2, count3, count4;
+
+ _getCounts(stroke->borders + 0, count1, count2);
+ _getCounts(stroke->borders + 1, count3, count4);
+
+ auto ptsCnt = count1 + count3;
+ auto cntrsCnt = count2 + count4;
+
+ auto outline = mpoolReqStrokeOutline(mpool, tid);
+ if (outline->reservedPtsCnt < ptsCnt) {
+ outline->pts = static_cast<SwPoint*>(realloc(outline->pts, sizeof(SwPoint) * ptsCnt));
+ outline->types = static_cast<uint8_t*>(realloc(outline->types, sizeof(uint8_t) * ptsCnt));
+ outline->reservedPtsCnt = ptsCnt;
+ }
+ if (outline->reservedCntrsCnt < cntrsCnt) {
+ outline->cntrs = static_cast<uint16_t*>(realloc(outline->cntrs, sizeof(uint16_t) * cntrsCnt));
+ outline->reservedCntrsCnt = cntrsCnt;
+ }
+
+ _exportBorderOutline(*stroke, outline, 0); //left
+ _exportBorderOutline(*stroke, outline, 1); //right
+
+ return outline;
+}