blob: 64bb3580f44d80a33462bc5d7ecc8d1cfac9f54e [file] [log] [blame]
/*
* Copyright 2025 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPathData_DEFINED
#define SkPathData_DEFINED
#include "include/core/SkMatrix.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSpan.h"
#include "include/private/SkPathRef.h"
#include "src/core/SkPathEnums.h"
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <optional>
struct SkPathRaw;
/*
* Immutable container for path geometry data: points, verbs, bounds, segment masks.
*
* All of the public factories check for valid input
* - valid verb sequence
* - corresponding # points
* - finite point and conic values
*
* If any of these checks fail, null is returned.
*
* A valid sequence of verbs (and corresponding points/conics) is:
*
* Any number of contours (0 or more)
* ... Each contour must begin with a single Move verb
* ... followed by any number of segments: lines, quads, conics, cubics (0 or more)
* ... followed by 0 or 1 Close verb
*
* A minor exception to these rules, is that the last contour may end with a single
* Move verb -- this will be ignored in the resulting PathData.
*
* Given a valid verb sequence, there must be the corresponding number of points and
* conics to match. If there are more or fewer, null is returned.
*
* Verb points conic_weights
* -----------------------------
* Move 1 0
* Line 1 0
* Quad 2 0
* Conic 2 1
* Cubic 3 0
* Close 0 0
*/
class SkPathData : public SkNVRefCnt<SkPathData> {
public:
/*
* Returns an empty pathdata.
*
* Since this is immutable, it may return the same object each time it is called.
*/
static sk_sp<SkPathData> Empty();
/*
* Return SkPathData with a copy of these buffers, or nullptr if they are illegal.
* Illegal = non-finite, or non-sensical verb sequences
*/
static sk_sp<SkPathData> Make(SkSpan<const SkPoint> pts,
SkSpan<const SkPathVerb> verbs,
SkSpan<const float> conics = {});
/*
* Attempt to transform src by the matrix. On success, return a new SkPathData
* with the result, else return {}.
*/
static sk_sp<SkPathData> MakeTransform(const SkPathRaw& src, const SkMatrix&);
/*
* When a factory takes a startIndex, this refers to the position of the first point
* when constructing one of our simple shapes: rect, oval, rrect.
* The index is the same as that passed to the equivalent factories in SkPath
* and in the associated addRect/Oval/RRect methods on SkPathBuilder.
*/
static sk_sp<SkPathData> Rect(const SkRect&,
SkPathDirection = SkPathDirection::kDefault,
unsigned startIndex = 0);
static sk_sp<SkPathData> Oval(const SkRect&,
SkPathDirection = SkPathDirection::kDefault,
unsigned startIndex = 1);
static sk_sp<SkPathData> RRect(const SkRRect&, SkPathDirection, unsigned startIndex);
static sk_sp<SkPathData> RRect(const SkRRect& rrect,
SkPathDirection dir = SkPathDirection::kDefault) {
return RRect(rrect, dir, dir == SkPathDirection::kCW ? 6 : 7);
}
static sk_sp<SkPathData> Polygon(SkSpan<const SkPoint> pts, bool isClosed);
static sk_sp<SkPathData> Line(SkPoint a, SkPoint b) {
return Polygon({a, b}, false);
}
friend bool operator==(const SkPathData& a, const SkPathData& b);
friend bool operator!=(const SkPathData& a, const SkPathData& b) {
return !(a == b);
}
SkSpan<const SkPoint> points() const { return fPoints; }
SkSpan<const SkPathVerb> verbs() const { return fVerbs; }
SkSpan<const float> conics() const { return fConics; }
SkRect bounds() const { return fBounds; }
uint8_t segmentMask() const { return fSegmentMask; }
SkPathRaw raw(SkPathFillType, SkResolveConvexity) const;
/**
* Return true if the path contains no points or verbs
*/
bool empty() const { return fVerbs.empty(); }
SkRect computeTightBounds() const;
/**
* Returns true if the pathdata is convex.
* Note: if necessary, it will first compute the convexity (and cache it).
*/
bool isConvex() const;
/**
* Returns two points if SkPath contains only one line. If not, return {}.
*/
std::optional<std::array<SkPoint, 2>> asLine() const;
/**
* If the pathdata is recognized as a rect, return it and its direction and open/closed.
* If not, return {}
*/
std::optional<SkPathRectInfo> asRect() const;
/**
* If the path is recognized as an oval, return its bounds. If not, return {}.
*/
std::optional<SkPathOvalInfo> asOval() const;
/**
* If the path is recognized as a round-rect, return it. If not, return {}.
*/
std::optional<SkPathRRectInfo> asRRect() const;
/**
* Attempt to transform the pathdata by the matrix. If this succeeds, return a new
* pathdata object: note, this may have different verbs / number-of-points, if the
* matrix contained perspective.
*
* If the matrix has no effect on the coordinates (e.g. it is identity), then this may
* return a ref to the same pathdata object.
*
* If the result of applying the matrix creates any non-finite coordinates, this returns
* nullptr.
*/
sk_sp<SkPathData> makeTransform(const SkMatrix&) const;
sk_sp<SkPathData> makeOffset(SkVector) const;
bool contains(SkPoint, SkPathFillType) const;
private:
friend class SkNVRefCnt<SkPathData>;
friend class SkPathPriv;
SkSpan<SkPoint> fPoints;
SkSpan<float> fConics;
SkSpan<SkPathVerb> fVerbs;
SkRect fBounds;
/*
* Convexity can be slow to compute, and (in theory) it can't always survive a matrix
* transform (due to numeric instability). Therefore we will lazily compute it as
* requested. Since we are technically always immutable, we have to store this field
* in an atomic.
*/
mutable std::atomic<uint8_t> fConvexity; // SkPathConvexity
uint8_t fSegmentMask; // SkPathSegmentMask
SkPathIsAType fType;
SkPathIsAData fIsA {};
//
// Memory layout after this (assuming we're not empty)
//
// [point data]
// [conic data]
// [verb data]
//
SkPathData(size_t npts, size_t nvbs, size_t ncns);
// Ensure the unsized delete is called (since we're manually allocating the storage)
void operator delete(void* p);
// internal finisher when building a PathData.
// If the optional value is not present, it will be computed (else checked in debug mode).
//
// In particular, if bounds is not provided, it will be computed, and if it proves
// to be non-finite, false will be returned.
bool finishInit(std::optional<SkRect> bounds, std::optional<uint8_t> segmentMask);
// If we know we're a special shape, call this after the normal initialization
void setupIsA(SkPathIsAType, SkPathDirection dir, unsigned startIndex);
SkPathConvexity getConvexityOrUnknown() const; // may return kUnknown
SkPathConvexity getResolvedConvexity() const; // never returns kUnknown
void setConvexity(SkPathConvexity) const; // const -- but convexity is mutable
static sk_sp<SkPathData> Alloc(size_t npts, size_t nvbs, size_t ncns);
static sk_sp<SkPathData> MakeNoCheck(SkSpan<const SkPoint> pts,
SkSpan<const SkPathVerb> verbs,
SkSpan<const float> conics,
std::optional<SkRect> bounds,
std::optional<unsigned> segmentMask);
static sk_sp<SkPathData> MakeNoCheck(const SkPathRaw&);
};
#endif