common: code cleanup++
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run

- Use RenderPath common interfaces instead of
  direct array manipulations.

- Replace multiple scalar operations with Point utility
  operations where applicable.
This commit is contained in:
Hermet Park 2025-07-18 17:24:05 +09:00 committed by Hermet Park
parent d85952b252
commit a79f9788c1
10 changed files with 181 additions and 362 deletions

View file

@ -189,7 +189,6 @@ Point operator*(const Point& pt, const Matrix& m);
Point normal(const Point& p1, const Point& p2);
void normalize(Point& pt);
static inline constexpr const Point operator*=(Point& pt, const Matrix* m)
{
if (m) pt *= *m;
@ -313,6 +312,13 @@ static inline Point operator*(const Point& lhs, const float rhs)
}
static inline void operator*=(Point& lhs, const float rhs)
{
lhs.x *= rhs;
lhs.y *= rhs;
}
static inline Point operator*(const float& lhs, const Point& rhs)
{
return {lhs * rhs.x, lhs * rhs.y};

View file

@ -33,22 +33,17 @@ static bool _colinear(const Point* p)
}
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, Point& prev, Point& curr, Point& next, float r)
static void _roundCorner(RenderPath& out, Point& prev, Point& curr, Point& next, float r)
{
auto lenPrev = length(prev - curr);
auto rPrev = lenPrev > 0.0f ? 0.5f * std::min(lenPrev * 0.5f, r) / lenPrev : 0.0f;
auto lenNext = length(next - curr);
auto rNext = lenNext > 0.0f ? 0.5f * std::min(lenNext * 0.5f, r) / lenNext : 0.0f;
auto dPrev = rPrev * (curr - prev);
auto dNext = rNext * (curr - next);
pts.push(curr - 2.0f * dPrev);
pts.push(curr - dPrev);
pts.push(curr - dNext);
pts.push(curr - 2.0f * dNext);
cmds.push(PathCommand::LineTo);
cmds.push(PathCommand::CubicTo);
out.lineTo(curr - 2.0f * dPrev);
out.cubicTo(curr - dPrev, curr - dNext, curr - 2.0f * dNext);
}
@ -106,24 +101,14 @@ void LottieOffsetModifier::corner(RenderPath& out, Line& line, Line& nextLine, u
} else {
out.pts.push(line.pt2);
if (join == StrokeJoin::Round) {
out.cmds.push(PathCommand::CubicTo);
out.pts.push((line.pt2 + intersect) * 0.5f);
out.pts.push((nextLine.pt1 + intersect) * 0.5f);
out.pts.push(nextLine.pt1);
out.cubicTo((line.pt2 + intersect) * 0.5f, (nextLine.pt1 + intersect) * 0.5f, nextLine.pt1);
} else if (join == StrokeJoin::Miter) {
auto norm = normal(line.pt1, line.pt2);
auto nextNorm = normal(nextLine.pt1, nextLine.pt2);
auto miterDirection = (norm + nextNorm) / length(norm + nextNorm);
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) {
out.cmds.push(PathCommand::LineTo);
out.pts.push(intersect);
}
out.cmds.push(PathCommand::LineTo);
out.pts.push(nextLine.pt1);
} else {
out.cmds.push(PathCommand::LineTo);
out.pts.push(nextLine.pt1);
}
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) out.lineTo(intersect);
out.lineTo(nextLine.pt1);
} else out.lineTo(nextLine.pt1);
}
} else out.pts.push(line.pt2);
}
@ -139,9 +124,8 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i
if (inCmds[curCmd - 1] != PathCommand::LineTo) state.line = _offset(inPts[curPt - 1], inPts[curPt], offset);
if (state.moveto) {
out.cmds.push(PathCommand::MoveTo);
out.moveTo(state.line.pt1);
state.movetoOutIndex = out.pts.count;
out.pts.push(state.line.pt1);
state.firstLine = state.line;
state.moveto = false;
}
@ -179,7 +163,6 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
buffer->clear();
auto& path = (next) ? *buffer : out;
path.cmds.reserve(inCmdsCnt * 2);
path.pts.reserve((uint32_t)(inPtsCnt * 1.5));
auto pivot = path.pts.count;
@ -189,8 +172,7 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
switch (inCmds[iCmds]) {
case PathCommand::MoveTo: {
startIndex = path.pts.count;
path.cmds.push(PathCommand::MoveTo);
path.pts.push(inPts[iPts++]);
path.moveTo(inPts[iPts++]);
break;
}
case PathCommand::CubicTo: {
@ -198,24 +180,22 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
auto& prev = inPts[iPts - 1];
auto& curr = inPts[iPts + 2];
if (inCmds[iCmds + 1] == PathCommand::CubicTo && _colinear(inPts + iPts + 2)) {
_roundCorner(path.cmds, path.pts, prev, curr, inPts[iPts + 5], r);
_roundCorner(path, prev, curr, inPts[iPts + 5], r);
iPts += 3;
break;
} else if (inCmds[iCmds + 1] == PathCommand::Close) {
_roundCorner(path.cmds, path.pts, prev, curr, inPts[2], r);
_roundCorner(path, prev, curr, inPts[2], r);
path.pts[startIndex] = path.pts.last();
iPts += 3;
break;
}
}
path.cmds.push(PathCommand::CubicTo);
path.pts.push(inPts[iPts++]);
path.pts.push(inPts[iPts++]);
path.pts.push(inPts[iPts++]);
path.cubicTo(inPts[iPts], inPts[iPts + 1], inPts[iPts + 2]);
iPts += 3;
break;
}
case PathCommand::Close: {
path.cmds.push(PathCommand::Close);
path.close();
break;
}
default: break;
@ -249,8 +229,7 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
path.pts.grow((uint32_t)(4.5 * in.cmds.count));
int start = 3 * tvg::zero(outerRoundness);
path.cmds.push(PathCommand::MoveTo);
path.pts.push(in.pts[start]);
path.moveTo(in.pts[start]);
for (uint32_t i = 1 + start; i < in.pts.count; i += 6) {
auto& prev = in.pts[i];
@ -265,12 +244,9 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
auto p2 = curr - dNext;
auto p3 = curr - 2.0f * dNext;
path.cmds.push(PathCommand::CubicTo);
path.pts.push(prev); path.pts.push(p0); path.pts.push(p0);
path.cmds.push(PathCommand::CubicTo);
path.pts.push(p1); path.pts.push(p2); path.pts.push(p3);
path.cmds.push(PathCommand::CubicTo);
path.pts.push(p3); path.pts.push(next); path.pts.push(nextCtrl);
path.cubicTo(prev, p0, p0);
path.cubicTo(p1, p2, p3);
path.cubicTo(p3, next, nextCtrl);
}
} else {
path.cmds.grow(2 * in.cmds.count);
@ -278,8 +254,7 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
auto dPrev = r * (in.pts[1] - in.pts[0]);
auto p = in.pts[0] + 2.0f * dPrev;
path.cmds.push(PathCommand::MoveTo);
path.pts.push(p);
path.moveTo(p);
for (uint32_t i = 1; i < in.pts.count; ++i) {
auto& curr = in.pts[i];
@ -291,10 +266,8 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
auto p2 = curr - dNext;
auto p3 = curr - 2.0f * dNext;
path.cmds.push(PathCommand::LineTo);
path.pts.push(p0);
path.cmds.push(PathCommand::CubicTo);
path.pts.push(p1); path.pts.push(p2); path.pts.push(p3);
path.lineTo(p0);
path.cubicTo(p1, p2, p3);
dPrev = -1.0f * dNext;
}
@ -359,9 +332,8 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
auto line3 = _offset(bezier.ctrl2, bezier.end, offset);
if (state.moveto) {
out.cmds.push(PathCommand::MoveTo);
out.moveTo(line1.pt1);
state.movetoOutIndex = out.pts.count;
out.pts.push(line1.pt1);
state.firstLine = line1;
state.moveto = false;
}

View file

@ -181,44 +181,36 @@ bool LottieParser::getValue(PathSet& path)
auto pt = pts.begin();
//Store manipulated results
Array<Point> outPts;
Array<PathCommand> outCmds;
RenderPath temp;
//Reuse the buffers
outPts.data = path.pts;
outPts.reserved = path.ptsCnt;
outCmds.data = path.cmds;
outCmds.reserved = path.cmdsCnt;
temp.pts.data = path.pts;
temp.pts.reserved = path.ptsCnt;
temp.cmds.data = path.cmds;
temp.cmds.reserved = path.cmdsCnt;
size_t extra = closed ? 3 : 0;
outPts.reserve(pts.count * 3 + 1 + extra);
outCmds.reserve(pts.count + 2);
temp.pts.reserve(pts.count * 3 + 1 + extra);
temp.cmds.reserve(pts.count + 2);
outCmds.push(PathCommand::MoveTo);
outPts.push(*pt);
temp.moveTo(*pt);
for (++pt, ++out, ++in; pt < pts.end(); ++pt, ++out, ++in) {
outCmds.push(PathCommand::CubicTo);
outPts.push(*(pt - 1) + *(out - 1));
outPts.push(*pt + *in);
outPts.push(*pt);
temp.cubicTo(*(pt - 1) + *(out - 1), *pt + *in, *pt);
}
if (closed) {
outPts.push(pts.last() + outs.last());
outPts.push(pts.first() + ins.first());
outPts.push(pts.first());
outCmds.push(PathCommand::CubicTo);
outCmds.push(PathCommand::Close);
temp.cubicTo(pts.last() + outs.last(), pts.first() + ins.first(), pts.first());
temp.close();
}
path.pts = outPts.data;
path.cmds = outCmds.data;
path.ptsCnt = outPts.count;
path.cmdsCnt = outCmds.count;
path.pts = temp.pts.data;
path.cmds = temp.cmds.data;
path.ptsCnt = temp.pts.count;
path.cmdsCnt = temp.cmds.count;
outPts.data = nullptr;
outCmds.data = nullptr;
temp.pts.data = nullptr;
temp.cmds.data = nullptr;
return false;
}

