Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
# Summary

On `react-native-macos` 0.76, `UIGraphicsBeginImageContextWithOptions`
and some other UIGraphics directives were removed so the temporary
solution is to copy the removed functions here.

More details
here #2528
and here microsoft/react-native-macos#2209

Closes #2528

## Test Plan

Built the `fabric-macos-example` for with `[email protected]`

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| MacOS   |    ✅      |

## Checklist

- [x] I have tested this on a device and a simulator
  • Loading branch information
jakex7 authored Nov 13, 2024
1 parent d0530e4 commit 1256d56
Show file tree
Hide file tree
Showing 19 changed files with 2,719 additions and 1,885 deletions.
10 changes: 7 additions & 3 deletions apple/Elements/RNSVGSvgView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
#import "RNSVGFabricConversions.h"
#endif // RCT_NEW_ARCH_ENABLED

#if TARGET_OS_OSX // [macOS
#import "RNSVGUIKit.h"
#endif // macOS]

@implementation RNSVGSvgView {
NSMutableDictionary<NSString *, RNSVGNode *> *_clipPaths;
NSMutableDictionary<NSString *, RNSVGNode *> *_templates;
Expand Down Expand Up @@ -368,7 +372,7 @@ - (NSString *)getDataURLWithBounds:(CGRect)bounds
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size];
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
#else // [macOS
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 1);
RNSVGUIGraphicsBeginImageContextWithOptions(bounds.size, NO, 1);
#endif // macOS]
[self clearChildCache];
[self drawRect:bounds];
Expand All @@ -381,9 +385,9 @@ - (NSString *)getDataURLWithBounds:(CGRect)bounds
NSData *imageData = UIImagePNGRepresentation(image);
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
#else // [macOS
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSData *imageData = UIImagePNGRepresentation(RNSVGUIGraphicsGetImageFromCurrentImageContext());
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
UIGraphicsEndImageContext();
RNSVGUIGraphicsEndImageContext();
#endif // macOS]
return base64;
}
Expand Down
8 changes: 6 additions & 2 deletions apple/Filters/RNSVGFilter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#import "RNSVGFabricConversions.h"
#endif // RCT_NEW_ARCH_ENABLED

#if TARGET_OS_OSX // [macOS
#import "RNSVGUIKit.h"
#endif // macOS]

@implementation RNSVGFilter {
NSMutableDictionary<NSString *, CIImage *> *resultsMap;
}
Expand Down Expand Up @@ -142,7 +146,7 @@ - (CIImage *)applyFilter:(CIImage *)img

- (CGContext *)openContext:(CGSize)size
{
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
RNSVGUIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
CGContextRef cropContext = UIGraphicsGetCurrentContext();
#if TARGET_OS_OSX
CGFloat scale = [RNSVGRenderUtils getScreenScale];
Expand All @@ -156,7 +160,7 @@ - (CGContext *)openContext:(CGSize)size

- (void)endContext:(CGContext *)context
{
UIGraphicsEndImageContext();
RNSVGUIGraphicsEndImageContext();
}

- (CIImage *)getMaskFromRect:(CGContext *)context rect:(CGRect)rect ctm:(CGAffineTransform)ctm
Expand Down
4 changes: 2 additions & 2 deletions apple/RNSVGRenderable.mm
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
[blendedImage drawInRect:scaledRect];
#else // [macOS
// Blend current element and mask
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
RNSVGUIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
CGContextRef newContext = UIGraphicsGetCurrentContext();

CGContextSetBlendMode(newContext, kCGBlendModeCopy);
Expand All @@ -415,7 +415,7 @@ - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
CGContextDrawImage(newContext, rect, contentImage);

CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
RNSVGUIGraphicsEndImageContext();

// Invert the CTM and apply transformations to draw image in 1:1
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
Expand Down
3 changes: 2 additions & 1 deletion apple/RNSVGRenderableModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ @implementation RNSVGRenderableModule
bounds = CGRectIntersection(bounds, clipBounds);
}
}
if (CGRectIsNull(bounds)) bounds = CGRectZero;
if (CGRectIsNull(bounds))
bounds = CGRectZero;
CGPoint origin = bounds.origin;
CGSize size = bounds.size;
return @{@"x" : @(origin.x), @"y" : @(origin.y), @"width" : @(size.width), @"height" : @(size.height)};
Expand Down
11 changes: 11 additions & 0 deletions apple/RNSVGUIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#define RNSVGView UIView
#endif // RCT_NEW_ARCH_ENABLED

#define RNSVGUIGraphicsBeginImageContextWithOptions UIGraphicsBeginImageContextWithOptions
#define RNSVGUIGraphicsEndImageContext UIGraphicsEndImageContext
#define RNSVGUIGraphicsGetImageFromCurrentImageContext UIGraphicsGetImageFromCurrentImageContext

#else // TARGET_OS_OSX [

