blob: b23a91ab4421ef1a942a859e7244f0295dd2450c [file] [log] [blame] [edit]
/*
* Copyright 2025 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* This file contains private enums related to paths. See also skbug.com/40042016
*/
#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkSpan.h"
#include "src/core/SkPathEnums.h"
#include "src/core/SkPathPriv.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits.h>
#include <utility>
/* Contains path methods that require SkPathData -- not SkPathRef:
*
* The remaining fields:
* - fFillType
* - fIsVolatile
*
* ... are shared in both implemtations.
*/
#ifdef SK_PATH_USES_PATHDATA
/*
* This returns a singleton instance which SkPath uses to signify that its pathdata is in error:
* either because the inputs were invalid (e.g. bad verbs), or its coordintes were non-finite
* (either from the client, or after a makeTransform() call).
*/
SkPathData* SkPath::PeekErrorSingleton() {
static SkPathData* gErrorSingleton = SkPathData::MakeNoCheck({}, {}, {}, {}, {}).release();
// Make sure MakeNoCheck() didn't alias us to the standard Empty instance. We want our
// pointer to be distinct from that one.
SkASSERT(gErrorSingleton != SkPathData::Empty().get());
return gErrorSingleton;
}
SkPath SkPath::MakeNullCheck(sk_sp<SkPathData> pdata, SkPathFillType ft, bool isVolatile) {
if (!pdata) {
pdata = sk_ref_sp(PeekErrorSingleton());
}
return SkPath(std::move(pdata), ft, isVolatile);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
SkPath::SkPath(sk_sp<SkPathData> pd, SkPathFillType ft, bool isVolatile)
: fPathData(std::move(pd))
, fFillType(ft)
, fIsVolatile(isVolatile)
{
SkASSERT(fPathData);
}
SkPath::SkPath(SkPathFillType ft)
: fPathData(SkPathData::Empty())
, fFillType(ft)
, fIsVolatile(false)
{}
SkPath::SkPath(const SkPath& that)
: fPathData(that.fPathData)
, fFillType(that.fFillType)
, fIsVolatile(that.fIsVolatile)
{}
SkPath& SkPath::operator=(const SkPath& o) {
if (this != &o) {
fPathData = o.fPathData;
fFillType = o.fFillType;
fIsVolatile = o.fIsVolatile;
}
return *this;
}
void SkPath::setConvexity(SkPathConvexity c) const {
fPathData->setConvexity(c);
}
SkPathConvexity SkPath::getConvexityOrUnknown() const {
return fPathData->getConvexityOrUnknown();
}
bool operator==(const SkPath& a, const SkPath& b) {
return &a == &b ||
(a.fFillType == b.fFillType && *a.fPathData == *b.fPathData);
}
void SkPath::swap(SkPath& that) {
if (this != &that) {
fPathData.swap(that.fPathData);
std::swap(fFillType, that.fFillType);
std::swap(fIsVolatile, that.fIsVolatile);
}
}
SkPath& SkPath::reset() {
*this = SkPath();
return *this;
}
const SkRect& SkPath::getBounds() const {
return fPathData->bounds();
}
uint32_t SkPath::getSegmentMasks() const {
return fPathData->segmentMask();
}
bool SkPath::isFinite() const {
return fPathData.get() != PeekErrorSingleton();
}
bool SkPath::isValid() const { return this->isFinite(); }
bool SkPath::hasComputedBounds() const { return true; }
uint32_t SkPath::getGenerationID() const { return fPathData->uniqueID(); }
#ifdef SK_DEBUG
void SkPath::validate() const {}
void SkPath::validateRef() const {}
#endif
std::optional<SkPathOvalInfo> SkPath::getOvalInfo() const { return fPathData->asOval(); }
std::optional<SkPathRRectInfo> SkPath::getRRectInfo() const { return fPathData->asRRect(); }
SkSpan<const SkPoint> SkPath::points() const { return fPathData->points(); }
SkSpan<const SkPathVerb> SkPath::verbs() const { return fPathData->verbs(); }
SkSpan<const float> SkPath::conicWeights() const { return fPathData->conics(); }
//////////////////////////////////////////////////////////////////////////////////////////////////
SkPath SkPath::Raw(SkSpan<const SkPoint> pts, SkSpan<const SkPathVerb> vbs,
SkSpan<const float> ws, SkPathFillType ft, bool isVolatile) {
return MakeNullCheck(SkPathData::Make(pts, vbs, ws), ft, isVolatile);
}
SkPath SkPath::Rect(const SkRect& r, SkPathFillType ft, SkPathDirection dir, unsigned startIndex) {
startIndex &= 3; // keep it legal
return MakeNullCheck(SkPathData::Rect(r, dir, startIndex), ft, false);
}
SkPath SkPath::Oval(const SkRect& r, SkPathDirection dir, unsigned startIndex) {
startIndex &= 3; // keep it legal
return MakeNullCheck(SkPathData::Oval(r, dir, startIndex), SkPathFillType::kDefault, false);
}
SkPath SkPath::RRect(const SkRRect& rr, SkPathDirection dir, unsigned startIndex) {
startIndex &= 7; // keep it legal
// To be backwards compatible with the old impl for building a rrect path, we
// first check to see if the rrect itself can be simplified...
const SkRect& bounds = rr.getBounds();
auto [asType, newIndex] = SkPathPriv::SimplifyRRect(rr, startIndex);
switch (asType) {
case SkPathPriv::RRectAsEnum::kRect:
return SkPath::Rect(bounds, SkPathFillType::kDefault, dir, newIndex);
case SkPathPriv::RRectAsEnum::kOval:
return SkPath::Oval(bounds, dir, newIndex);
case SkPathPriv::RRectAsEnum::kRRect:
// fall through
break;
}
return MakeNullCheck(SkPathData::RRect(rr, dir, newIndex), SkPathFillType::kDefault, false);
}
SkPath SkPath::Polygon(SkSpan<const SkPoint> pts, bool isClosed,
SkPathFillType ft, bool isVolatile) {
return MakeNullCheck(SkPathData::Polygon(pts, isClosed), ft, isVolatile);
}
std::optional<SkPath> SkPath::tryMakeTransform(const SkMatrix& matrix) const {
if (auto pdata = fPathData->makeTransform(matrix)) {
return SkPath(std::move(pdata), fFillType, fIsVolatile);
}
return {};
}
SkPath SkPath::makeTransform(const SkMatrix& matrix) const {
if (!this->isFinite()) {
return *this;
}
if (auto newpath = this->tryMakeTransform(matrix)) {
return *newpath;
}
return SkPath(sk_ref_sp(PeekErrorSingleton()), fFillType, false);
}
std::optional<SkPathRaw> SkPath::raw(SkResolveConvexity rc) const {
return fPathData->raw(fFillType, rc);
}
//////////////////////////////////////////////////////////////////////////////////////////////
bool SkPathPriv::TestingOnly_unique(const SkPath& path) {
return path.fPathData->unique();
}
int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) {
return path.fPathData->genIDChangeListenerCount();
}
void SkPathPriv::AddGenIDChangeListener(const SkPath& path, sk_sp<SkIDChangeListener> listener) {
auto pdata = path.fPathData.get();
// SkPath's error-singleton is never deleted, so we don't want to add any listeners to it.
if (pdata != SkPath::PeekErrorSingleton()) {
pdata->addGenIDChangeListener(std::move(listener));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) {
this->reset().setFillType(src.getFillType());
this->setIsVolatile(src.isVolatile());
if (src.isEmpty()) {
return *this;
}
this->addRaw(src.fPathData->raw(src.getFillType(), SkResolveConvexity::kYes));
// These are not part of SkPathRaw, so we set them separately
fLastMoveIndex = SkPathPriv::FindLastMoveToIndex(fVerbs, fPts.size());
SkASSERT(fLastMoveIndex < fPts.size());
fType = src.fPathData->fType;
fIsA = src.fPathData->fIsA;
return *this;
}
SkPath SkPathBuilder::snapshot(const SkMatrix* mx) const {
if (!mx) {
mx = &SkMatrix::I();
}
sk_sp<SkPathData> pdata;
if (auto raw = SkPathPriv::Raw(*this, SkResolveConvexity::kNo)) {
pdata = SkPathData::MakeTransform(*raw, *mx);
}
if (pdata && fType != SkPathIsAType::kGeneral) {
pdata->setupIsA(fType, fIsA.fDirection, fIsA.fStartIndex);
}
return SkPath::MakeNullCheck(std::move(pdata), fFillType, fIsVolatile);
}
#endif