View file

@ -25,7 +25,6 @@
#include <cstring>
#include <ctype.h>
#include "tvgMath.h"
#include "tvgShape.h"
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgPath.h"
#include "tvgStr.h"
@ -69,108 +68,64 @@ static bool _parseFlag(char** content, int* number)
}
void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
//Some helpful stuff is available here:
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
void _pathAppendArcTo(RenderPath& out, Point& cur, Point& curCtl, const Point& next, Point radius, float angle, bool largeArc, bool sweep)
{
float cxp, cyp, cx, cy;
float sx, sy;
float cosPhi, sinPhi;
float dx2, dy2;
float x1p, y1p;
float x1p2, y1p2;
float rx2, ry2;
float lambda;
float c;
float at;
float theta1, deltaTheta;
float nat;
float delta, bcp;
float cosPhiRx, cosPhiRy;
float sinPhiRx, sinPhiRy;
float cosTheta1, sinTheta1;
int segments;
//Some helpful stuff is available here:
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
sx = cur->x;
sy = cur->y;
//Correction of out-of-range radii, see F6.6.1 (step 2)
rx = fabsf(rx);
ry = fabsf(ry);
angle = deg2rad(angle);
cosPhi = cosf(angle);
sinPhi = sinf(angle);
dx2 = (sx - x) / 2.0f;
dy2 = (sy - y) / 2.0f;
x1p = cosPhi * dx2 + sinPhi * dy2;
y1p = cosPhi * dy2 - sinPhi * dx2;
x1p2 = x1p * x1p;
y1p2 = y1p * y1p;
rx2 = rx * rx;
ry2 = ry * ry;
lambda = (x1p2 / rx2) + (y1p2 / ry2);
auto start = cur;
auto cosPhi = cosf(angle);
auto sinPhi = sinf(angle);
auto d2 = (start - next) * 0.5f;
auto x1p = cosPhi * d2.x + sinPhi * d2.y;
auto y1p = cosPhi * d2.y - sinPhi * d2.x;
auto x1p2 = x1p * x1p;
auto y1p2 = y1p * y1p;
auto radius2 = Point{radius.x * radius.x, radius.y * radius.y};
auto lambda = (x1p2 / radius2.x) + (y1p2 / radius2.y);
//Correction of out-of-range radii, see F6.6.2 (step 4)
if (lambda > 1.0f) {
//See F6.6.3
float lambdaRoot = sqrtf(lambda);
rx *= lambdaRoot;
ry *= lambdaRoot;
//Update rx2 and ry2
rx2 = rx * rx;
ry2 = ry * ry;
radius *= sqrtf(lambda);
radius2 = {radius.x * radius.x, radius.y * radius.y};
}
c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
Point cp, center;
auto c = (radius2.x * radius2.y) - (radius2.x * y1p2) - (radius2.y * x1p2);
//Check if there is no possible solution
//(i.e. we can't do a square root of a negative value)
if (c < 0.0f) {
//Scale uniformly until we have a single solution
//(see F6.2) i.e. when c == 0.0
float scale = sqrtf(1.0f - c / (rx2 * ry2));
rx *= scale;
ry *= scale;
//Update rx2 and ry2
rx2 = rx * rx;
ry2 = ry * ry;
radius *= sqrtf(1.0f - c / (radius2.x * radius2.y));
radius2 = {radius.x * radius.x, radius.y * radius.y};
//Step 2 (F6.5.2) - simplified since c == 0.0
cxp = 0.0f;
cyp = 0.0f;
cp = {0.0f, 0.0f};
//Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
cx = 0.0f;
cy = 0.0f;
center = {0.0f, 0.0f};
} else {
//Complete c calculation
c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
c = sqrtf(c / ((radius2.x * y1p2) + (radius2.y * x1p2)));
//Inverse sign if Fa == Fs
if (largeArc == sweep) c = -c;
//Step 2 (F6.5.2)
cxp = c * (rx * y1p / ry);
cyp = c * (-ry * x1p / rx);
cp = c * Point{(radius.x * y1p / radius.y), (-radius.y * x1p / radius.x)};
//Step 3 (F6.5.3 first part)
cx = cosPhi * cxp - sinPhi * cyp;
cy = sinPhi * cxp + cosPhi * cyp;
center = {cosPhi * cp.x - sinPhi * cp.y, sinPhi * cp.x + cosPhi * cp.y};
}
//Step 3 (F6.5.3 second part) we now have the center point of the ellipse
cx += (sx + x) / 2.0f;
cy += (sy + y) / 2.0f;
center += (start + next) * 0.5f;
//Step 4 (F6.5.4)
//We dont' use arccos (as per w3c doc), see
//http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
//Note: atan2 (0.0, 1.0) == 0.0
at = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
auto at = tvg::atan2(((y1p - cp.y) / radius.y), ((x1p - cp.x) / radius.x));
auto theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
auto nat = tvg::atan2(((-y1p - cp.y) / radius.y), ((-x1p - cp.x) / radius.x));
auto deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
if (sweep) {
//Ensure delta theta < 0 or else add 360 degrees
@ -184,52 +139,35 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
//(smaller than 90 degrees)
//We add one extra segment because we want something
//Smaller than 90deg (i.e. not 90 itself)
segments = static_cast<int>(fabsf(deltaTheta / MATH_PI2) + 1.0f);
delta = deltaTheta / segments;
auto segments = int(fabsf(deltaTheta / MATH_PI2) + 1.0f);
auto delta = deltaTheta / segments;
//http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
cosPhiRx = cosPhi * rx;
cosPhiRy = cosPhi * ry;
sinPhiRx = sinPhi * rx;
sinPhiRy = sinPhi * ry;
cosTheta1 = cosf(theta1);
sinTheta1 = sinf(theta1);
auto bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
auto cosPhiR = Point{cosPhi * radius.x, cosPhi * radius.y};
auto sinPhiR = Point{sinPhi * radius.x, sinPhi * radius.y};
auto cosTheta1 = cosf(theta1);
auto sinTheta1 = sinf(theta1);
for (int i = 0; i < segments; ++i) {
//End angle (for this segment) = current + delta
float c1x, c1y, ex, ey, c2x, c2y;
float theta2 = theta1 + delta;
float cosTheta2 = cosf(theta2);
float sinTheta2 = sinf(theta2);
Point p[3];
auto theta2 = theta1 + delta;
auto cosTheta2 = cosf(theta2);
auto sinTheta2 = sinf(theta2);
//First control point (based on start point sx,sy)
c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
auto c1 = start + Point{-bcp * (cosPhiR.x * sinTheta1 + sinPhiR.y * cosTheta1), bcp * (cosPhiR.y * cosTheta1 - sinPhiR.x * sinTheta1)};
//End point (for this segment)
ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
auto e = center + Point{cosPhiR.x * cosTheta2 - sinPhiR.y * sinTheta2, sinPhiR.x * cosTheta2 + cosPhiR.y * sinTheta2};
//Second control point (based on end point ex,ey)
c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
cmds->push(PathCommand::CubicTo);
p[0] = {c1x, c1y};
p[1] = {c2x, c2y};
p[2] = {ex, ey};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
curCtl = e + Point{bcp * (cosPhiR.x * sinTheta2 + sinPhiR.y * cosTheta2), bcp * (sinPhiR.x * sinTheta2 - cosPhiR.y * cosTheta2)};
cur = e;
out.cubicTo(c1, curCtl, cur);
//Next start point is the current end point (same for angle)
sx = ex;
sy = ey;
start = e;
theta1 = theta2;
//Avoid recomputations
cosTheta1 = cosTheta2;
@ -237,6 +175,7 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
}
}
static int _numberCount(char cmd)
{
int count = 0;
@ -276,14 +215,13 @@ static int _numberCount(char cmd)
count = 7;
break;
}
default:
break;
default: break;
}
return count;
}
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
static bool _processCommand(RenderPath& out, char cmd, float* arr, int count, Point& cur, Point& curCtl, Point& start, bool& quadratic, bool& closed)
{
switch (cmd) {
case 'm':
@ -293,169 +231,120 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
case 'q':
case 't': {
for (int i = 0; i < count - 1; i += 2) {
arr[i] = arr[i] + cur->x;
arr[i + 1] = arr[i + 1] + cur->y;
arr[i] = arr[i] + cur.x;
arr[i + 1] = arr[i + 1] + cur.y;
}
break;
}
case 'h': {
arr[0] = arr[0] + cur->x;
arr[0] = arr[0] + cur.x;
break;
}
case 'v': {
arr[0] = arr[0] + cur->y;
arr[0] = arr[0] + cur.y;
break;
}
case 'a': {
arr[5] = arr[5] + cur->x;
arr[6] = arr[6] + cur->y;
break;
}
default: {
arr[5] = arr[5] + cur.x;
arr[6] = arr[6] + cur.y;
break;
}
default: break;
}
switch (cmd) {
case 'm':
case 'M': {
Point p = {arr[0], arr[1]};
cmds->push(PathCommand::MoveTo);
pts->push(p);
*cur = {arr[0], arr[1]};
*startPoint = {arr[0], arr[1]};
start = cur = {arr[0], arr[1]};
out.moveTo(cur);
break;
}
case 'l':
case 'L': {
Point p = {arr[0], arr[1]};
cmds->push(PathCommand::LineTo);
pts->push(p);
*cur = {arr[0], arr[1]};
cur = {arr[0], arr[1]};
out.lineTo(cur);
break;
}
case 'c':
case 'C': {
Point p[3];
cmds->push(PathCommand::CubicTo);
p[0] = {arr[0], arr[1]};
p[1] = {arr[2], arr[3]};
p[2] = {arr[4], arr[5]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
*isQuadratic = false;
curCtl = {arr[2], arr[3]};
cur = {arr[4], arr[5]};
out.cubicTo({arr[0], arr[1]}, curCtl, cur);
quadratic = false;
break;
}
case 's':
case 'S': {
Point p[3], ctrl;
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
!(*isQuadratic)) {
ctrl.x = 2 * cur->x - curCtl->x;
ctrl.y = 2 * cur->y - curCtl->y;
Point ctrl;
if ((out.cmds.count > 1) && (out.cmds.last() == PathCommand::CubicTo) && !quadratic) {
ctrl = 2 * cur - curCtl;
} else {
ctrl = *cur;
ctrl = cur;
}
cmds->push(PathCommand::CubicTo);
p[0] = ctrl;
p[1] = {arr[0], arr[1]};
p[2] = {arr[2], arr[3]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = p[1];
*cur = p[2];
*isQuadratic = false;
curCtl = {arr[0], arr[1]};
cur = {arr[2], arr[3]};
out.cubicTo(ctrl, curCtl, cur);
quadratic = false;
break;
}
case 'q':
case 'Q': {
Point p[3];
float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0f / 3.0f);
float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0f / 3.0f);
float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0f / 3.0f);
float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0f / 3.0f);
cmds->push(PathCommand::CubicTo);
p[0] = {ctrl_x0, ctrl_y0};
p[1] = {ctrl_x1, ctrl_y1};
p[2] = {arr[2], arr[3]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = {arr[0], arr[1]};
*cur = p[2];
*isQuadratic = true;
auto ctrl1 = (cur + 2 * Point{arr[0], arr[1]}) * (1.0f / 3.0f);
auto ctrl2 = (Point{arr[2], arr[3]} + 2 * Point{arr[0], arr[1]}) * (1.0f / 3.0f);
curCtl = {arr[0], arr[1]};
cur = {arr[2], arr[3]};
out.cubicTo(ctrl1, ctrl2, cur);
quadratic = true;
break;
}
case 't':
case 'T': {
Point p[3], ctrl;
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
*isQuadratic) {
ctrl.x = 2 * cur->x - curCtl->x;
ctrl.y = 2 * cur->y - curCtl->y;
Point ctrl;
if ((out.cmds.count > 1) && (out.cmds.last() == PathCommand::CubicTo) && quadratic) {
ctrl = 2 * cur - curCtl;
} else {
ctrl = *cur;
ctrl = cur;
}
float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0f / 3.0f);
float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0f / 3.0f);
float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0f / 3.0f);
float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0f / 3.0f);
cmds->push(PathCommand::CubicTo);
p[0] = {ctrl_x0, ctrl_y0};
p[1] = {ctrl_x1, ctrl_y1};
p[2] = {arr[0], arr[1]};
pts->push(p[0]);
pts->push(p[1]);
pts->push(p[2]);
*curCtl = {ctrl.x, ctrl.y};
*cur = p[2];
*isQuadratic = true;
auto ctrl1 = (cur + 2 * ctrl) * (1.0f / 3.0f);
auto ctrl2 = (Point{arr[0], arr[1]} + 2 * ctrl) * (1.0f / 3.0f);
curCtl = {ctrl.x, ctrl.y};
cur = {arr[0], arr[1]};
out.cubicTo(ctrl1, ctrl2, cur);
quadratic = true;
break;
}
case 'h':
case 'H': {
Point p = {arr[0], cur->y};
cmds->push(PathCommand::LineTo);
pts->push(p);
cur->x = arr[0];
out.lineTo({arr[0], cur.y});
cur.x = arr[0];
break;
}
case 'v':
case 'V': {
Point p = {cur->x, arr[0]};
cmds->push(PathCommand::LineTo);
pts->push(p);
cur->y = arr[0];
out.lineTo({cur.x, arr[0]});
cur.y = arr[0];
break;
}
case 'z':
case 'Z': {
cmds->push(PathCommand::Close);
*cur = *startPoint;
*closed = true;
out.close();
cur = start;
closed = true;
break;
}
case 'a':
case 'A': {
if (tvg::zero(arr[0]) || tvg::zero(arr[1])) {
Point p = {arr[5], arr[6]};
cmds->push(PathCommand::LineTo);
pts->push(p);
*cur = {arr[5], arr[6]};
} else if (!tvg::equal(cur->x, arr[5]) || !tvg::equal(cur->y, arr[6])) {
_pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]);
*cur = *curCtl = {arr[5], arr[6]};
*isQuadratic = false;
cur = {arr[5], arr[6]};
out.lineTo(cur);
} else if (!tvg::equal(cur.x, arr[5]) || !tvg::equal(cur.y, arr[6])) {
_pathAppendArcTo(out, cur, curCtl, {arr[5], arr[6]}, {fabsf(arr[0]), fabsf(arr[1])}, deg2rad(arr[2]), arr[3], arr[4]);
cur = curCtl = {arr[5], arr[6]};
quadratic = false;
}
break;
}
default: {
return false;
}
default: return false;
}
return true;
}
@ -497,12 +386,12 @@ static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* c
}
}
*count = 0;
return NULL;
return nullptr;
}
for (int i = 0; i < *count; i++) {
if (!_parseNumber(&path, &arr[i])) {
*count = 0;
return NULL;
return nullptr;
}
path = _skipComma(path);
}
@ -515,29 +404,26 @@ static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* c
/************************************************************************/
bool svgPathToShape(const char* svgPath, Shape* shape)
bool svgPathToShape(const char* svgPath, RenderPath& out)
{
float numberArray[7];
int numberCount = 0;
Point cur = { 0, 0 };
Point curCtl = { 0, 0 };
Point startPoint = { 0, 0 };
Point cur = {0, 0};
Point curCtl = {0, 0};
Point start = {0, 0};
char cmd = 0;
bool isQuadratic = false;
bool closed = false;
char* path = (char*)svgPath;
auto& pts = SHAPE(shape)->rs.path.pts;
auto& cmds = SHAPE(shape)->rs.path.cmds;
auto lastCmds = cmds.count;
auto path = (char*)svgPath;
auto lastCmds = out.cmds.count;
auto isQuadratic = false;
auto closed = false;
while ((path[0] != '\0')) {
path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
if (!path) break;
closed = false;
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
if (!_processCommand(out, cmd, numberArray, numberCount, cur, curCtl, start, isQuadratic, closed)) break;
}
if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
if (out.cmds.count > lastCmds && out.cmds[lastCmds] != PathCommand::MoveTo) return false;
return true;
}