// Due to name mangling, calling c-style functions from .mm files will fail, therefore we need to wrap them with extern
Expand Down Expand Up @@ -67,4 +71,11 @@ extern "C" {
@property (readonly) CGPoint CGPointValue;
@end

// These functions are copied from react-native-macos to enable compatibility with [email protected]+
// https://github.com/microsoft/react-native-macos/blob/7361b165ef633d3d95dbdb69da58ff6119f07369/packages/react-native/React/Base/macOS/RCTUIKit.m
// See https://github.com/software-mansion/react-native-svg/issues/2528
void RNSVGUIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, CGFloat scale);
void RNSVGUIGraphicsEndImageContext(void);
NSImage *RNSVGUIGraphicsGetImageFromCurrentImageContext(void);

#endif // ] TARGET_OS_OSX
68 changes: 68 additions & 0 deletions apple/RNSVGUIKit.macos.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "RNSVGUIKit.h"
#import <objc/runtime.h>

@implementation RNSVGView {
}
Expand Down Expand Up @@ -57,3 +58,70 @@ - (CGPoint)CGPointValue
}

@end

static char RCTGraphicsContextSizeKey;

void RNSVGUIGraphicsBeginImageContextWithOptions(CGSize size, __unused BOOL opaque, CGFloat scale)
{
if (scale == 0.0) {
// TODO: Assert. We can't assume a display scale on macOS
scale = 1.0;
}

size_t width = ceilf(size.width * scale);
size_t height = ceilf(size.height * scale);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(
NULL,
width,
height,
8 /*bitsPerComponent*/,
width * 4 /*bytesPerRow*/,
colorSpace,
kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);

if (ctx != NULL) {
// flip the context (top left at 0, 0) and scale it
CGContextTranslateCTM(ctx, 0.0, height);
CGContextScaleCTM(ctx, scale, -scale);

NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
objc_setAssociatedObject(
graphicsContext, &RCTGraphicsContextSizeKey, [NSValue valueWithSize:size], OBJC_ASSOCIATION_COPY_NONATOMIC);

[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:graphicsContext];

CFRelease(ctx);
}
}

void RNSVGUIGraphicsEndImageContext(void)
{
RCTAssert(
objc_getAssociatedObject([NSGraphicsContext currentContext], &RCTGraphicsContextSizeKey),
@"The current graphics context is not a React image context!");
[NSGraphicsContext restoreGraphicsState];
}

NSImage *RNSVGUIGraphicsGetImageFromCurrentImageContext(void)
{
NSImage *image = nil;
NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];

NSValue *sizeValue = objc_getAssociatedObject(graphicsContext, &RCTGraphicsContextSizeKey);
if (sizeValue != nil) {
CGImageRef cgImage = CGBitmapContextCreateImage([graphicsContext CGContext]);

if (cgImage != NULL) {
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
image = [[NSImage alloc] initWithSize:[sizeValue sizeValue]];
[image addRepresentation:imageRep];
CFRelease(cgImage);
}
}

return image;
}
6 changes: 3 additions & 3 deletions apple/Utils/RNSVGRenderUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ + (CGImage *)renderToImage:(RNSVGRenderable *)renderable
{
CGFloat scale = [self getScreenScale];
#if TARGET_OS_OSX // [macOS
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
RNSVGUIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
#else // macOS]
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
RNSVGUIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
#endif // [macOS]
CGContextRef cgContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(cgContext, CGAffineTransformInvert(CGContextGetCTM(cgContext)));
Expand All @@ -53,7 +53,7 @@ + (CGImage *)renderToImage:(RNSVGRenderable *)renderable
}
[renderable renderLayerTo:cgContext rect:rect];
CGImageRef contentImage = CGBitmapContextCreateImage(cgContext);
UIGraphicsEndImageContext();
RNSVGUIGraphicsEndImageContext();
return contentImage;
}

Expand Down
12 changes: 10 additions & 2 deletions apps/fabric-macos-example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local
**/.xcode.env.local

# Android/IntelliJ
#
Expand Down Expand Up @@ -56,11 +56,19 @@ yarn-error.log
*.jsbundle

# Ruby / CocoaPods
/ios/Pods/
**/Pods/
/vendor/bundle/

# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
8 changes: 4 additions & 4 deletions apps/fabric-macos-example/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"

# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
# bound in the template on Cocoapods with next React Native release.
gem 'cocoapods', '>= 1.13', '< 1.15'
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
5 changes: 3 additions & 2 deletions apps/fabric-macos-example/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ PLATFORMS
ruby

DEPENDENCIES
activesupport (>= 6.1.7.5, < 7.1.0)
cocoapods (>= 1.13, < 1.15)
activesupport (>= 6.1.7.5, != 7.1.0)
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
xcodeproj (< 1.26.0)

RUBY VERSION
ruby 2.7.6p219
Expand Down
2 changes: 1 addition & 1 deletion apps/fabric-macos-example/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'module-resolver',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
Expand Down
Loading

0 comments on commit 1256d56

Please sign in to comment.