diff --git a/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.m b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.m index 849295cb..3e6dd63d 100644 --- a/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.m +++ b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.m @@ -9,31 +9,47 @@ #if OPENSWIFTUI_TARGET_OS_DARWIN #include +#include "OpenSwiftUICoreTextGraphicsContextProvider.h" +#include "Shims/UIFoundation/NSTextGraphicsContextInternal.h" + +#if OPENSWIFTUI_TARGET_OS_OSX +#include +#endif static _Thread_local __unsafe_unretained OpenSwiftUICoreGraphicsContext * _current = NULL; +dispatch_once_t _once; +#if OPENSWIFTUI_TARGET_OS_OSX +Class _nsGraphicsContextClass; +#else IMP _pushContextIMP; IMP _popContextIMP; +#endif @interface OpenSwiftUICoreGraphicsContext () { OpenSwiftUICoreGraphicsContext *_next; CGContextRef _ctx; } +#if !OPENSWIFTUI_TARGET_OS_OSX - (id)__createsImages; +#endif @end @implementation OpenSwiftUICoreGraphicsContext - (instancetype)initWithCGContext:(CGContextRef)ctx { - static dispatch_once_t __once; - dispatch_once(&__once, ^{ + dispatch_once(&_once, ^{ + #if OPENSWIFTUI_TARGET_OS_OSX + _nsGraphicsContextClass = NSClassFromString(@"NSGraphicsContext"); + #else Class renderClass = NSClassFromString(@"UIGraphicsRenderer"); if (renderClass) { _pushContextIMP = [renderClass instanceMethodForSelector:@selector(pushContext:)]; _popContextIMP = [renderClass instanceMethodForSelector:@selector(popContext:)]; } else { - // TODO: CoreTextGraphicsContextProvider.sharedProvider + InitializeCoreTextGraphicsContextProvider(); } + #endif }); self = [super init]; if (self) { @@ -43,25 +59,43 @@ - (instancetype)initWithCGContext:(CGContextRef)ctx { } - (void)push { + #if OPENSWIFTUI_TARGET_OS_OSX + _next = _current; + _current = self; + if (_nsGraphicsContextClass) { + [_nsGraphicsContextClass saveGraphicsState]; + [_nsGraphicsContextClass graphicsContextWithCGContext: _ctx flipped: YES]; + NSGraphicsContext *graphicsContext = [_nsGraphicsContextClass graphicsContextWithCGContext: _ctx flipped: YES]; + [_nsGraphicsContextClass setCurrentContext: graphicsContext]; + } + #else _next = _current; _current = self; if (_pushContextIMP != NULL && _popContextIMP != NULL) { typedef BOOL (*FUNC)(id, SEL, OpenSwiftUICoreGraphicsContext *); ((FUNC)(_pushContextIMP))(NULL, @selector(pushContext:), _current); } + #endif } - (void)pop { + #if OPENSWIFTUI_TARGET_OS_OSX + _current = _next; + [_nsGraphicsContextClass restoreGraphicsState]; + #else _current = _next; if (_pushContextIMP != NULL && _popContextIMP != NULL) { typedef BOOL (*FUNC)(id, SEL, OpenSwiftUICoreGraphicsContext *); ((FUNC)(_popContextIMP))(NULL, @selector(popContext:), _current); } + #endif } +#if !OPENSWIFTUI_TARGET_OS_OSX - (id)__createsImages { return nil; } +#endif - (CGContextRef)CGContext { return _ctx; diff --git a/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.h b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.h new file mode 100644 index 00000000..ea4a94cc --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.h @@ -0,0 +1,41 @@ +// +// OpenSwiftUICoreTextGraphicsContextProvider.h +// OpenSwiftUI_SPI +// +// Audited for RELEASE_2024 +// Status: Complete + +#ifndef OpenSwiftUICoreTextGraphicsContextProvider_h +#define OpenSwiftUICoreTextGraphicsContextProvider_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#include "Shims/UIFoundation/NSTextGraphicsContext.h" +#include "Shims/UIFoundation/NSTextGraphicsContextProvider.h" + +void InitializeCoreTextGraphicsContextProvider(void); + +@interface OpenSwiftUICoreTextGraphicsContextProvider : NSObject + +@property (readonly) CGContextRef CGContext; +@property (readonly, getter=isFlipped) BOOL flipped; +@property (readonly, getter=isDrawingToScreen) BOOL drawingToScreen; + ++ (instancetype)sharedProvider; ++ (id)graphicsContextForApplicationFrameworkContext:(id)context; ++ (Class)colorClassForApplicationFrameworkContext:(id)context; + +- (BOOL)isFlipped; +- (BOOL)isDrawingToScreen; +- (CGContextRef)CGContext; +- (void)saveGraphicsState; +- (void)restoreGraphicsState; +- (void)becomeCurrentGraphicsContextDuringBlock:(void (^)(void))block; + +@end + +#endif + +#endif /* OpenSwiftUICoreTextGraphicsContextProvider_h */ diff --git a/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.m b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.m new file mode 100644 index 00000000..a1a7a2e3 --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Overlay/CoreGraphics/OpenSwiftUICoreTextGraphicsContextProvider.m @@ -0,0 +1,71 @@ +// +// OpenSwiftUICoreTextGraphicsContextProvider.m +// OpenSwiftUI_SPI +// +// Audited for RELEASE_2024 +// Status: Complete + +#include "OpenSwiftUICoreTextGraphicsContextProvider.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#include "OpenSwiftUICoreColor.h" +#include "OpenSwiftUICoreGraphicsContext.h" + +void InitializeCoreTextGraphicsContextProvider(void) { + (void)OpenSwiftUICoreTextGraphicsContextProvider.sharedProvider; +} + +@implementation OpenSwiftUICoreTextGraphicsContextProvider + ++ (instancetype)sharedProvider { + static OpenSwiftUICoreTextGraphicsContextProvider *sharedPvdr = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + sharedPvdr = [[OpenSwiftUICoreTextGraphicsContextProvider alloc] init]; + [NSTextGraphicsContextProvider setTextGraphicsContextProviderClass:OpenSwiftUICoreTextGraphicsContextProvider.class]; + }); + return sharedPvdr; +} + ++ (id)graphicsContextForApplicationFrameworkContext:(id)context { + return OpenSwiftUICoreTextGraphicsContextProvider.sharedProvider; +} + ++ (Class)colorClassForApplicationFrameworkContext:(id)context { + return OpenSwiftUICoreColor.class; +} + +- (BOOL)isFlipped { + return YES; +} + +- (BOOL)isDrawingToScreen { + return YES; +} + +- (CGContextRef)CGContext { + return OpenSwiftUICoreGraphicsContext.current.CGContext; +} + +- (void)saveGraphicsState { + CGContextRef context = OpenSwiftUICoreGraphicsContext.current.CGContext; + if (context) { + CGContextSaveGState(context); + } +} + +- (void)restoreGraphicsState { + CGContextRef context = OpenSwiftUICoreGraphicsContext.current.CGContext; + if (context) { + CGContextRestoreGState(context); + } +} + +- (void)becomeCurrentGraphicsContextDuringBlock:(void (^)(void))block { + block(); +} + +@end + +#endif diff --git a/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContext.h b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContext.h new file mode 100644 index 00000000..519a489c --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContext.h @@ -0,0 +1,33 @@ +// +// NSTextGraphicsContext.h +// OpenSwiftUI_SPI +// +// Audited for RELEASE_2024 +// Status: Complete + +#ifndef NSTextGraphicsContext_h +#define NSTextGraphicsContext_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#include +#include + +@protocol NSTextGraphicsContext + +@required +@property (readonly) CGContextRef CGContext; +@property (readonly, getter=isFlipped) BOOL flipped; +@property (readonly, getter=isDrawingToScreen) BOOL drawingToScreen; ++ (id)graphicsContextForApplicationFrameworkContext:(id)context NS_SWIFT_NAME(graphicsContext(forApplicationFrameworkContext:)); + +- (CGContextRef)CGContext; +- (BOOL)isFlipped; +- (BOOL)isDrawingToScreen; +@end + +#endif + +#endif /* NSTextGraphicsContext_h */ diff --git a/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextInternal.h b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextInternal.h new file mode 100644 index 00000000..59881b0c --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextInternal.h @@ -0,0 +1,29 @@ +// +// NSTextGraphicsContextInternal.h +// OpenSwiftUI_SPI +// +// Audited for RELEASE_2024 +// Status: Complete + +#ifndef NSTextGraphicsContextInternal_h +#define NSTextGraphicsContextInternal_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#include "NSTextGraphicsContext.h" + +@protocol NSTextGraphicsContextInternal +@required +-(void)saveGraphicsState; +-(void)restoreGraphicsState; +@optional +- (void)becomeCurrentGraphicsContextDuringBlock:(void (^)(void))block; +@end + +#endif + +#endif /* NSTextGraphicsContextInternal_h */ + + diff --git a/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextProvider.h b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextProvider.h new file mode 100644 index 00000000..42bdb50c --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Shims/UIFoundation/NSTextGraphicsContextProvider.h @@ -0,0 +1,37 @@ +// +// NSTextGraphicsContextProvider.h +// OpenSwiftUI_SPI +// +// Audited for RELEASE_2024 +// Status: Complete + +#ifndef NSTextGraphicsContextProvider_h +#define NSTextGraphicsContextProvider_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +#include "NSTextGraphicsContext.h" + +@protocol NSTextGraphicsContextProvider + +@required ++ (id)graphicsContextForApplicationFrameworkContext:(id)context; +@optional ++ (Class)colorClassForApplicationFrameworkContext:(id)context; // FIXME +@end + +@interface NSTextGraphicsContextProvider : NSObject ++ (Class)textGraphicsContextProviderClass; ++ (void)setTextGraphicsContextProviderClass:(Class)cls; ++ (BOOL)textGraphicsContextProviderClassRespondsToColorQuery; ++ (Class)__defaultColorClass; ++ (Class)textGraphicsContextClass; ++ (void)setTextGraphicsContextClass:(Class)cls; ++ (void)setCurrentTextGraphicsContext:(id)context duringBlock:(void (^)(void))block; +@end + +#endif + +#endif /* NSTextGraphicsContextProvider_h */ diff --git a/Tests/OpenSwiftUI_SPITests/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.swift b/Tests/OpenSwiftUI_SPITests/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.swift index 3de9d48f..ea47bc5c 100644 --- a/Tests/OpenSwiftUI_SPITests/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.swift +++ b/Tests/OpenSwiftUI_SPITests/Overlay/CoreGraphics/OpenSwiftUICoreGraphicsContext.swift @@ -1,14 +1,42 @@ // -// OpenSwiftUICoreGraphicsContext.swift +// OpenSwiftUICoreGraphicsContextTests.swift // OpenSwiftUI_SPITests import OpenSwiftUI_SPI import Testing #if canImport(Darwin) +import CoreGraphics +import Foundation -struct OpenSwiftUICoreGraphicsContext { +@MainActor +struct OpenSwiftUICoreGraphicsContextTests { + @Test + func example() async throws { + let width = 200 + let height = 200 + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue + guard let cgContext = CGContext( + data: nil, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: 4 * width, + space: colorSpace, + bitmapInfo: bitmapInfo + ) else { + fatalError("Could not create CGContext") + } + + let context = OpenSwiftUICoreGraphicsContext(cgContext: cgContext) + #expect(OpenSwiftUICoreGraphicsContext.current == nil) + context.push() + #expect(OpenSwiftUICoreGraphicsContext.current != nil) + context.pop() + #expect(OpenSwiftUICoreGraphicsContext.current == nil) + } } #endif