View file

@ -23,8 +23,8 @@
#ifndef _TVG_SVG_PATH_H_
#define _TVG_SVG_PATH_H_
#include <tvgCommon.h>
#include "tvgRender.h"
bool svgPathToShape(const char* svgPath, Shape* shape);
bool svgPathToShape(const char* svgPath, RenderPath& out);
#endif //_TVG_SVG_PATH_H_

View file

@ -25,6 +25,7 @@
#include "tvgCompressor.h"
#include "tvgFill.h"
#include "tvgStr.h"
#include "tvgShape.h"
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgSceneBuilder.h"
#include "tvgSvgPath.h"
@ -454,7 +455,7 @@ static bool _recognizeShape(SvgNode* node, Shape* shape)
switch (node->type) {
case SvgNodeType::Path: {
if (node->node.path.path) {
if (!svgPathToShape(node->node.path.path, shape)) {
if (!svgPathToShape(node->node.path.path, SHAPE(shape)->rs.path)) {
TVGERR("SVG", "Invalid path information.");
return false;
}

View file

@ -474,10 +474,9 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
if (!this->points(outline, flags, pts, ptsCnt, offset + kerning)) return false;
//generate tvg paths.
auto& pathCmds = SHAPE(shape)->rs.path.cmds;
auto& pathPts = SHAPE(shape)->rs.path.pts;
pathCmds.reserve(ptsCnt);
pathPts.reserve(ptsCnt);
auto& path = SHAPE(shape)->rs.path;
path.cmds.reserve(ptsCnt);
path.pts.reserve(ptsCnt);
uint32_t begin = 0;
@ -485,42 +484,31 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
//contour must start with move to
bool offCurve = !(flags[begin] & ON_CURVE);
Point ptsBegin = offCurve ? (pts[begin] + pts[endPts[i]]) * 0.5f : pts[begin];
pathCmds.push(PathCommand::MoveTo);
pathPts.push(ptsBegin);
path.moveTo(ptsBegin);
auto cnt = endPts[i] - begin + 1;
for (uint32_t x = 1; x < cnt; ++x) {
if (flags[begin + x] & ON_CURVE) {
if (offCurve) {
pathCmds.push(PathCommand::CubicTo);
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - pathPts.last()));
pathPts.push(pts[begin + x] + (2.0f/3.0f) * (pts[begin + x - 1] - pts[begin + x]));
pathPts.push(pts[begin + x]);
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - path.pts.last()), pts[begin + x] + (2.0f/3.0f) * (pts[begin + x - 1] - pts[begin + x]), pts[begin + x]);
offCurve = false;
} else {
pathCmds.push(PathCommand::LineTo);
pathPts.push(pts[begin + x]);
path.lineTo(pts[begin + x]);
}
} else {
if (offCurve) {
pathCmds.push(PathCommand::CubicTo);
auto end = (pts[begin + x] + pts[begin + x - 1]) * 0.5f;
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - pathPts.last()));
pathPts.push(end + (2.0f/3.0f) * (pts[begin + x - 1] - end));
pathPts.push(end);
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - path.pts.last()), end + (2.0f/3.0f) * (pts[begin + x - 1] - end), end);
} else {
offCurve = true;
}
}
}
if (offCurve) {
pathCmds.push(PathCommand::CubicTo);
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + cnt - 1] - pathPts.last()));
pathPts.push(ptsBegin + (2.0f/3.0f) * (pts[begin + cnt - 1] - ptsBegin));
pathPts.push(ptsBegin);
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + cnt - 1] - path.pts.last()), ptsBegin + (2.0f/3.0f) * (pts[begin + cnt - 1] - ptsBegin), ptsBegin);
}
//contour must end with close
pathCmds.push(PathCommand::Close);
path.close();
begin = endPts[i] + 1;
}
return true;

