From d35878baccf717b777a8745825b53f79238835d0 Mon Sep 17 00:00:00 2001 From: Joel Arvidsson Date: Wed, 23 Feb 2022 16:28:31 +0100 Subject: [PATCH] Support PlatformColor (#1561) Since version 0.63 React Native has supported PlatformColor structs that among other features has dark mode support built in. Using those values with RNSVG today throws a warning "[Object object]" is not a valid color or brush and makes the path have a clear color. This PR adds support for PlatformColor by utilizing the color parsing code shipped with React Native itself. In order to support the dynamic properties I had to retain the reference as a UIColor and convert it to CGColorRef as it's read. This might have a performance penalty. The Android implementation doesn't yet have support for dynamic values (EDIT: react-native itself doesn't support it, so we're good there I think). Since it relies on the ColorPropConverter class, I think it means minimum supported version of RN will be raised to 0.63. --- .../java/com/horcrux/svg/RenderableView.java | 8 +++++++- apple/Brushes/RNSVGSolidColorBrush.m | 19 ++++++++++--------- apple/Utils/RCTConvert+RNSVG.h | 2 +- apple/Utils/RCTConvert+RNSVG.m | 6 +++--- src/index.d.ts | 4 ++-- src/lib/extract/extractBrush.ts | 10 ++++++++++ 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index 0c0cc595..c0cd440e 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -27,6 +27,7 @@ import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.ColorPropConverter; import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.annotations.ReactProp; @@ -474,7 +475,12 @@ abstract public class RenderableView extends VirtualView { switch (colorType) { case 0: if (colors.size() == 2) { - int color = colors.getInt(1); + int color; + if (colors.getType(1) == ReadableType.Map) { + color = ColorPropConverter.getColor(colors.getMap(1), getContext()); + } else { + color = colors.getInt(1); + } int alpha = color >>> 24; int combined = Math.round((float)alpha * opacity); paint.setColor(combined << 24 | (color & 0x00ffffff)); diff --git a/apple/Brushes/RNSVGSolidColorBrush.m b/apple/Brushes/RNSVGSolidColorBrush.m index 82eda8a0..f02cf4cc 100644 --- a/apple/Brushes/RNSVGSolidColorBrush.m +++ b/apple/Brushes/RNSVGSolidColorBrush.m @@ -13,13 +13,13 @@ @implementation RNSVGSolidColorBrush { - CGColorRef _color; + UIColor *_color; } - (instancetype)initWithArray:(NSArray *)array { if ((self = [super initWithArray:array])) { - _color = CGColorRetain([RCTConvert RNSVGCGColor:array offset:1]); + _color = [RCTConvert RNSVGUIColor:array offset:1]; } return self; } @@ -27,34 +27,35 @@ - (instancetype)initWithNumber:(NSNumber *)number { if ((self = [super init])) { - _color = CGColorRetain([RCTConvert CGColor:number]); + _color = [RCTConvert UIColor:number]; } return self; } - (void)dealloc { - CGColorRelease(_color); + _color = nil; } - (CGColorRef)getColorWithOpacity:(CGFloat)opacity { - return CGColorCreateCopyWithAlpha(_color, opacity * CGColorGetAlpha(_color)); + CGColorRef baseColor = _color.CGColor; + CGColorRef color = CGColorCreateCopyWithAlpha(baseColor, opacity * CGColorGetAlpha(baseColor)); + CGColorRelease(baseColor); + return color; } - (BOOL)applyFillColor:(CGContextRef)context opacity:(CGFloat)opacity { - CGColorRef color = CGColorCreateCopyWithAlpha(_color, opacity * CGColorGetAlpha(_color)); + CGColorRef color = [self getColorWithOpacity:opacity]; CGContextSetFillColorWithColor(context, color); - CGColorRelease(color); return YES; } - (BOOL)applyStrokeColor:(CGContextRef)context opacity:(CGFloat)opacity { - CGColorRef color = CGColorCreateCopyWithAlpha(_color, opacity * CGColorGetAlpha(_color)); + CGColorRef color = [self getColorWithOpacity:opacity]; CGContextSetStrokeColorWithColor(context, color); - CGColorRelease(color); return YES; } diff --git a/apple/Utils/RCTConvert+RNSVG.h b/apple/Utils/RCTConvert+RNSVG.h index 4388e0ca..10b9bbc9 100644 --- a/apple/Utils/RCTConvert+RNSVG.h +++ b/apple/Utils/RCTConvert+RNSVG.h @@ -28,7 +28,7 @@ + (RNSVGBrush *)RNSVGBrush:(id)json; + (RNSVGPathParser *)RNSVGCGPath:(NSString *)d; + (CGRect)RNSVGCGRect:(id)json offset:(NSUInteger)offset; -+ (CGColorRef)RNSVGCGColor:(id)json offset:(NSUInteger)offset; ++ (UIColor *)RNSVGUIColor:(id)json offset:(NSUInteger)offset; + (CGGradientRef)RNSVGCGGradient:(id)json; @end diff --git a/apple/Utils/RCTConvert+RNSVG.m b/apple/Utils/RCTConvert+RNSVG.m index 94ef7eb8..dc344a07 100644 --- a/apple/Utils/RCTConvert+RNSVG.m +++ b/apple/Utils/RCTConvert+RNSVG.m @@ -130,17 +130,17 @@ RCT_ENUM_CONVERTER(RNSVGUnits, (@{ }; } -+ (CGColorRef)RNSVGCGColor:(id)json offset:(NSUInteger)offset ++ (UIColor *)RNSVGUIColor:(id)json offset:(NSUInteger)offset { NSArray *arr = [self NSArray:json]; if (arr.count == offset + 1) { - return [self CGColor:[arr objectAtIndex:offset]]; + return [self UIColor:[arr objectAtIndex:offset]]; } if (arr.count < offset + 4) { RCTLogError(@"Too few elements in array (expected at least %zd): %@", (ssize_t)(4 + offset), arr); return nil; } - return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]]; + return [self UIColor:[arr subarrayWithRange:(NSRange){offset, 4}]]; } + (CGGradientRef)RNSVGCGGradient:(id)json diff --git a/src/index.d.ts b/src/index.d.ts index 7f4d1521..d37fed53 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactNative from 'react-native'; -import { GestureResponderEvent, TransformsStyle } from 'react-native'; +import { GestureResponderEvent, TransformsStyle, OpaqueColorValue } from 'react-native'; // Common props export type NumberProp = string | number; @@ -101,7 +101,7 @@ export type rgbaArray = ReadonlyArray; // int32ARGBColor = 0xaarrggbb export type int32ARGBColor = number; -export type Color = int32ARGBColor | rgbaArray | string; +export type Color = int32ARGBColor | rgbaArray | OpaqueColorValue | string; export interface FillProps { fill?: Color; diff --git a/src/lib/extract/extractBrush.ts b/src/lib/extract/extractBrush.ts index 1bb7e3a3..bdccbf79 100644 --- a/src/lib/extract/extractBrush.ts +++ b/src/lib/extract/extractBrush.ts @@ -40,6 +40,16 @@ export default function extractBrush(color?: Color) { return int32ARGBColor; } + // iOS PlatformColor + if ('semantic' in color) { + return [0, color]; + } + + // Android PlatformColor + if ('resource_paths' in color) { + return [0, color]; + } + console.warn(`"${color}" is not a valid color or brush`); return null; }