diff --git a/Package.resolved b/Package.resolved index 72dfccd..bc372ce 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "77b14da56d67bb9354fb38d4a97117f83dfa94752d93103f728d617a6fda088a", + "originHash" : "690a05887e434b4960ff5a0cb3c6ff7828728045e61fb5fcc5094b76e1d28222", "pins" : [ { "identity" : "darwinprivateframeworks", @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "53c0103e449cd0fecc931d8a1af63e7ff036d575" + "revision" : "145cfde7a6b78248622bb2cd267101a8083bb126" } }, { diff --git a/Package.swift b/Package.swift index 88be25b..ff8d04f 100644 --- a/Package.swift +++ b/Package.swift @@ -159,12 +159,12 @@ let renderBoxCondtion = envBoolValue("RENDERBOX", default: buildForDarwinPlatfor var sharedCSettings: [CSetting] = [ .unsafeFlags(["-I", libSwiftPath], .when(platforms: .nonDarwinPlatforms)), - .unsafeFlags(["-fmodules"]), + // .unsafeFlags(["-fmodules"]), .define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)), ] var sharedCxxSettings: [CXXSetting] = [ .unsafeFlags(["-I", libSwiftPath], .when(platforms: .nonDarwinPlatforms)), - .unsafeFlags(["-fcxx-modules"]), + // .unsafeFlags(["-fcxx-modules"]), .define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)), ] var sharedSwiftSettings: [SwiftSetting] = [ diff --git a/Scripts/build.sh b/Scripts/build.sh old mode 100644 new mode 100755 index 85dad82..3c46504 --- a/Scripts/build.sh +++ b/Scripts/build.sh @@ -9,4 +9,10 @@ OPENRENDERBOX_ROOT="$(dirname $(dirname $(filepath $0)))" cd $OPENRENDERBOX_ROOT +# Set OPENRENDERBOX_LIB_SWIFT_PATH on Linux if swiftly is installed +if [[ "$(uname)" == "Linux" ]] && command -v swiftly &> /dev/null && [[ -z "$OPENRENDERBOX_LIB_SWIFT_PATH" ]]; then + export OPENRENDERBOX_LIB_SWIFT_PATH="$(swiftly use --print-location)/usr/lib/swift" + echo "Set OPENRENDERBOX_LIB_SWIFT_PATH=$OPENRENDERBOX_LIB_SWIFT_PATH" +fi + swift build diff --git a/Sources/OpenRenderBox/Path/ORBPath.cpp b/Sources/OpenRenderBox/Path/ORBPath.cpp index c7c0542..2aadf2e 100644 --- a/Sources/OpenRenderBox/Path/ORBPath.cpp +++ b/Sources/OpenRenderBox/Path/ORBPath.cpp @@ -5,12 +5,144 @@ // Created by Kyle on 2025/3/25. // -#include +#include +#include + +// Empty path callbacks (all null) - C++ internal linkage +static const ORBPathCallbacks empty_path_callbacks = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; + +// Empty path (storage = null) +const ORBPath ORBPathEmpty = { + nullptr, + &empty_path_callbacks, +}; + +// Null path (storage = 0x1) +const ORBPath ORBPathNull = { + reinterpret_cast(0x1), + &empty_path_callbacks, +}; void ORBPathRetain(ORBPath path) { - // TODO + auto retain = path.callbacks->retain; + if (retain != nullptr) { + retain(&path); + } } void ORBPathRelease(ORBPath path) { - // TODO + auto release = path.callbacks->release; + if (release != nullptr) { + release(&path); + } +} + +#if ORB_TARGET_OS_DARWIN + +// MARK: - Path Creation + +// TODO: TO be implemented natively + +ORBPath ORBPathMakeWithCGPath(CGPathRef cgPath) { + if (cgPath == nullptr) { + return ORBPathNull; + } + CFRetain(cgPath); + return ORBPath { + reinterpret_cast(const_cast(cgPath)), + &ORBPathCGPathCallbacks, + }; } + +ORBPath ORBPathMakeRect(CGRect rect, const CGAffineTransform *transform) { + CGPathRef cgPath = CGPathCreateWithRect(rect, transform); + ORBPath path = { + reinterpret_cast(const_cast(cgPath)), + &ORBPathCGPathCallbacks, + }; + return path; +} + +ORBPath ORBPathMakeEllipse(CGRect rect, const CGAffineTransform *transform) { + CGPathRef cgPath = CGPathCreateWithEllipseInRect(rect, transform); + ORBPath path = { + reinterpret_cast(const_cast(cgPath)), + &ORBPathCGPathCallbacks, + }; + return path; +} + +ORBPath ORBPathMakeRoundedRect(CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight, ORBPathRoundedCornerStyle style, const CGAffineTransform *transform) { + // TODO: Handle ORBRoundedCornerStyleContinuous with custom bezier curves + CGPathRef cgPath = CGPathCreateWithRoundedRect(rect, cornerWidth, cornerHeight, transform); + ORBPath path = { + reinterpret_cast(const_cast(cgPath)), + &ORBPathCGPathCallbacks, + }; + return path; +} + +ORBPath ORBPathMakeUnevenRoundedRect(CGRect rect, CGFloat topLeftRadius, CGFloat bottomLeftRadius, CGFloat bottomRightRadius, CGFloat topRightRadius, ORBPathRoundedCornerStyle style, const CGAffineTransform *transform) { + // TODO: Handle ORBRoundedCornerStyleContinuous with custom bezier curves + CGMutablePathRef cgPath = CGPathCreateMutable(); + + CGFloat minX = CGRectGetMinX(rect); + CGFloat minY = CGRectGetMinY(rect); + CGFloat maxX = CGRectGetMaxX(rect); + CGFloat maxY = CGRectGetMaxY(rect); + + // Start at top-left corner (after the rounded corner) + CGPathMoveToPoint(cgPath, transform, minX + topLeftRadius, minY); + + // Top edge and top-right corner + CGPathAddLineToPoint(cgPath, transform, maxX - topRightRadius, minY); + CGPathAddArc(cgPath, transform, maxX - topRightRadius, minY + topRightRadius, topRightRadius, -M_PI_2, 0, false); + + // Right edge and bottom-right corner + CGPathAddLineToPoint(cgPath, transform, maxX, maxY - bottomRightRadius); + CGPathAddArc(cgPath, transform, maxX - bottomRightRadius, maxY - bottomRightRadius, bottomRightRadius, 0, M_PI_2, false); + + // Bottom edge and bottom-left corner + CGPathAddLineToPoint(cgPath, transform, minX + bottomLeftRadius, maxY); + CGPathAddArc(cgPath, transform, minX + bottomLeftRadius, maxY - bottomLeftRadius, bottomLeftRadius, M_PI_2, M_PI, false); + + // Left edge and top-left corner + CGPathAddLineToPoint(cgPath, transform, minX, minY + topLeftRadius); + CGPathAddArc(cgPath, transform, minX + topLeftRadius, minY + topLeftRadius, topLeftRadius, M_PI, M_PI + M_PI_2, false); + + CGPathCloseSubpath(cgPath); + + ORBPath path = { + reinterpret_cast(cgPath), + &ORBPathCGPathCallbacks, + }; + return path; +} + +CGPathRef ORBPathCopyCGPath(ORBPath path) { + // TODO: Return a retained copy of the CGPath + return nullptr; +} + +bool ORBPathContainsPoint(ORBPath path, CGPoint point, bool eoFill) { + return ORBPathContainsPoints(path, 1, &point, eoFill, nullptr); +} + +bool ORBPathContainsPoints(ORBPath path, uint64_t count, const CGPoint *points, bool eoFill, const CGAffineTransform *transform) { + // TODO: Implement point containment testing with winding rule + return false; +} + +#endif /* ORB_TARGET_OS_DARWIN */ diff --git a/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp b/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp new file mode 100644 index 0000000..a2c96a5 --- /dev/null +++ b/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp @@ -0,0 +1,75 @@ +// +// ORBPathCallbacks.cpp +// OpenRenderBox +// +// Audited for 6.5.1 +// Status: Complete + +#include +#include + +#if ORB_TARGET_OS_DARWIN + +CG_EXTERN size_t CGPathGetNumberOfPoints(CGPathRef cg_nullable path); +CG_EXTERN size_t CGPathGetNumberOfElements(CGPathRef cg_nullable path); +typedef void (^CGPathApplyBlock2)(const CGPathElement * element, bool *stop); +CG_EXTERN void CGPathApplyWithBlock2(CGPathRef path, CGPathApplyBlock2 CF_NOESCAPE block); + +namespace { + uint32_t cgpath_bezier_order(CGPathRef cgPath) { + size_t pointsCount = CGPathGetNumberOfPoints(cgPath); + size_t elementsCount = CGPathGetNumberOfElements(cgPath); + if (pointsCount > elementsCount) { return 3; } + if (pointsCount != 3 || elementsCount != 3) { return 1; } + bool hasCurve = false; + CGPathApply(cgPath, &hasCurve, [](void *info, const CGPathElement *element) { + if (element->type == kCGPathElementAddQuadCurveToPoint + || element->type == kCGPathElementAddCurveToPoint) { + *static_cast(info) = true; + } + }); + return hasCurve ? 3 : 1; + } +} + +const ORBPathCallbacks ORBPathCGPathCallbacks = { + nullptr, + CFRetain, + CFRelease, + +[](const void *object, void *info, ORBPathApplyCallback callback) -> bool { + CGPathRef cgPath = reinterpret_cast(object); + __block bool shouldStop = false; + CGPathApplyWithBlock2(cgPath, ^(const CGPathElement *element, bool *stop) { + bool result = callback(info, + (ORBPathElement)element->type, + (CGFloat *)element->points, + nullptr); + if (!result) { + *stop = true; + shouldStop = true; + } + }); + return !shouldStop; + }, + +[](const void *object, const void *otherObject) -> bool { + return CGPathEqualToPath(static_cast(object), static_cast(otherObject)); + }, + +[](const void *object) -> bool { + return CGPathIsEmpty(static_cast(object)); + }, + +[](const void *object) -> bool { + return false; + }, + +[](const void *object) -> uint32_t { + return cgpath_bezier_order(static_cast(object)); + }, + +[](const void *object) -> CGRect { + return CGPathGetPathBoundingBox(static_cast(object)); + }, + +[](const void *object) -> CGPathRef { + return static_cast(object); + }, + nullptr, +}; + +#endif /* ORB_TARGET_OS_DARWIN */ diff --git a/Sources/OpenRenderBox/Path/ORBPathStorage.cpp b/Sources/OpenRenderBox/Path/ORBPathStorage.cpp index 33f33f0..7164798 100644 --- a/Sources/OpenRenderBox/Path/ORBPathStorage.cpp +++ b/Sources/OpenRenderBox/Path/ORBPathStorage.cpp @@ -24,7 +24,17 @@ void ORBPathStorageClear(ORBPathStorageRef storage) { storage->storage.clear(); } -// ... +bool ORBPathStorageAppendElement(ORBPathStorageRef storage, ORBPathElement element, double const * points, const void * userInfo) { + precondition_failure("TODO"); +} + +void ORBPathStorageAppendPath(ORBPathStorageRef storage, ORBPath path) { + precondition_failure("TODO"); +} + +void ORBPathStorageApplyElements(ORBPathStorageRef, void *info, ORBPathApplyCallback callback) { + precondition_failure("TODO"); +} bool ORBPathStorageIsEmpty(ORBPathStorageRef storage) { return storage->storage.isEmpty(); diff --git a/Sources/OpenRenderBox/Path/PathStorage.cpp b/Sources/OpenRenderBox/Path/PathStorage.cpp index c6282c2..c75a81f 100644 --- a/Sources/OpenRenderBox/Path/PathStorage.cpp +++ b/Sources/OpenRenderBox/Path/PathStorage.cpp @@ -5,10 +5,24 @@ #include #include +#if ORB_TARGET_OS_DARWIN +#include +#endif + namespace ORB { namespace Path { atomic_long Storage::_last_identifier; +Storage::Storage(uint32_t capacity) { + _unknown = nullptr; + if (capacity <= 63) { + precondition_failure("invalid capacity"); + } + uint32_t cappedCapacity = fmin(capacity - 16, 4095); + _flags = StorageFlags().withCapacity(cappedCapacity); + _identifier = get_identifier(); +} + Storage::Storage(uint32_t capacity, const Storage &storage): Storage(capacity) { uint32_t originalCapity = flags().capacity(); StorageFlags sourceFlags = storage.flags(); @@ -44,8 +58,8 @@ Storage::Storage(uint32_t capacity, const Storage &storage): Storage(capacity) { } Storage::~Storage() { - if (_unknonw != nullptr) { - _unknonw = nullptr; + if (_unknown != nullptr) { + _unknown = nullptr; // TODO } } @@ -59,5 +73,16 @@ void Storage::clear() { // TODO } +void * Storage::cgpath() const ORB_NOEXCEPT { + if (_flags.isInline()) { + return nullptr; + } + if (_cached_cgPath != nullptr) { + return _cached_cgPath; + } + // TODO: Create CGPath via RBPathCopyCGPath + return nullptr; +} + } /* Path */ } /* ORB */ diff --git a/Sources/OpenRenderBox/Util/assert.cpp b/Sources/OpenRenderBox/Util/assert.cpp index ecd758c..04aae13 100644 --- a/Sources/OpenRenderBox/Util/assert.cpp +++ b/Sources/OpenRenderBox/Util/assert.cpp @@ -19,7 +19,7 @@ void precondition_failure(const char *format, ...) { os_log_error(error_log(), "precondition failure: %s", s); #endif /* ORB_TARGET_OS_DARWIN */ if (error_message == nullptr) { - asprintf(&error_message, "OpenGraph precondition failure: %s.\n", s); + asprintf(&error_message, "OpenRenderBox precondition failure: %s.\n", s); } free(s); } diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/CFCGTypes.h b/Sources/OpenRenderBox/include/OpenRenderBox/CFCGTypes.h new file mode 100644 index 0000000..1ca3d8d --- /dev/null +++ b/Sources/OpenRenderBox/include/OpenRenderBox/CFCGTypes.h @@ -0,0 +1,124 @@ +/* CFCGTypes.h + Copyright (c) 1998-2021, Apple Inc. and the Swift project authors + + Portions Copyright (c) 2014-2021, Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +*/ + +#if !defined(__COREFOUNDATION_CFCGTYPES__) +#define __COREFOUNDATION_CFCGTYPES__ 1 + +#include +#include +#include +#include + +#define CF_DEFINES_CG_TYPES + +#if defined(__has_attribute) && __has_attribute(objc_boxable) +# define CF_BOXABLE __attribute__((objc_boxable)) +#else +# define CF_BOXABLE +#endif + +/* Definition of `CGFLOAT_TYPE', `CGFLOAT_IS_DOUBLE', `CGFLOAT_MIN', and + * `CGFLOAT_MAX'. */ + + #if defined(__LP64__) && __LP64__ + # define CGFLOAT_TYPE double + # define CGFLOAT_IS_DOUBLE 1 + # define CGFLOAT_MIN DBL_MIN + # define CGFLOAT_MAX DBL_MAX + # define CGFLOAT_EPSILON DBL_EPSILON + #else + # define CGFLOAT_TYPE float + # define CGFLOAT_IS_DOUBLE 0 + # define CGFLOAT_MIN FLT_MIN + # define CGFLOAT_MAX FLT_MAX + # define CGFLOAT_EPSILON FLT_EPSILON + #endif + +/* Definition of the `CGFloat' type and `CGFLOAT_DEFINED'. */ + +typedef CGFLOAT_TYPE CGFloat; +#define CGFLOAT_DEFINED 1 + +/* Points. */ +struct +CGPoint { + CGFloat x; + CGFloat y; +}; +typedef struct CF_BOXABLE CGPoint CGPoint; + +/* Sizes. */ + +struct CGSize { + CGFloat width; + CGFloat height; +}; +typedef struct CF_BOXABLE CGSize CGSize; + +/* Vectors. */ + +#define CGVECTOR_DEFINED 1 + +struct CGVector { + CGFloat dx; + CGFloat dy; +}; +typedef struct CF_BOXABLE CGVector CGVector; + +/* Rectangles. */ + +struct CGRect { + CGPoint origin; + CGSize size; +}; +typedef struct CF_BOXABLE CGRect CGRect; + +/* Rectangle edges. */ + +typedef CF_CLOSED_ENUM(uint32_t, CGRectEdge) { + CGRectMinXEdge, CGRectMinYEdge, CGRectMaxXEdge, CGRectMaxYEdge +}; + +typedef struct CGAffineTransform CGAffineTransform; + +struct CGAffineTransform { + CGFloat a, b, c, d; + CGFloat tx, ty; +}; + +#define CF_DEFINES_CGAFFINETRANSFORMCOMPONENTS + +/* |------------------ CGAffineTransformComponents ----------------| + * + * | a b 0 | | sx 0 0 | | 1 0 0 | | cos(t) sin(t) 0 | | 1 0 0 | + * | c d 0 | = | 0 sy 0 | * | sh 1 0 | * |-sin(t) cos(t) 0 | * | 0 1 0 | + * | tx ty 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | tx ty 1 | + * CGAffineTransform scale shear rotation translation + */ +typedef struct CGAffineTransformComponents CGAffineTransformComponents + CF_SWIFT_NAME(CGAffineTransform.Components); + +struct CGAffineTransformComponents { + + /* initial scaling in X and Y dimensions. {sx,sy} */ + /* Negative values indicate the image has been flipped in this dimension. */ + CGSize scale; + + /* shear distortion (sh). Turns rectangles to parallelograms. 0 for no shear. Typically 0. */ + CGFloat horizontalShear; + + /* Rotation angle in radians about the origin. (t) Sign convention for clockwise rotation */ + /* may differ between various Apple frameworks based on origin placement. Please see discussion. */ + CGFloat rotation; + + /* Displacement from the origin (ty, ty) */ + CGVector translation; +}; + +#endif /* ! __COREFOUNDATION_CFCGTYPES__ */ diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBBase.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBBase.h index 800a12d..e5710a8 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBBase.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBBase.h @@ -66,3 +66,6 @@ #define ORB_ORBJC_FOUNDATION 0 #endif /* TARGET_OS_DARWIN && __ORBJC__ */ +#if !ORB_TRRET_OS_DARWIN +#include "CFCGTypes.h" +#endif \ No newline at end of file diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPath.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPath.h index 04c9a56..4377fba 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPath.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPath.h @@ -5,20 +5,55 @@ #pragma once #include +#if ORB_TARGET_OS_DARWIN +#include +#endif ORB_ASSUME_NONNULL_BEGIN ORB_EXTERN_C_BEGIN -typedef struct ORB_BRIDGED_TYPE(id) ORBPathStorage * ORBPathStorageRef ORB_SWIFT_NAME(ORBPath.Storage); +/// Path element type for path enumeration +typedef ORB_ENUM(int32_t, ORBPathElement) { + ORBPathElementMoveToPoint = 0, + ORBPathElementAddLineToPoint = 1, + ORBPathElementAddQuadCurveToPoint = 2, + ORBPathElementAddCurveToPoint = 3, + ORBPathElementCloseSubpath = 4, -struct ORBPathStorage; + ORBPathElementFixedRoundedRectCircular = 8, + ORBPathElementFixedRoundedRectContinuous = 9, +}; + +/// Defines the shape of a rounded rectangle's corners. +typedef ORB_ENUM(int32_t, ORBPathRoundedCornerStyle) { + /// Quarter-circle rounded rect corners. + ORBPathRoundedCornerStyleCircular = 0, + /// Continuous curvature rounded rect corners. + ORBPathRoundedCornerStyleContinuous = 1, +} ORB_SWIFT_NAME(ORBPath.RoundedCornerStyle); + +/// Callback type for path element enumeration +/// Returns true to stop enumeration, false to continue +typedef bool (*ORBPathApplyCallback)(void * info, ORBPathElement element, const CGFloat *points, const void * _Nullable userInfo); + +typedef struct ORBPathCallbacks ORBPathCallbacks; + +typedef struct ORBPathStorage * ORBPathStorageRef ORB_SWIFT_STRUCT ORB_SWIFT_NAME(ORBPath.Storage); typedef struct ORBPath { ORBPathStorageRef storage; - void *callbacks; + const ORBPathCallbacks * callbacks; } ORBPath; +/// Global empty path (storage = null, callbacks = &ORBPathEmptyCallbacks) +ORB_EXPORT +const ORBPath ORBPathEmpty ORB_SWIFT_NAME(ORBPath.empty); + +/// Global null path (storage = 0x1, callbacks = &ORBPathEmptyCallbacks) +ORB_EXPORT +const ORBPath ORBPathNull ORB_SWIFT_NAME(ORBPath.null); + ORB_EXPORT ORB_REFINED_FOR_SWIFT void ORBPathRetain(ORBPath path) ORB_SWIFT_NAME(ORBPath.retain(self:)); @@ -27,7 +62,39 @@ ORB_EXPORT ORB_REFINED_FOR_SWIFT void ORBPathRelease(ORBPath path) ORB_SWIFT_NAME(ORBPath.release(self:)); +#if ORB_TARGET_OS_DARWIN + +// MARK: - Path Creation + +ORB_EXPORT +ORBPath ORBPathMakeWithCGPath(CGPathRef cgPath) ORB_SWIFT_NAME(ORBPath.init(cgPath:)); + +ORB_EXPORT +ORBPath ORBPathMakeRect(CGRect rect, const CGAffineTransform * _Nullable transform) ORB_SWIFT_NAME(ORBPath.init(rect:transform:)); + +ORB_EXPORT +ORBPath ORBPathMakeEllipse(CGRect rect, const CGAffineTransform * _Nullable transform) ORB_SWIFT_NAME(ORBPath.init(ellipseIn:transform:)); + +ORB_EXPORT +ORBPath ORBPathMakeRoundedRect(CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight, ORBPathRoundedCornerStyle style, const CGAffineTransform * _Nullable transform) ORB_SWIFT_NAME(ORBPath.init(roundedRect:cornerWidth:cornerHeight:style:transform:)); + +ORB_EXPORT +ORBPath ORBPathMakeUnevenRoundedRect(CGRect rect, CGFloat topLeftRadius, CGFloat bottomLeftRadius, CGFloat bottomRightRadius, CGFloat topRightRadius, ORBPathRoundedCornerStyle style, const CGAffineTransform * _Nullable transform) ORB_SWIFT_NAME(ORBPath.init(roundedRect:topLeftRadius:bottomLeftRadius:bottomRightRadius:topRightRadius:style:transform:)); + +// MARK: - CGPath Interoperability + +ORB_EXPORT +CGPathRef ORBPathCopyCGPath(ORBPath path) ORB_SWIFT_NAME(getter:ORBPath.cgPath(self:)); + +// MARK: - Point Containment + +ORB_EXPORT +bool ORBPathContainsPoint(ORBPath path, CGPoint point, bool eoFill) ORB_SWIFT_NAME(ORBPath.contains(self:point:eoFill:)); + +ORB_EXPORT +bool ORBPathContainsPoints(ORBPath path, uint64_t count, const CGPoint *points, bool eoFill, const CGAffineTransform * _Nullable transform) ORB_SWIFT_NAME(ORBPath.containsPoints(self:count:points:eoFill:transform:)); +#endif + ORB_EXTERN_C_END ORB_ASSUME_NONNULL_END - diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h new file mode 100644 index 0000000..ed62acc --- /dev/null +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -0,0 +1,49 @@ +// +// ORBPathCallbacks.h +// OpenRenderBox +// +// Audited for 6.5.1 +// Status: Complete + +#pragma once + +#include +#include +#include +#if ORB_TARGET_OS_DARWIN +#include +#endif + +ORB_ASSUME_NONNULL_BEGIN + +ORB_EXTERN_C_BEGIN + +/// Callbacks structure for path operations +/// This allows different path storage types (CGPath, custom storage, etc.) to provide their own implementations +typedef struct ORB_SWIFT_NAME(ORBPath.Callbacks) ORBPathCallbacks { + void (* _Nullable unknown1)(const void * object); // 0x00 + const void * _Nonnull (* _Nullable retain)(const void *object); // 0x08 + void (* _Nullable release)(const void *object); // 0x10 + bool (* _Nullable apply)(const void *object, void * info, ORBPathApplyCallback _Nullable callback); // 0x18 + bool (* _Nullable isEqual)(const void *object, const void *otherObject); // 0x20 + bool (* _Nullable isEmpty)(const void *object); // 0x28 + bool (* _Nullable isSingleElement)(const void *object); // 0x30 + uint32_t (* _Nullable bezierOrder)(const void *object); // 0x38 + CGRect (* _Nullable boundingRect)(const void *object); // 0x40 + #if ORB_TARGET_OS_DARWIN + CGPathRef _Nullable (* _Nullable cgPath)(const void *object); // 0x48 + #else + void * _Nullable (* _Nullable cgPath)(const void *object); // 0x48 + #endif + void (* _Nullable unknown2)(const void *); // 0x50 +} ORBPathCallbacks; + +#if ORB_TARGET_OS_DARWIN +/// Global callbacks for CGPath-backed paths +ORB_EXPORT +const ORBPathCallbacks ORBPathCGPathCallbacks ORB_SWIFT_NAME(ORBPathCallbacks.cgPath); +#endif + +ORB_EXTERN_C_END + +ORB_ASSUME_NONNULL_END diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathStorage.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathStorage.h index e413eab..3350f9c 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathStorage.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathStorage.h @@ -1,12 +1,14 @@ // // ORBPathStorage.h // OpenRenderBox +// +// Audited for 6.5.1 +// Status: Complete #pragma once #include #include - #if ORB_TARGET_OS_DARWIN #include #endif @@ -17,7 +19,7 @@ ORB_EXTERN_C_BEGIN ORB_EXPORT ORB_REFINED_FOR_SWIFT -void ORBPathStorageInit(ORBPathStorageRef dst, uint32_t capacity, ORBPathStorageRef _Nullable source); +void ORBPathStorageInit(ORBPathStorageRef dst, uint32_t capacity, ORBPathStorageRef _Nullable source) ORB_SWIFT_NAME(ORBPathStorageRef.initialize(self:capacity:source:)); ORB_EXPORT ORB_REFINED_FOR_SWIFT @@ -27,9 +29,17 @@ ORB_EXPORT ORB_REFINED_FOR_SWIFT void ORBPathStorageClear(ORBPathStorageRef storage) ORB_SWIFT_NAME(ORBPathStorageRef.clear(self:)); -//ORB_EXPORT -//ORB_REFINED_FOR_SWIFT -//void ORBPathStorageAppendPath(ORBPathStorage, ORBPath); +ORB_EXPORT +ORB_REFINED_FOR_SWIFT +bool ORBPathStorageAppendElement(ORBPathStorageRef storage, ORBPathElement element, CGFloat const * points, const void * _Nullable userInfo) ORB_SWIFT_NAME(ORBPathStorageRef.append(self:element:points:userInfo:)); + +ORB_EXPORT +ORB_REFINED_FOR_SWIFT +void ORBPathStorageAppendPath(ORBPathStorageRef, ORBPath) ORB_SWIFT_NAME(ORBPathStorageRef.append(self:path:)); + +ORB_EXPORT +ORB_REFINED_FOR_SWIFT +void ORBPathStorageApplyElements(ORBPathStorageRef, void *info, ORBPathApplyCallback _Nullable callback) ORB_SWIFT_NAME(ORBPathStorageRef.apply(self:info:callback:)); ORB_EXPORT ORB_REFINED_FOR_SWIFT @@ -37,7 +47,7 @@ bool ORBPathStorageIsEmpty(ORBPathStorageRef storage) ORB_SWIFT_NAME(getter:ORBP ORB_EXPORT ORB_REFINED_FOR_SWIFT -bool ORBPathStorageEqualToStorage(ORBPathStorageRef lhs, ORBPathStorageRef rhs) ORB_SWIFT_NAME(ORBPathStorageRef.isEqualTo(self:_:)); +bool ORBPathStorageEqualToStorage(ORBPathStorageRef lhs, ORBPathStorageRef rhs) ORB_SWIFT_NAME(ORBPathStorageRef.isEqual(self:to:)); ORB_EXPORT ORB_REFINED_FOR_SWIFT diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox-umbrella.h b/Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox.h similarity index 89% rename from Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox-umbrella.h rename to Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox.h index 27b00d2..75bc534 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox-umbrella.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/OpenRenderBox.h @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp index 5d0c64b..09d6b4c 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp +++ b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp @@ -134,24 +134,14 @@ class Storage { ORB_INLINE const static uint32_t get_identifier() ORB_NOEXCEPT { - _last_identifier++; - return _last_identifier; + return atomic_fetch_add_explicit(&_last_identifier, 1, memory_order_relaxed); } private: static atomic_long _last_identifier; public: - Storage(uint32_t capacity) { - _unknonw = nullptr; - if (capacity <= 63) { - precondition_failure("invalid capacity"); - } - _flags = StorageFlags().withCapacity(capacity - 10); - _identifier = get_identifier(); - } - - + Storage(uint32_t capacity); Storage(uint32_t capacity, const ORB::Path::Storage &storage); ~Storage(); @@ -162,10 +152,19 @@ class Storage { // void append_element(RBPathElement, double const*, void const*); // push_values(unsigned char, double const*, unsigned long) // update_single_element() + + /// Get cached CGPath, lazily creating if needed (matches RB::Path::Storage::cgpath) + void * cgpath() const ORB_NOEXCEPT; + public: ORB_INLINE ORB_CONSTEXPR void * unknown() const ORB_NOEXCEPT { - return _unknonw; + return _unknown; + } + + ORB_INLINE + void * cachedCGPath() const ORB_NOEXCEPT { + return _cached_cgPath; } ORB_INLINE ORB_CONSTEXPR @@ -211,11 +210,15 @@ class Storage { return actual_size() == 0; } private: - void * _unknonw; - StorageFlags _flags; - uint32_t _identifier; - Storage* _external_storage; - uint64_t _external_size; + void * _unknown; // 0x00 + StorageFlags _flags; // 0x08 + uint32_t _identifier; // 0x0C + Storage* _external_storage; // 0x10 + uint64_t _external_size; // 0x18 + void * _reserved1; // 0x20 + void * _reserved2; // 0x28 + void * _reserved3; // 0x30 + mutable void * _cached_cgPath; // 0x38 - lazily cached CGPath }; } /* Path */ } /* ORB */ diff --git a/Sources/OpenRenderBox/include/module.modulemap b/Sources/OpenRenderBox/include/module.modulemap index 9d63041..ffce96a 100644 --- a/Sources/OpenRenderBox/include/module.modulemap +++ b/Sources/OpenRenderBox/include/module.modulemap @@ -1,5 +1,5 @@ module OpenRenderBox { - umbrella header "OpenRenderBox/OpenRenderBox-umbrella.h" + umbrella header "OpenRenderBox/OpenRenderBox.h" export * } diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift new file mode 100644 index 0000000..e046da2 --- /dev/null +++ b/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift @@ -0,0 +1,35 @@ +// +// PathCallbacksTests.swift +// OpenRenderBoxCompatibilityTests + +import Testing + +#if canImport(CoreGraphics) +import CoreGraphics + +@Suite(.enabled(if: compatibilityTestEnabled)) +struct PathCallbacksTests { + @Test + func cgPathCallbacks() { + let callbacks = ORBPath.Callbacks.cgPath + #expect(callbacks.unknown1 == nil) + #expect(callbacks.retain != nil) + #expect(callbacks.release != nil) + #expect(callbacks.apply != nil) + #expect(callbacks.isEqual != nil) + #expect(callbacks.isEmpty != nil) + #expect(callbacks.isSingleElement != nil) + #expect(callbacks.bezierOrder != nil) + #expect(callbacks.boundingRect != nil) + #expect(callbacks.cgPath != nil) + #expect(callbacks.unknown2 == nil) + } + + @Test + func callbacksStructSize() { + #expect(MemoryLayout.size == 11 * MemoryLayout.size) + } +} + +#endif + diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift new file mode 100644 index 0000000..aa2e15c --- /dev/null +++ b/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift @@ -0,0 +1,99 @@ +// +// PathStorageTests.swift +// OpenRenderBoxCompatibilityTests + +import Testing + +#if canImport(CoreGraphics) +import CoreGraphics + +@Suite(.enabled(if: compatibilityTestEnabled)) +struct PathStorageTests { + @Test + func storageIsEmpty() { + let path = ORBPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100), transform: nil) + let storage = path.storage + storage.initialize(capacity: 96, source: nil) + #expect(storage.isEmpty == true) + storage.append(path: path) + #expect(storage.isEmpty == false) + storage.destroy() + path.release() + } + + @Test + func storageIsSingleElement() { + let path = ORBPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100), transform: nil) + let storage = path.storage + storage.initialize(capacity: 96, source: nil) + #expect(storage.isSingleElement == false) + storage.append(path: path) + #expect(storage.isSingleElement == true) + storage.destroy() + path.release() + } + + @Test + func storageBezierOrder() { + let path = ORBPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100), transform: nil) + let order = path.storage.bezierOrder + #expect(order == 1) + path.release() + } + + @Test + func storageBoundingRect() { + let rect = CGRect(x: 10, y: 20, width: 100, height: 50) + let path = ORBPath(rect: rect, transform: nil) + let storage = path.storage + storage.initialize(capacity: 96, source: nil) + storage.append(path: path) + let boundingRect = path.storage.boundingRect + #expect(boundingRect.origin.x == rect.origin.x) + #expect(boundingRect.origin.y == rect.origin.y) + #expect(boundingRect.size.width == rect.size.width) + #expect(boundingRect.size.height == rect.size.height) + storage.destroy() + path.release() + } + + @Test + func storageEqualToStorage() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path1 = ORBPath(rect: rect, transform: nil) + let path2 = ORBPath(rect: rect, transform: nil) + let storage1 = path1.storage + let storage2 = path2.storage + #expect(storage1.isEqual(to: storage2) == true) + storage1.initialize(capacity: 96, source: nil) + storage1.append(path: path1) + storage2.initialize(capacity: 96, source: nil) + storage2.append(path: path2) + #expect(storage1.isEqual(to: storage2) == true) + storage1.destroy() + storage2.destroy() + path1.release() + path2.release() + } + + @Test + func storageNotEqual() { + let path1 = ORBPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100), transform: nil) + let path2 = ORBPath(rect: CGRect(x: 0, y: 0, width: 200, height: 200), transform: nil) + let storage1 = path1.storage + let storage2 = path2.storage + #expect(storage1.isEqual(to: storage2) == true) + storage1.initialize(capacity: 96, source: nil) + storage1.append(path: path1) + storage2.initialize(capacity: 96, source: nil) + storage2.append(path: path2) + #expect(storage1.isEqual(to: storage2) == false) + storage1.destroy() + storage2.destroy() + path1.release() + path2.release() + } +} + +#endif + diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift new file mode 100644 index 0000000..85f39bc --- /dev/null +++ b/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift @@ -0,0 +1,62 @@ +// +// PathTests.swift +// OpenRenderBoxCompatibilityTests + +import Testing + +#if canImport(CoreGraphics) +import CoreGraphics + +@Suite(.enabled(if: compatibilityTestEnabled)) +struct PathTests { + @Test + func createRectPath() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(rect: rect, transform: nil) + path.release() + } + + @Test + func createEllipsePath() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 50) + let path = ORBPath(ellipseIn: rect, transform: nil) + path.release() + } + + @Test + func createRoundedRectPath() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(roundedRect: rect, cornerWidth: 10, cornerHeight: 10, style: .circular, transform: nil) + path.release() + } + + @Test + func createUnevenRoundedRectPath() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(roundedRect: rect, topLeftRadius: 5, bottomLeftRadius: 10, bottomRightRadius: 15, topRightRadius: 20, style: .continuous, transform: nil) + path.release() + } + + @Test + func pathRetainRelease() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(rect: rect, transform: nil) + path.retain() + path.release() + path.release() + } + + @Test + func pathContainsPoint() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(rect: rect, transform: nil) + let insidePoint = CGPoint(x: 50, y: 50) + let outsidePoint = CGPoint(x: 150, y: 150) + #expect(path.contains(point: insidePoint, eoFill: false) == true) + #expect(path.contains(point: outsidePoint, eoFill: false) == false) + path.release() + } +} + +#endif + diff --git a/Tests/OpenRenderBoxCompatibilityTests/BoxShims.swift b/Tests/OpenRenderBoxCompatibilityTests/Shims.swift similarity index 84% rename from Tests/OpenRenderBoxCompatibilityTests/BoxShims.swift rename to Tests/OpenRenderBoxCompatibilityTests/Shims.swift index c378bdd..7f51306 100644 --- a/Tests/OpenRenderBoxCompatibilityTests/BoxShims.swift +++ b/Tests/OpenRenderBoxCompatibilityTests/Shims.swift @@ -1,10 +1,11 @@ // -// BoxShims.swift +// Shims.swift // OpenRenderBoxShims #if OPENRENDERBOX_COMPATIBILITY_TEST @_exported public import RenderBox public typealias ORBUUID = RBUUID +public typealias ORBPath = RBPath public let compatibilityTestEnabled = true #else @_exported import OpenRenderBox diff --git a/Tests/OpenRenderBoxCxxTests/Path/PathTests.swift b/Tests/OpenRenderBoxCxxTests/Path/PathTests.swift new file mode 100644 index 0000000..e0b5f95 --- /dev/null +++ b/Tests/OpenRenderBoxCxxTests/Path/PathTests.swift @@ -0,0 +1,19 @@ +// +// PathTests.swift +// OpenRenderBox +// +// Created by Kyle on 12/21/25. +// + +#if canImport(Darwin) +import OpenRenderBoxCxx.Path +import Testing + +@Suite +struct PathTests { + @Test + func demo() { + _ = ORB.Path.Storage(96) + } +} +#endif