View file

@ -235,6 +235,8 @@ struct RenderPath
void close()
{
//Don't close multiple times.
if (cmds.count > 0 && cmds.last() == PathCommand::Close) return;
cmds.push(PathCommand::Close);
}

View file

@ -66,28 +66,31 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point*
Result Shape::moveTo(float x, float y) noexcept
{
SHAPE(this)->moveTo(x, y);
SHAPE(this)->rs.path.moveTo({x, y});
return Result::Success;
}
Result Shape::lineTo(float x, float y) noexcept
{
SHAPE(this)->lineTo(x, y);
SHAPE(this)->rs.path.lineTo({x, y});
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
return Result::Success;
}
Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept
{
SHAPE(this)->cubicTo(cx1, cy1, cx2, cy2, x, y);
SHAPE(this)->rs.path.cubicTo({cx1, cy1}, {cx2, cy2}, {x, y});
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
return Result::Success;
}
Result Shape::close() noexcept
{
SHAPE(this)->close();
SHAPE(this)->rs.path.close();
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
return Result::Success;
}

View file

@ -170,37 +170,6 @@ struct ShapeImpl : Shape
rs.path.pts.count += ptsCnt;
}
void moveTo(float x, float y)
{
rs.path.cmds.push(PathCommand::MoveTo);
rs.path.pts.push({x, y});
}
void lineTo(float x, float y)
{
rs.path.cmds.push(PathCommand::LineTo);
rs.path.pts.push({x, y});
impl.mark(RenderUpdateFlag::Path);
}
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
{
rs.path.cmds.push(PathCommand::CubicTo);
rs.path.pts.push({cx1, cy1});
rs.path.pts.push({cx2, cy2});
rs.path.pts.push({x, y});
impl.mark(RenderUpdateFlag::Path);
}
void close()
{
//Don't close multiple times.
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
rs.path.cmds.push(PathCommand::Close);
impl.mark(RenderUpdateFlag::Path);
}
void strokeWidth(float width)
{
if (!rs.stroke) rs.stroke = new RenderStroke();