From 23ebaf09971ef3c88689bed71849da2ebe23c119 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 15:39:22 +0800 Subject: [PATCH 1/9] Add ORBPathCallbacksExtended --- Sources/OpenRenderBox/Path/ORBPath.cpp | 2 +- .../OpenRenderBox/Path/ORBPathCallbacks.cpp | 22 ++++++------- Sources/OpenRenderBox/Path/PathStorage.cpp | 4 +-- .../include/OpenRenderBox/ORBPathCallbacks.h | 31 +++++++++++++++++-- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Sources/OpenRenderBox/Path/ORBPath.cpp b/Sources/OpenRenderBox/Path/ORBPath.cpp index 2132037..28905e2 100644 --- a/Sources/OpenRenderBox/Path/ORBPath.cpp +++ b/Sources/OpenRenderBox/Path/ORBPath.cpp @@ -12,7 +12,7 @@ // Empty path callbacks (all null) - C++ internal linkage static const ORBPathCallbacks empty_path_callbacks = { - nullptr, + {}, nullptr, nullptr, nullptr, diff --git a/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp b/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp index a2c96a5..fbd7e06 100644 --- a/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp +++ b/Sources/OpenRenderBox/Path/ORBPathCallbacks.cpp @@ -33,10 +33,10 @@ namespace { } const ORBPathCallbacks ORBPathCGPathCallbacks = { - nullptr, - CFRetain, - CFRelease, - +[](const void *object, void *info, ORBPathApplyCallback callback) -> bool { + .flags = {}, + .retain = CFRetain, + .release = CFRelease, + .apply = +[](const void *object, void *info, ORBPathApplyCallback callback) -> bool { CGPathRef cgPath = reinterpret_cast(object); __block bool shouldStop = false; CGPathApplyWithBlock2(cgPath, ^(const CGPathElement *element, bool *stop) { @@ -51,25 +51,25 @@ const ORBPathCallbacks ORBPathCGPathCallbacks = { }); return !shouldStop; }, - +[](const void *object, const void *otherObject) -> bool { + .isEqual = +[](const void *object, const void *otherObject) -> bool { return CGPathEqualToPath(static_cast(object), static_cast(otherObject)); }, - +[](const void *object) -> bool { + .isEmpty = +[](const void *object) -> bool { return CGPathIsEmpty(static_cast(object)); }, - +[](const void *object) -> bool { + .isSingleElement = +[](const void *object) -> bool { return false; }, - +[](const void *object) -> uint32_t { + .bezierOrder = +[](const void *object) -> uint32_t { return cgpath_bezier_order(static_cast(object)); }, - +[](const void *object) -> CGRect { + .boundingRect = +[](const void *object) -> CGRect { return CGPathGetPathBoundingBox(static_cast(object)); }, - +[](const void *object) -> CGPathRef { + .cgPath = +[](const void *object) -> CGPathRef { return static_cast(object); }, - nullptr, + .next = nullptr, }; #endif /* ORB_TARGET_OS_DARWIN */ diff --git a/Sources/OpenRenderBox/Path/PathStorage.cpp b/Sources/OpenRenderBox/Path/PathStorage.cpp index b7d56bc..ab4e0c9 100644 --- a/Sources/OpenRenderBox/Path/PathStorage.cpp +++ b/Sources/OpenRenderBox/Path/PathStorage.cpp @@ -119,10 +119,10 @@ CGPathRef Storage::cgpath() const ORB_NOEXCEPT { return cached; } static const ORBPathCallbacks callbacks = { + {}, nullptr, nullptr, - nullptr, - +[](const void *object, void *info, ORBPathApplyCallback callback) -> bool { + .apply = +[](const void *object, void *info, ORBPathApplyCallback callback) -> bool { auto storage = reinterpret_cast(object); return storage->apply_elements(info, callback); }, diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h index 39c54bf..7230764 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -17,10 +17,18 @@ ORB_ASSUME_NONNULL_BEGIN ORB_EXTERN_C_BEGIN +/// Flags for path callbacks +typedef struct ORBPathCallbacksFlags { + uint8_t unknown0; // 0x0 + uint8_t unknown1; // 0x1 + uint8_t isExtended; // 0x2 - bit 0: if true, cast to ORBPathCallbacksExtended + uint8_t padding[5]; // 0x3-0x7 +} ORBPathCallbacksFlags; + /// 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 + ORBPathCallbacksFlags flags; // 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 @@ -34,9 +42,28 @@ typedef struct ORB_SWIFT_NAME(ORBPath.Callbacks) ORBPathCallbacks { #else void * _Nullable (* _Nullable cgPath)(const void *object); // 0x48 #endif - void (* _Nullable unknown2)(const void *); // 0x50 + const struct ORBPathCallbacks * _Nullable next; // 0x50 } ORBPathCallbacks; +/// Extended callbacks structure with additional extended callbacks argument +typedef struct ORB_SWIFT_NAME(ORBPath.CallbacksExtended) ORBPathCallbacksExtended { + ORBPathCallbacksFlags flags; // 0x00 + const void * _Nonnull (* _Nullable retain)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x08 + void (* _Nullable release)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x10 + bool (* _Nullable apply)(const void *object, void * info, ORBPathApplyCallback _Nullable callback, const struct ORBPathCallbacksExtended *extended); // 0x18 + bool (* _Nullable isEqual)(const void *object, const void *otherObject, const struct ORBPathCallbacksExtended *extended); // 0x20 + bool (* _Nullable isEmpty)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x28 + bool (* _Nullable isSingleElement)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x30 + uint32_t (* _Nullable bezierOrder)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x38 + CGRect (* _Nullable boundingRect)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x40 + #if ORB_TARGET_OS_DARWIN + CGPathRef _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x48 + #else + void * _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x48 + #endif + const struct ORBPathCallbacks * _Nullable next; // 0x50 +} ORBPathCallbacksExtended; + #if ORB_TARGET_OS_DARWIN /// Global callbacks for CGPath-backed paths ORB_EXPORT From 6a7489a363eba06abed6be9154adaf6851de3c98 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 16:52:59 +0800 Subject: [PATCH 2/9] Add NestedCallbacks --- .../OpenRenderBox/Path/NestedCallbacks.cpp | 305 ++++++++++++++++++ Sources/OpenRenderBox/Path/ORBPath.cpp | 2 +- Sources/OpenRenderBox/Path/ORBPathStorage.cpp | 2 +- .../Path/{PathStorage.cpp => Storage.cpp} | 4 +- .../include/OpenRenderBox/ORBPathCallbacks.h | 4 +- .../OpenRenderBoxCxx/Path/NestedCallbacks.hpp | 54 ++++ .../Path/{PathStorage.hpp => Storage.hpp} | 2 +- 7 files changed, 366 insertions(+), 7 deletions(-) create mode 100644 Sources/OpenRenderBox/Path/NestedCallbacks.cpp rename Sources/OpenRenderBox/Path/{PathStorage.cpp => Storage.cpp} (98%) create mode 100644 Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/NestedCallbacks.hpp rename Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/{PathStorage.hpp => Storage.hpp} (99%) diff --git a/Sources/OpenRenderBox/Path/NestedCallbacks.cpp b/Sources/OpenRenderBox/Path/NestedCallbacks.cpp new file mode 100644 index 0000000..00e8d88 --- /dev/null +++ b/Sources/OpenRenderBox/Path/NestedCallbacks.cpp @@ -0,0 +1,305 @@ +// +// NestedCallbacks.cpp +// OpenRenderBox + +// TODO: Implement NestedCallbacks + +#include +#include +#if ORB_TARGET_OS_DARWIN +#include +#endif +#include + +namespace ORB { +namespace Path { + +#if ORB_TARGET_OS_DARWIN + +namespace { + +struct CacheEntry { + const ORBPathCallbacks* callbacks; + size_t index; + const NestedCallbacks* result; +}; + +struct Cache { + ORB::vector entries; + os_unfair_lock lock; + + Cache() : entries(), lock(OS_UNFAIR_LOCK_INIT) {} +}; + +Cache* gCache = nullptr; + +// Helper to call bezierOrder on callbacks +inline uint32_t callBezierOrder(const void* object, const ORBPathCallbacks* callbacks) { + if (callbacks->bezierOrder) { + return callbacks->bezierOrder(object); + } + return 3; // Default: cubic bezier +} + +} // anonymous namespace + +NestedCallbacks::NestedCallbacks(const ORBPathCallbacks* callbacks, size_t index) + : extended{} + , originalCallbacks(callbacks) + , nestedIndex(index) +{ + // Set isExtended flag (byte at offset 0x2) + extended.flags.isExtended = 1; + + // Copy retain/release from original callbacks + extended.retain = reinterpret_cast(callbacks->retain); + extended.release = reinterpret_cast(callbacks->release); + + extended.apply = +[](const void *object, void *info, ORBPathApplyCallback callback, const ORBPathCallbacksExtended *ext) -> bool { + auto self = reinterpret_cast(ext); + auto original = self->originalCallbacks; + if (original->apply) { + return original->apply(object, info, callback); + } + return true; + }; + + extended.isEqual = nullptr; + + extended.isEmpty = +[](const void *object, const ORBPathCallbacksExtended *ext) -> bool { + auto self = reinterpret_cast(ext); + auto original = self->originalCallbacks; + if (original->apply) { + // Use single_element_callback to detect emptiness + struct Info { + size_t count; + size_t depth; + bool sawClose; + } info = {0, 0, false}; + info.depth = self->nestedIndex; + original->apply(object, &info, NestedCallbacks::single_element_callback); + return info.count == 1; + } + return true; + }; + + extended.bezierOrder = +[](const void *object, const ORBPathCallbacksExtended *ext) -> uint32_t { + auto self = reinterpret_cast(ext); + return callBezierOrder(object, self->originalCallbacks); + }; + + extended.isSingleElement = nullptr; + extended.boundingRect = nullptr; + extended.cgPath = nullptr; + + extended.next = +[](const void *object, const ORBPathCallbacksExtended *ext) -> const ORBPathCallbacksExtended* { + auto self = reinterpret_cast(ext); + auto nextCallbacks = NestedCallbacks::get(self->originalCallbacks, self->nestedIndex + 1); + return &nextCallbacks->extended; + }; +} + +const NestedCallbacks* NestedCallbacks::get(const ORBPathCallbacks* callbacks, size_t index) { + // Lazy initialization of global cache + static std::once_flag initFlag; + std::call_once(initFlag, []() { + gCache = new Cache(); + }); + + os_unfair_lock_lock(&gCache->lock); + + // Binary search for existing entry + auto& entries = gCache->entries; + size_t lo = 0; + size_t hi = entries.size(); + + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + const auto& entry = entries[mid]; + + if (entry.callbacks < callbacks) { + lo = mid + 1; + } else if (entry.callbacks > callbacks) { + hi = mid; + } else if (entry.index < index) { + lo = mid + 1; + } else if (entry.index > index) { + hi = mid; + } else { + // Found exact match + const NestedCallbacks* result = entry.result; + os_unfair_lock_unlock(&gCache->lock); + return result; + } + } + + // Not found, create new NestedCallbacks + auto* nested = new NestedCallbacks(callbacks, index); + + // Insert at position 'lo' to maintain sorted order + entries.reserve(entries.size() + 1); + + // Shift elements to make room + if (lo < entries.size()) { + // Use memmove for efficiency + size_t count = entries.size() - lo; + memmove(&entries[lo + 1], &entries[lo], count * sizeof(CacheEntry)); + } + + // Insert new entry + entries[lo] = CacheEntry{callbacks, index, nested}; + + os_unfair_lock_unlock(&gCache->lock); + return nested; +} + +// Mask for nesting elements (bits 17-20,21-24): used to detect depth changes +// From assembly: 0x1de0000 = bits for elements 17, 18, 19, 20, 22, 23, 24 +static constexpr uint32_t kNestingElementMask = 0x1de0000; + +// Helper to check if an element affects nesting depth +static inline bool isNestingElement(ORBPathElement element) { + if (element > 0x18) return false; + return ((1u << element) & kNestingElementMask) != 0; +} + +// Helper to check if element is in range 0x0-0x3 (move/line/quad/curve) +static inline bool isDrawingElement(ORBPathElement element) { + return element < 4; +} + +// apply_elements_callback: tracks nesting depth during path enumeration +// Info structure: { context, callback, depth, elementCount, sawClose } +bool NestedCallbacks::apply_elements_callback(void* info, ORBPathElement element, const double* points, const void* userInfo) { + struct ApplyInfo { + void* context; + ORBPathApplyCallback callback; + size_t depth; + }; + auto* applyInfo = static_cast(info); + + if (element > 0x18) { + // Forward to user callback if we're at depth 0 + if (applyInfo->depth == 0) { + return applyInfo->callback(applyInfo->context, element, points, userInfo); + } + return false; + } + + uint32_t mask = 1u << element; + if (mask & kNestingElementMask) { + // Nesting element: increment depth + applyInfo->depth++; + return false; + } + + if (element == 0x10) { // Close subpath + if (applyInfo->depth > 0) { + applyInfo->depth--; + } + return false; + } + + // Forward non-nesting elements at depth 0 + if (applyInfo->depth == 0) { + return applyInfo->callback(applyInfo->context, element, points, userInfo); + } + return false; +} + +// single_element_callback: checks if path is a single element +// Info structure: { depth, elementCount, sawClose } +bool NestedCallbacks::single_element_callback(void* info, ORBPathElement element, const double* points, const void* userInfo) { + struct SingleInfo { + size_t depth; + size_t count; + bool sawClose; + }; + auto* singleInfo = static_cast(info); + + if (element > 0x18) { + // Elements > 0x18: count as elements at depth 0 + if (singleInfo->depth == singleInfo->count) { + singleInfo->count++; + if (element == 0x13) { // Specific element marker + singleInfo->sawClose = false; + } else { + singleInfo->sawClose = true; + } + } + return false; + } + + uint32_t mask = 1u << element; + + // Handle nesting elements + if ((mask & 0xf000) != 0) { + // Elements 12-15 + if (singleInfo->depth == singleInfo->count) { + singleInfo->sawClose = true; + singleInfo->count++; + } + return false; + } + + if (mask & kNestingElementMask) { + // Other nesting elements: increment depth + if (singleInfo->depth == singleInfo->count) { + singleInfo->count++; + } + return false; + } + + if (element == 0x10) { // Close subpath + if (singleInfo->depth > 0) { + singleInfo->depth--; + } + if (singleInfo->depth == singleInfo->count && singleInfo->sawClose) { + singleInfo->sawClose = false; + singleInfo->count++; + } + return false; + } + + // Drawing elements (0-3) + if (isDrawingElement(element)) { + if (singleInfo->depth == singleInfo->count) { + singleInfo->sawClose = true; + singleInfo->count++; + } + } + + // Return true if count >= 2 (stop enumeration) + return singleInfo->count >= 2; +} + +// apply_elements_fast: fast path for Storage +bool NestedCallbacks::apply_elements_fast(const Storage& storage, void* info) { + // TODO: Implement fast path using Storage iterator + (void)storage; + (void)info; + return true; +} + +// single_element_fast: fast path for single element detection on Storage +bool NestedCallbacks::single_element_fast(const Storage& storage, void* info) { + // TODO: Implement fast path using Storage iterator + (void)storage; + (void)info; + return true; +} + +// first_element: find first element at given nesting depth +bool NestedCallbacks::first_element(const Storage& storage, void* iterator, size_t depth) { + // TODO: Implement using Storage iterator + (void)storage; + (void)iterator; + (void)depth; + return false; +} + +#endif /* ORB_TARGET_OS_DARWIN */ + +} /* namespace Path */ +} /* namespace ORB */ + diff --git a/Sources/OpenRenderBox/Path/ORBPath.cpp b/Sources/OpenRenderBox/Path/ORBPath.cpp index 28905e2..c1b75c1 100644 --- a/Sources/OpenRenderBox/Path/ORBPath.cpp +++ b/Sources/OpenRenderBox/Path/ORBPath.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include // Empty path callbacks (all null) - C++ internal linkage diff --git a/Sources/OpenRenderBox/Path/ORBPathStorage.cpp b/Sources/OpenRenderBox/Path/ORBPathStorage.cpp index 9f2e480..4b76466 100644 --- a/Sources/OpenRenderBox/Path/ORBPathStorage.cpp +++ b/Sources/OpenRenderBox/Path/ORBPathStorage.cpp @@ -3,7 +3,7 @@ // OpenRenderBox #include -#include +#include #include using namespace ORB; diff --git a/Sources/OpenRenderBox/Path/PathStorage.cpp b/Sources/OpenRenderBox/Path/Storage.cpp similarity index 98% rename from Sources/OpenRenderBox/Path/PathStorage.cpp rename to Sources/OpenRenderBox/Path/Storage.cpp index ab4e0c9..5aae837 100644 --- a/Sources/OpenRenderBox/Path/PathStorage.cpp +++ b/Sources/OpenRenderBox/Path/Storage.cpp @@ -1,10 +1,10 @@ // -// PathStorage.cpp +// Storage.cpp // OpenRenderBox #include #include -#include +#include #include #if ORB_TARGET_OS_DARWIN diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h index 7230764..bc23096 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -42,7 +42,7 @@ typedef struct ORB_SWIFT_NAME(ORBPath.Callbacks) ORBPathCallbacks { #else void * _Nullable (* _Nullable cgPath)(const void *object); // 0x48 #endif - const struct ORBPathCallbacks * _Nullable next; // 0x50 + const struct ORBPathCallbacks * _Nullable (* _Nullable next)(const void *object); // 0x50 } ORBPathCallbacks; /// Extended callbacks structure with additional extended callbacks argument @@ -61,7 +61,7 @@ typedef struct ORB_SWIFT_NAME(ORBPath.CallbacksExtended) ORBPathCallbacksExtende #else void * _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x48 #endif - const struct ORBPathCallbacks * _Nullable next; // 0x50 + const struct ORBPathCallbacksExtended * _Nullable (* _Nullable next)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x50 } ORBPathCallbacksExtended; #if ORB_TARGET_OS_DARWIN diff --git a/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/NestedCallbacks.hpp b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/NestedCallbacks.hpp new file mode 100644 index 0000000..f117434 --- /dev/null +++ b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/NestedCallbacks.hpp @@ -0,0 +1,54 @@ +// +// NestedCallbacks.hpp +// OpenRenderBox + +#pragma once + +#include +#include + +ORB_ASSUME_NONNULL_BEGIN + +namespace ORB { +namespace Path { + +class Storage; + +/// Extended callbacks wrapper for accessing nested paths. +/// Maintains a global cache to avoid creating duplicate wrappers. +struct NestedCallbacks { + ORBPathCallbacksExtended extended; // 0x00-0x57 + const ORBPathCallbacks * _Nonnull originalCallbacks; // 0x58 + size_t nestedIndex; // 0x60 + + /// Get or create a NestedCallbacks for the given callbacks and nesting index + static const NestedCallbacks* get(const ORBPathCallbacks* callbacks, size_t index); + + /// Callback for apply_elements that tracks nesting depth + static bool apply_elements_callback(void* info, ORBPathElement element, const double* points, const void* userInfo); + + /// Callback for single_element detection + static bool single_element_callback(void* info, ORBPathElement element, const double* points, const void* userInfo); + + /// Fast path for applying elements on Storage + static bool apply_elements_fast(const Storage& storage, void* info); + + /// Fast path for single element detection on Storage + static bool single_element_fast(const Storage& storage, void* info); + + /// Find the first element at a given nesting depth + static bool first_element(const Storage& storage, void* iterator, size_t depth); + +private: + NestedCallbacks(const ORBPathCallbacks* callbacks, size_t index); +}; + +static_assert(sizeof(NestedCallbacks) == 0x68, "NestedCallbacks size mismatch"); +static_assert(offsetof(NestedCallbacks, originalCallbacks) == 0x58, "originalCallbacks offset mismatch"); +static_assert(offsetof(NestedCallbacks, nestedIndex) == 0x60, "nestedIndex offset mismatch"); + +} /* namespace Path */ +} /* namespace ORB */ + +ORB_ASSUME_NONNULL_END + diff --git a/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/Storage.hpp similarity index 99% rename from Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp rename to Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/Storage.hpp index 1dcf843..c2d53bb 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/PathStorage.hpp +++ b/Sources/OpenRenderBox/include/OpenRenderBoxCxx/Path/Storage.hpp @@ -1,5 +1,5 @@ // -// PathStorage.hpp +// Storage.hpp // OpenRenderBox #pragma once From a7760cec8f786ab9b738f0133e37fe243fe0c604 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:18:58 +0800 Subject: [PATCH 3/9] Update Callback header --- .../include/OpenRenderBox/ORBPathCallbacks.h | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h index bc23096..372399f 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -9,6 +9,7 @@ #include #include +#include #if ORB_TARGET_OS_DARWIN #include #endif @@ -19,51 +20,77 @@ ORB_EXTERN_C_BEGIN /// Flags for path callbacks typedef struct ORBPathCallbacksFlags { - uint8_t unknown0; // 0x0 - uint8_t unknown1; // 0x1 - uint8_t isExtended; // 0x2 - bit 0: if true, cast to ORBPathCallbacksExtended - uint8_t padding[5]; // 0x3-0x7 + uint8_t unknown0; + uint8_t unknown1; + uint8_t isExtended; + uint8_t padding[5]; } ORBPathCallbacksFlags; /// 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 { - ORBPathCallbacksFlags flags; // 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 + ORBPathCallbacksFlags flags; + const void * _Nonnull (* _Nullable retain)(const void *object); + void (* _Nullable release)(const void *object); + bool (* _Nullable apply)(const void *object, void * info, ORBPathApplyCallback _Nullable callback); + bool (* _Nullable isEqual)(const void *object, const void *otherObject); + bool (* _Nullable isEmpty)(const void *object); + bool (* _Nullable isSingleElement)(const void *object); + uint32_t (* _Nullable bezierOrder)(const void *object); + CGRect (* _Nullable boundingRect)(const void *object); #if ORB_TARGET_OS_DARWIN - CGPathRef _Nullable (* _Nullable cgPath)(const void *object); // 0x48 + CGPathRef _Nullable (* _Nullable cgPath)(const void *object); #else - void * _Nullable (* _Nullable cgPath)(const void *object); // 0x48 + void * _Nullable (* _Nullable cgPath)(const void *object); #endif - const struct ORBPathCallbacks * _Nullable (* _Nullable next)(const void *object); // 0x50 + const struct ORBPathCallbacks * _Nullable (* _Nullable next)(const void *object); } ORBPathCallbacks; +static_assert(sizeof(ORBPathCallbacks) == 0x58); +static_assert(offsetof(ORBPathCallbacks, flags) == 0x00); +static_assert(offsetof(ORBPathCallbacks, retain) == 0x08); +static_assert(offsetof(ORBPathCallbacks, release) == 0x10); +static_assert(offsetof(ORBPathCallbacks, apply) == 0x18); +static_assert(offsetof(ORBPathCallbacks, isEqual) == 0x20); +static_assert(offsetof(ORBPathCallbacks, isEmpty) == 0x28); +static_assert(offsetof(ORBPathCallbacks, isSingleElement) == 0x30); +static_assert(offsetof(ORBPathCallbacks, bezierOrder) == 0x38); +static_assert(offsetof(ORBPathCallbacks, boundingRect) == 0x40); +static_assert(offsetof(ORBPathCallbacks, cgPath) == 0x48); +static_assert(offsetof(ORBPathCallbacks, next) == 0x50); + /// Extended callbacks structure with additional extended callbacks argument typedef struct ORB_SWIFT_NAME(ORBPath.CallbacksExtended) ORBPathCallbacksExtended { - ORBPathCallbacksFlags flags; // 0x00 - const void * _Nonnull (* _Nullable retain)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x08 - void (* _Nullable release)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x10 - bool (* _Nullable apply)(const void *object, void * info, ORBPathApplyCallback _Nullable callback, const struct ORBPathCallbacksExtended *extended); // 0x18 - bool (* _Nullable isEqual)(const void *object, const void *otherObject, const struct ORBPathCallbacksExtended *extended); // 0x20 - bool (* _Nullable isEmpty)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x28 - bool (* _Nullable isSingleElement)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x30 - uint32_t (* _Nullable bezierOrder)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x38 - CGRect (* _Nullable boundingRect)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x40 + ORBPathCallbacksFlags flags; + const void * _Nonnull (* _Nullable retain)(const void *object); + void (* _Nullable release)(const void *object); + bool (* _Nullable apply)(const void *object, void * info, ORBPathApplyCallback _Nullable callback, const struct ORBPathCallbacksExtended *extended); + bool (* _Nullable isEqual)(const void *object, const void *otherObject, const struct ORBPathCallbacksExtended *extended); + bool (* _Nullable isEmpty)(const void *object, const struct ORBPathCallbacksExtended *extended); + bool (* _Nullable isSingleElement)(const void *object, const struct ORBPathCallbacksExtended *extended); + uint32_t (* _Nullable bezierOrder)(const void *object, const struct ORBPathCallbacksExtended *extended); + CGRect (* _Nullable boundingRect)(const void *object, const struct ORBPathCallbacksExtended *extended); #if ORB_TARGET_OS_DARWIN - CGPathRef _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x48 + CGPathRef _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); #else - void * _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x48 + void * _Nullable (* _Nullable cgPath)(const void *object, const struct ORBPathCallbacksExtended *extended); #endif - const struct ORBPathCallbacksExtended * _Nullable (* _Nullable next)(const void *object, const struct ORBPathCallbacksExtended *extended); // 0x50 + const struct ORBPathCallbacksExtended * _Nullable (* _Nullable next)(const void *object, const struct ORBPathCallbacksExtended *extended); } ORBPathCallbacksExtended; +static_assert(sizeof(ORBPathCallbacksExtended) == 0x58); +static_assert(offsetof(ORBPathCallbacksExtended, flags) == 0x00); +static_assert(offsetof(ORBPathCallbacksExtended, retain) == 0x08); +static_assert(offsetof(ORBPathCallbacksExtended, release) == 0x10); +static_assert(offsetof(ORBPathCallbacksExtended, apply) == 0x18); +static_assert(offsetof(ORBPathCallbacksExtended, isEqual) == 0x20); +static_assert(offsetof(ORBPathCallbacksExtended, isEmpty) == 0x28); +static_assert(offsetof(ORBPathCallbacksExtended, isSingleElement) == 0x30); +static_assert(offsetof(ORBPathCallbacksExtended, bezierOrder) == 0x38); +static_assert(offsetof(ORBPathCallbacksExtended, boundingRect) == 0x40); +static_assert(offsetof(ORBPathCallbacksExtended, cgPath) == 0x48); +static_assert(offsetof(ORBPathCallbacksExtended, next) == 0x50); + #if ORB_TARGET_OS_DARWIN /// Global callbacks for CGPath-backed paths ORB_EXPORT From 389434d779d79007aa32a0f77f6bfa014b78c0cb Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:19:26 +0800 Subject: [PATCH 4/9] Add isExtended check for apply and isEmpty --- Sources/OpenRenderBox/Path/ORBPath.cpp | 51 +++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/Sources/OpenRenderBox/Path/ORBPath.cpp b/Sources/OpenRenderBox/Path/ORBPath.cpp index c1b75c1..82b88eb 100644 --- a/Sources/OpenRenderBox/Path/ORBPath.cpp +++ b/Sources/OpenRenderBox/Path/ORBPath.cpp @@ -147,33 +147,58 @@ ORBPath ORBPathMakeUnevenRoundedRect(CGRect rect, CGFloat topLeftRadius, CGFloat bool ORBPathIsEmpty(ORBPath path) { if (path.callbacks == &empty_path_callbacks) { return true; + } + if (path.callbacks->flags.isExtended) { + auto extended = reinterpret_cast(path.callbacks); + auto isEmptyCallback = extended->isEmpty; + if (isEmptyCallback) { + return isEmptyCallback(path.storage, extended); + } else { + bool isEmpty = true; + auto applyCallback = extended->apply; + if (applyCallback) { + applyCallback(path.storage, &isEmpty, +[](void * info, ORBPathElement element, const CGFloat *points, const void * _Nullable userInfo) -> bool { + *((bool *)info) = false; + return false; + }, extended); + } + return isEmpty; + } } else { auto isEmptyCallback = path.callbacks->isEmpty; if (isEmptyCallback) { return isEmptyCallback(path.storage); } else { bool isEmpty = true; - return ORBPathApplyElements(path, &isEmpty, +[](void * info, ORBPathElement element, const CGFloat *points, const void * _Nullable userInfo) -> bool { - *((bool *)info) = false; - return false; - }); + auto applyCallback = path.callbacks->apply; + if (applyCallback) { + applyCallback(path.storage, &isEmpty, +[](void * info, ORBPathElement element, const CGFloat *points, const void * _Nullable userInfo) -> bool { + *((bool *)info) = false; + return false; + }); + } + return isEmpty; } } } bool ORBPathApplyElements(ORBPath path, void *info, ORBPathApplyCallback callback) { - auto apply = path.callbacks->apply; - bool flag = false; // TODO: calllbacks's flag to indicate whether it supports extra features - if (flag) { - if (callback == nullptr) { - return true; + if (callback == nullptr) { + return true; + } + if (path.callbacks->flags.isExtended) { + auto extended = reinterpret_cast(path.callbacks); + auto applyCallback = extended->apply; + if (applyCallback) { + return applyCallback(path.storage, info, callback, extended); } - return apply(path.storage, info, callback/*, path.callbacks*/); + return true; } else { - if (callback == nullptr) { - return true; + auto applyCallback = path.callbacks->apply; + if (applyCallback) { + return applyCallback(path.storage, info, callback); } - return apply(path.storage, info, callback); + return true; } } From 7bbb6c55eefff05f47c4a10813ddac9008407b57 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:26:15 +0800 Subject: [PATCH 5/9] Update ORBPathEqualToPath --- Sources/OpenRenderBox/Path/ORBPath.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/OpenRenderBox/Path/ORBPath.cpp b/Sources/OpenRenderBox/Path/ORBPath.cpp index 82b88eb..43f3de0 100644 --- a/Sources/OpenRenderBox/Path/ORBPath.cpp +++ b/Sources/OpenRenderBox/Path/ORBPath.cpp @@ -207,6 +207,20 @@ bool ORBPathEqualToPath(ORBPath lhs, ORBPath rhs) { if (lhs.storage == rhs.storage) { return true; } + auto callbacks = lhs.callbacks; + if (callbacks->flags.isExtended) { + auto extended = reinterpret_cast(callbacks); + auto isEqualCallback = extended->isEqual; + if (isEqualCallback) { + return isEqualCallback(lhs.storage, rhs.storage, extended); + } + } else { + auto isEqualCallback = callbacks->isEqual; + if (isEqualCallback) { + return isEqualCallback(lhs.storage, rhs.storage); + } + } + // auto storage = ORB::Path::Storage(1088); // TODO return false; } else { From 09f51cffa555b05a99d18c0c7f613b4495d1db22 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:51:52 +0800 Subject: [PATCH 6/9] Update test case --- .../include/OpenRenderBox/ORBPathCallbacks.h | 2 +- .../PathCallbacksTests.swift | 9 +-- .../PathStorageTests.swift | 60 +++++++------------ .../PathTests.swift | 56 +++++++++++++++-- 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h index 372399f..16155ce 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -22,7 +22,7 @@ ORB_EXTERN_C_BEGIN typedef struct ORBPathCallbacksFlags { uint8_t unknown0; uint8_t unknown1; - uint8_t isExtended; + bool isExtended; uint8_t padding[5]; } ORBPathCallbacksFlags; diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift index e046da2..31bc75f 100644 --- a/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift +++ b/Tests/OpenRenderBoxCompatibilityTests/PathCallbacksTests.swift @@ -12,7 +12,7 @@ struct PathCallbacksTests { @Test func cgPathCallbacks() { let callbacks = ORBPath.Callbacks.cgPath - #expect(callbacks.unknown1 == nil) + #expect(callbacks.flags.isExtended == false) #expect(callbacks.retain != nil) #expect(callbacks.release != nil) #expect(callbacks.apply != nil) @@ -22,12 +22,7 @@ struct PathCallbacksTests { #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) + #expect(callbacks.next == nil) } } diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift index 1b6e1ca..4883f1b 100644 --- a/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift +++ b/Tests/OpenRenderBoxCompatibilityTests/PathStorageTests.swift @@ -32,7 +32,7 @@ struct PathStorageTests { storage.destroy() path.release() } - + @Test func storageBezierOrder() { let path = ORBPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100), transform: nil) @@ -40,7 +40,7 @@ struct PathStorageTests { #expect(order == 1) path.release() } - + @Test func storageBoundingRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) @@ -56,66 +56,48 @@ struct PathStorageTests { 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) + + @Test(arguments: [ + (CGRect(x: 0, y: 0, width: 100, height: 100), CGRect(x: 0, y: 0, width: 100, height: 100), true), + (CGRect(x: 0, y: 0, width: 100, height: 100), CGRect(x: 0, y: 0, width: 200, height: 200), false), + (CGRect(x: 10, y: 20, width: 50, height: 50), CGRect(x: 10, y: 20, width: 50, height: 50), true), + ]) + func storageEquality(rect1: CGRect, rect2: CGRect, expectedEqual: Bool) { + let path1 = ORBPath(rect: rect1, transform: nil) + let path2 = ORBPath(rect: rect2, 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) + #expect(storage1.isEqual(to: storage2) == expectedEqual) storage1.destroy() storage2.destroy() path1.release() path2.release() } - @Test("Verify no crash or memleak of ORBPathStorageGetCGPath call") - func storageGetCGPath() { + @Test("Verify no crash or memleak of ORBPathStorageGetCGPath call", arguments: 1...20) + func storageGetCGPath(iteration: Int) { let rect = CGRect(x: 0, y: 0, width: 100, height: 100) let path = ORBPath(rect: rect, transform: nil) let storage = path.storage storage.initialize(capacity: 64, source: nil) storage.append(path: path) - for _ in 1 ... 7 { + for _ in 1...7 { storage.append(element: .moveToPoint, points: [50, 50], userInfo: nil) storage.append(element: .addLineToPoint, points: [100, 50], userInfo: nil) storage.append(element: .closeSubpath, points: [], userInfo: nil) } - func test(_ s: ORBPath.Storage) { - if let p = s.cgPath { - _ = p.isEmpty - _ = p.boundingBox - } - } - for i in 1 ... 20 { - test(storage) + if let p = storage.cgPath { + _ = p.isEmpty + _ = p.boundingBox } + storage.destroy() + path.release() } + } #endif diff --git a/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift b/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift index 85f39bc..2718190 100644 --- a/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift +++ b/Tests/OpenRenderBoxCompatibilityTests/PathTests.swift @@ -46,16 +46,62 @@ struct PathTests { path.release() } + @Test(arguments: [ + (CGPoint(x: 50, y: 50), true), + (CGPoint(x: 150, y: 150), false), + (CGPoint(x: 0, y: 0), true), + (CGPoint(x: 100, y: 100), false), + (CGPoint(x: -1, y: 50), false), + ]) + func pathContainsPoint(point: CGPoint, expected: Bool) { + let rect = CGRect(x: 0, y: 0, width: 100, height: 100) + let path = ORBPath(rect: rect, transform: nil) + #expect(path.contains(point: point, eoFill: false) == expected) + path.release() + } + @Test - func pathContainsPoint() { + func pathIsEmpty() { + let emptyPath = ORBPath.null + #expect(emptyPath.isEmpty == true) + 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) + #expect(path.isEmpty == false) path.release() } + + @Test + func pathApplyElements() { + let rect = CGRect(x: 0, y: 0, width: 10, height: 10) + let cgPath = CGPath(rect: rect, transform: nil) + let path = ORBPath(cgPath: cgPath) + var elementCount: Int32 = 0 + path.apply(info: &elementCount) { info, element, points, userInfo in + let countPtr = info.assumingMemoryBound(to: Int32.self) + countPtr.pointee += 1 + return true // continue iteration + } + #expect(elementCount == 5) // move + 4 lines for rect + path.release() + } + + @Test + func pathEqualToPath() { + 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) + #expect(path1.isEqual(to: path2) == true) + path1.release() + path2.release() + + let differentRect = CGRect(x: 0, y: 0, width: 200, height: 200) + let path3 = ORBPath(rect: rect, transform: nil) + let path4 = ORBPath(rect: differentRect, transform: nil) + #expect(path3.isEqual(to: path4) == false) + path3.release() + path4.release() + } } #endif From 3047ceb2572564889c7c1d6aa7d5fcec73fc18f4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:52:02 +0800 Subject: [PATCH 7/9] Update dependency --- Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index 8e57171..1b961f6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "13c32a737bf2c440d45224a3b2dc4bb7017c5038" + "revision" : "db8c67d76c1c95d70389e6aa8d1a26ec6720adda" } }, { From bd95e36bea71f694784e15094c4a75fcab654f7d Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 17:54:50 +0800 Subject: [PATCH 8/9] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 92559ae..7338d71 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ DerivedData/ .claude/ .rb_template/ .augment/ +/.build-debug \ No newline at end of file From 14166e244c2a2eeaea8eaff37c7dc5de0c8c9db4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 27 Dec 2025 21:01:24 +0800 Subject: [PATCH 9/9] Add C23 check for static_assert without message --- .../include/OpenRenderBox/ORBPathCallbacks.h | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h index 16155ce..c2fc4d3 100644 --- a/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h +++ b/Sources/OpenRenderBox/include/OpenRenderBox/ORBPathCallbacks.h @@ -46,18 +46,21 @@ typedef struct ORB_SWIFT_NAME(ORBPath.Callbacks) ORBPathCallbacks { const struct ORBPathCallbacks * _Nullable (* _Nullable next)(const void *object); } ORBPathCallbacks; -static_assert(sizeof(ORBPathCallbacks) == 0x58); -static_assert(offsetof(ORBPathCallbacks, flags) == 0x00); -static_assert(offsetof(ORBPathCallbacks, retain) == 0x08); -static_assert(offsetof(ORBPathCallbacks, release) == 0x10); -static_assert(offsetof(ORBPathCallbacks, apply) == 0x18); -static_assert(offsetof(ORBPathCallbacks, isEqual) == 0x20); -static_assert(offsetof(ORBPathCallbacks, isEmpty) == 0x28); -static_assert(offsetof(ORBPathCallbacks, isSingleElement) == 0x30); -static_assert(offsetof(ORBPathCallbacks, bezierOrder) == 0x38); -static_assert(offsetof(ORBPathCallbacks, boundingRect) == 0x40); -static_assert(offsetof(ORBPathCallbacks, cgPath) == 0x48); -static_assert(offsetof(ORBPathCallbacks, next) == 0x50); +// static_assert without message requires C23 or C++17 +#if defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) +static_assert(sizeof(struct ORBPathCallbacks) == 0x58); +static_assert(offsetof(struct ORBPathCallbacks, flags) == 0x00); +static_assert(offsetof(struct ORBPathCallbacks, retain) == 0x08); +static_assert(offsetof(struct ORBPathCallbacks, release) == 0x10); +static_assert(offsetof(struct ORBPathCallbacks, apply) == 0x18); +static_assert(offsetof(struct ORBPathCallbacks, isEqual) == 0x20); +static_assert(offsetof(struct ORBPathCallbacks, isEmpty) == 0x28); +static_assert(offsetof(struct ORBPathCallbacks, isSingleElement) == 0x30); +static_assert(offsetof(struct ORBPathCallbacks, bezierOrder) == 0x38); +static_assert(offsetof(struct ORBPathCallbacks, boundingRect) == 0x40); +static_assert(offsetof(struct ORBPathCallbacks, cgPath) == 0x48); +static_assert(offsetof(struct ORBPathCallbacks, next) == 0x50); +#endif /// Extended callbacks structure with additional extended callbacks argument typedef struct ORB_SWIFT_NAME(ORBPath.CallbacksExtended) ORBPathCallbacksExtended { @@ -78,18 +81,21 @@ typedef struct ORB_SWIFT_NAME(ORBPath.CallbacksExtended) ORBPathCallbacksExtende const struct ORBPathCallbacksExtended * _Nullable (* _Nullable next)(const void *object, const struct ORBPathCallbacksExtended *extended); } ORBPathCallbacksExtended; -static_assert(sizeof(ORBPathCallbacksExtended) == 0x58); -static_assert(offsetof(ORBPathCallbacksExtended, flags) == 0x00); -static_assert(offsetof(ORBPathCallbacksExtended, retain) == 0x08); -static_assert(offsetof(ORBPathCallbacksExtended, release) == 0x10); -static_assert(offsetof(ORBPathCallbacksExtended, apply) == 0x18); -static_assert(offsetof(ORBPathCallbacksExtended, isEqual) == 0x20); -static_assert(offsetof(ORBPathCallbacksExtended, isEmpty) == 0x28); -static_assert(offsetof(ORBPathCallbacksExtended, isSingleElement) == 0x30); -static_assert(offsetof(ORBPathCallbacksExtended, bezierOrder) == 0x38); -static_assert(offsetof(ORBPathCallbacksExtended, boundingRect) == 0x40); -static_assert(offsetof(ORBPathCallbacksExtended, cgPath) == 0x48); -static_assert(offsetof(ORBPathCallbacksExtended, next) == 0x50); +// static_assert without message requires C23 or C++17 +#if defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) +static_assert(sizeof(struct ORBPathCallbacksExtended) == 0x58); +static_assert(offsetof(struct ORBPathCallbacksExtended, flags) == 0x00); +static_assert(offsetof(struct ORBPathCallbacksExtended, retain) == 0x08); +static_assert(offsetof(struct ORBPathCallbacksExtended, release) == 0x10); +static_assert(offsetof(struct ORBPathCallbacksExtended, apply) == 0x18); +static_assert(offsetof(struct ORBPathCallbacksExtended, isEqual) == 0x20); +static_assert(offsetof(struct ORBPathCallbacksExtended, isEmpty) == 0x28); +static_assert(offsetof(struct ORBPathCallbacksExtended, isSingleElement) == 0x30); +static_assert(offsetof(struct ORBPathCallbacksExtended, bezierOrder) == 0x38); +static_assert(offsetof(struct ORBPathCallbacksExtended, boundingRect) == 0x40); +static_assert(offsetof(struct ORBPathCallbacksExtended, cgPath) == 0x48); +static_assert(offsetof(struct ORBPathCallbacksExtended, next) == 0x50); +#endif #if ORB_TARGET_OS_DARWIN /// Global callbacks for CGPath-backed paths