blob: e00c23c29fedf2c840daeba110523b1f2c02cd90 [file] [log] [blame]
/*
* Copyright 2025 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkPathRaw.h"
#include "tests/Test.h"
namespace {
class SkSPathRawBuilder {
public:
SkSPathRawBuilder(SkSpan<SkPoint> ptStore, SkSpan<SkPathVerb> vbStore, SkSpan<float> cnStore)
: fPtStorage(ptStore)
, fVbStorage(vbStore)
, fCnStorage(cnStore)
, fPts(0), fCns(0), fVbs(0)
{}
void moveTo(SkPoint);
void lineTo(SkPoint);
void quadTo(SkPoint, SkPoint);
void conicTo(SkPoint, SkPoint, SkScalar w);
void cubicTo(SkPoint, SkPoint, SkPoint);
void close();
SkPathRaw raw(SkPathFillType, bool isConvex = false) const;
private:
SkSpan<SkPoint> fPtStorage;
SkSpan<SkPathVerb> fVbStorage;
SkSpan<SkScalar> fCnStorage;
size_t fPts, fCns, fVbs;
void check_extend_pts(size_t n) const {
SkASSERT(fPts + n <= fPtStorage.size());
}
void check_extend_vbs(size_t n) const {
SkASSERT(fVbs + n <= fVbStorage.size());
}
void check_extend_cns(size_t n) const {
SkASSERT(fCns + n <= fCnStorage.size());
}
};
} // namespace
void SkSPathRawBuilder::moveTo(SkPoint p) {
check_extend_pts(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p;
fVbStorage[fVbs++] = SkPathVerb::kMove;
}
void SkSPathRawBuilder::lineTo(SkPoint p) {
check_extend_pts(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p;
fVbStorage[fVbs++] = SkPathVerb::kLine;
}
void SkSPathRawBuilder::quadTo(SkPoint p1, SkPoint p2) {
check_extend_pts(2);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fVbStorage[fVbs++] = SkPathVerb::kQuad;
}
void SkSPathRawBuilder::conicTo(SkPoint p1, SkPoint p2, SkScalar w) {
check_extend_pts(2);
check_extend_cns(1);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fCnStorage[fCns++] = w;
fVbStorage[fVbs++] = SkPathVerb::kConic;
}
void SkSPathRawBuilder::cubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
check_extend_pts(3);
check_extend_vbs(1);
fPtStorage[fPts++] = p1;
fPtStorage[fPts++] = p2;
fPtStorage[fPts++] = p3;
fVbStorage[fVbs++] = SkPathVerb::kCubic;
}
void SkSPathRawBuilder::close() {
check_extend_vbs(1);
fVbStorage[fVbs++] = SkPathVerb::kClose;
}
SkPathRaw SkSPathRawBuilder::raw(SkPathFillType ft, bool isConvex) const {
const auto ptSpan = fPtStorage.first(fPts);
return {
ptSpan,
fVbStorage.first(fVbs),
fCnStorage.first(fCns),
SkRect::BoundsOrEmpty(ptSpan),
ft,
isConvex,
SkPathPriv::ComputeSegmentMask(fVbStorage),
};
}
static void check_iter(skiatest::Reporter* reporter, const SkPathRaw& raw,
SkSpan<SkPoint> pts, SkSpan<const SkPathVerb> vbs, SkSpan<const float> cns) {
size_t pIndex = 0, vIndex = 0, cIndex = 0;
size_t moveToIndex = 0; // track the start of each contour
auto iter = raw.iter();
while (auto r = iter.next()) {
REPORTER_ASSERT(reporter, vIndex < vbs.size());
REPORTER_ASSERT(reporter, vbs[vIndex++] == r->fVerb);
switch (r->fVerb) {
case SkPathVerb::kMove:
moveToIndex = pIndex;
REPORTER_ASSERT(reporter, pIndex < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[0]);
break;
case SkPathVerb::kLine:
REPORTER_ASSERT(reporter, pIndex < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->fPoints[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[1]);
break;
case SkPathVerb::kQuad:
REPORTER_ASSERT(reporter, pIndex+1 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->fPoints[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[2]);
break;
case SkPathVerb::kConic:
REPORTER_ASSERT(reporter, pIndex+1 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->fPoints[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[2]);
REPORTER_ASSERT(reporter, cIndex < cns.size());
REPORTER_ASSERT(reporter, cns[cIndex++] == r->fConicWeight);
break;
case SkPathVerb::kCubic:
REPORTER_ASSERT(reporter, pIndex+2 < pts.size());
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->fPoints[0]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[1]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[2]);
REPORTER_ASSERT(reporter, pts[pIndex++] == r->fPoints[3]);
break;
case SkPathVerb::kClose:
REPORTER_ASSERT(reporter, pts[pIndex-1] == r->fPoints[0]); // last pt
REPORTER_ASSERT(reporter, pts[moveToIndex] == r->fPoints[1]); // first pt
break;
}
}
// make sure the iter is really done
REPORTER_ASSERT(reporter, !iter.next());
}
DEF_TEST(pathraw_iter, reporter) {
SkPoint pts[11];
SkPathVerb vbs[8];
float cns[1];
constexpr size_t N = 11;
SkPoint p[N];
for (size_t i = 0; i < N; ++i) {
p[i] = {SkScalar(i), SkScalar(i)};
}
SkSPathRawBuilder bu(pts, vbs, cns);
const SkPathVerb verbs[] = {
SkPathVerb::kMove,
SkPathVerb::kLine,
SkPathVerb::kQuad,
SkPathVerb::kCubic,
SkPathVerb::kClose,
SkPathVerb::kMove,
SkPathVerb::kLine,
SkPathVerb::kConic,
};
bu.moveTo(p[0]);
bu.lineTo(p[1]);
bu.quadTo(p[2], p[3]);
bu.cubicTo(p[4], p[5], p[6]);
bu.close();
bu.moveTo(p[7]);
bu.lineTo(p[8]);
bu.conicTo(p[9], p[10], 2);
auto raw = bu.raw(SkPathFillType::kWinding);
REPORTER_ASSERT(reporter, raw.fPoints.size() == N);
REPORTER_ASSERT(reporter, raw.fVerbs.size() == 8);
REPORTER_ASSERT(reporter, raw.fConics.size() == 1);
check_iter(reporter, raw, p, verbs, cns);
// now make sure pathbuilder generates the same results
SkPathBuilder pb;
pb.moveTo(p[0]);
pb.lineTo(p[1]);
pb.quadTo(p[2], p[3]);
pb.cubicTo(p[4], p[5], p[6]);
pb.close();
pb.moveTo(p[7]);
pb.lineTo(p[8]);
pb.conicTo(p[9], p[10], 2);
auto path = pb.detach();
raw = SkPathPriv::Raw(path);
check_iter(reporter, raw, p, verbs, cns);
}