From f9a72381b71141290d671be9aeaccfab29b50842 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 3 Oct 2019 14:15:04 +0300 Subject: [PATCH] feat: implement support for context-fill and context-stroke color --- .../java/com/horcrux/svg/RenderableView.java | 16 ++++ ios/Brushes/RNSVGContextBrush.h | 16 ++++ ios/Brushes/RNSVGContextBrush.m | 85 +++++++++++++++++++ ios/RNSVG.xcodeproj/project.pbxproj | 8 ++ ios/RNSVGRenderable.h | 1 + ios/RNSVGRenderable.m | 6 ++ ios/Utils/RCTConvert+RNSVG.m | 5 ++ src/lib/extract/extractBrush.ts | 10 +++ 8 files changed, 147 insertions(+) create mode 100644 ios/Brushes/RNSVGContextBrush.h create mode 100644 ios/Brushes/RNSVGContextBrush.m diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index 6d63f912a..27baf5687 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -42,6 +42,7 @@ abstract public class RenderableView extends VirtualView { super(reactContext); } + static RenderableView contextElement; // strokeLinecap private static final int CAP_BUTT = 0; static final int CAP_ROUND = 1; @@ -378,6 +379,7 @@ void renderMarkers(Canvas canvas, Paint paint, float opacity) { MarkerView markerMid = (MarkerView)getSvgView().getDefinedMarker(mMarkerMid); MarkerView markerEnd = (MarkerView)getSvgView().getDefinedMarker(mMarkerEnd); if (elements != null && (markerStart != null || markerMid != null || markerEnd != null)) { + contextElement = this; ArrayList positions = RNSVGMarkerPosition.fromPath(elements); float width = (float)(this.strokeWidth != null ? relativeOnOther(this.strokeWidth) : 1); for (RNSVGMarkerPosition position : positions) { @@ -399,8 +401,10 @@ void renderMarkers(Canvas canvas, Paint paint, float opacity) { break; } } + contextElement = null; } } + /** * Sets up paint according to the props set on a view. Returns {@code true} * if the fill should be drawn, {@code false} if not. @@ -477,6 +481,18 @@ private void setupPaint(Paint paint, float opacity, ReadableArray colors) { paint.setColor(brush); break; } + case 3: { + if (contextElement != null && contextElement.fill != null) { + setupPaint(paint, opacity, contextElement.fill); + } + break; + } + case 4: { + if (contextElement != null && contextElement.stroke != null) { + setupPaint(paint, opacity, contextElement.stroke); + } + break; + } } } diff --git a/ios/Brushes/RNSVGContextBrush.h b/ios/Brushes/RNSVGContextBrush.h new file mode 100644 index 000000000..269f46c32 --- /dev/null +++ b/ios/Brushes/RNSVGContextBrush.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, react-native-community. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RNSVGBrush.h" + +@interface RNSVGContextBrush : RNSVGBrush + +- (instancetype)initFill; +- (instancetype)initStroke; + +@end diff --git a/ios/Brushes/RNSVGContextBrush.m b/ios/Brushes/RNSVGContextBrush.m new file mode 100644 index 000000000..43d7c2afd --- /dev/null +++ b/ios/Brushes/RNSVGContextBrush.m @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015-present, react-native-community. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RNSVGContextBrush.h" +#import "RNSVGRenderable.h" +#import "RNSVGNode.h" + +#import "RCTConvert+RNSVG.h" +#import + +@implementation RNSVGContextBrush +{ + BOOL _isStroke; +} + +- (instancetype)initFill +{ + if ((self = [super initWithArray:nil])) { + _isStroke = NO; + } + return self; +} + +- (instancetype)initStroke +{ + if ((self = [super initWithArray:nil])) { + _isStroke = YES; + } + return self; +} + +- (void)dealloc +{ +} + +- (BOOL)applyFillColor:(CGContextRef)context opacity:(CGFloat)opacity +{ + RNSVGRenderable *element = RNSVGRenderable.contextElement; + if (!element) { + return NO; + } + + RNSVGBrush *brush = _isStroke ? element.stroke : element.fill; + + BOOL fillColor; + + if (brush.class == RNSVGBrush.class) { + CGContextSetFillColorWithColor(context, [element.tintColor CGColor]); + fillColor = YES; + } else { + fillColor = [brush applyFillColor:context opacity:opacity]; + } + + return fillColor; +} + + + +- (BOOL)applyStrokeColor:(CGContextRef)context opacity:(CGFloat)opacity +{ + RNSVGRenderable *element = RNSVGRenderable.contextElement; + if (!element) { + return NO; + } + + RNSVGBrush *brush = _isStroke ? element.stroke : element.fill; + + BOOL strokeColor; + + if (brush.class == RNSVGBrush.class) { + CGContextSetStrokeColorWithColor(context, [element.tintColor CGColor]); + strokeColor = YES; + } else { + strokeColor = [brush applyStrokeColor:context opacity:opacity]; + } + + return YES; +} + +@end diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 9cc81e6e9..d4e716a63 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -65,6 +65,8 @@ 947F380C214810DC00677F2A /* RNSVGMask.m in Sources */ = {isa = PBXBuildFile; fileRef = 947F380A214810DC00677F2A /* RNSVGMask.m */; }; 947F380F2148119A00677F2A /* RNSVGMaskManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 947F380E2148119A00677F2A /* RNSVGMaskManager.m */; }; 947F38102148119A00677F2A /* RNSVGMaskManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 947F380E2148119A00677F2A /* RNSVGMaskManager.m */; }; + 9482DEFA23460EC800FC486E /* RNSVGContextBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 9482DEF823460EC700FC486E /* RNSVGContextBrush.m */; }; + 9482DEFB23460EC800FC486E /* RNSVGContextBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 9482DEF823460EC700FC486E /* RNSVGContextBrush.m */; }; 9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */; }; 9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D91F473BCB00D5BCFD /* CoreText.framework */; }; 9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */; }; @@ -257,6 +259,8 @@ 947F380A214810DC00677F2A /* RNSVGMask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGMask.m; path = Elements/RNSVGMask.m; sourceTree = ""; }; 947F380D2148118300677F2A /* RNSVGMaskManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGMaskManager.h; sourceTree = ""; }; 947F380E2148119A00677F2A /* RNSVGMaskManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGMaskManager.m; sourceTree = ""; }; + 9482DEF823460EC700FC486E /* RNSVGContextBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGContextBrush.m; sourceTree = ""; }; + 9482DEF923460EC800FC486E /* RNSVGContextBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGContextBrush.h; sourceTree = ""; }; 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 9494C4D91F473BCB00D5BCFD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -340,6 +344,8 @@ 0CF68AEA1AF0549300FF9E5C /* Brushes */ = { isa = PBXGroup; children = ( + 9482DEF923460EC800FC486E /* RNSVGContextBrush.h */, + 9482DEF823460EC700FC486E /* RNSVGContextBrush.m */, 10FDEEB31D3FBED400A5C46C /* RNSVGBrushType.h */, 10FDEEB01D3FB60500A5C46C /* RNSVGPainterBrush.h */, 10FDEEB11D3FB60500A5C46C /* RNSVGPainterBrush.m */, @@ -599,6 +605,7 @@ 10BEC1C21D3F680F00FDCB19 /* RNSVGLinearGradientManager.m in Sources */, 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */, 10BA0D3B1CE74E3100887C2B /* RNSVGRectManager.m in Sources */, + 9482DEFA23460EC800FC486E /* RNSVGContextBrush.m in Sources */, 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */, 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */, @@ -664,6 +671,7 @@ A361E77A1EB0C33D00646005 /* RNSVGText.m in Sources */, A361E77B1EB0C33D00646005 /* RNSVGRectManager.m in Sources */, A361E77C1EB0C33D00646005 /* RNSVGRenderable.m in Sources */, + 9482DEFB23460EC800FC486E /* RNSVGContextBrush.m in Sources */, A361E77D1EB0C33D00646005 /* RNSVGGroup.m in Sources */, A361E77E1EB0C33D00646005 /* RNSVGClipPathManager.m in Sources */, A361E77F1EB0C33D00646005 /* RNSVGPainter.m in Sources */, diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 5a4dd5391..0ef7f8cea 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -16,6 +16,7 @@ @interface RNSVGRenderable : RNSVGNode +@property (class) RNSVGRenderable *contextElement; @property (nonatomic, strong) RNSVGBrush *fill; @property (nonatomic, assign) CGFloat fillOpacity; @property (nonatomic, assign) RNSVGCGFCRule fillRule; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 5d5b85a05..914170820 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -26,6 +26,10 @@ @implementation RNSVGRenderable CGPathRef _hitArea; } +static RNSVGRenderable * _contextElement; ++ (RNSVGRenderable *)contextElement { return _contextElement; } ++ (void)setContextElement:(RNSVGRenderable *)contextElement { _contextElement = contextElement; } + - (id)init { if (self = [super init]) { @@ -304,6 +308,7 @@ - (void)renderMarkers:(CGContextRef)context path:(CGPathRef)path rect:(const CGR RNSVGMarker *markerMid = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerMid]; RNSVGMarker *markerEnd = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerEnd]; if (markerStart || markerMid || markerEnd) { + _contextElement = self; NSArray* positions = [RNSVGMarkerPosition fromCGPath:path]; CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1; for (RNSVGMarkerPosition* position in positions) { @@ -325,6 +330,7 @@ - (void)renderMarkers:(CGContextRef)context path:(CGPathRef)path rect:(const CGR break; } } + _contextElement = nil; } } diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index 912b41a05..a94d8fccb 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -10,6 +10,7 @@ #import "RNSVGPainterBrush.h" #import "RNSVGSolidColorBrush.h" +#import "RNSVGContextBrush.h" #import #import @@ -65,6 +66,10 @@ + (RNSVGBrush *)RNSVGBrush:(id)json return [[RNSVGPainterBrush alloc] initWithArray:arr]; case 2: // currentColor return [[RNSVGBrush alloc] initWithArray:nil]; + case 3: // context-fill + return [[RNSVGContextBrush alloc] initFill]; + case 4: // context-stroke + return [[RNSVGContextBrush alloc] initStroke]; default: RCTLogError(@"Unknown brush type: %zd", (unsigned long)type); return nil; diff --git a/src/lib/extract/extractBrush.ts b/src/lib/extract/extractBrush.ts index 4868c1888..3903107da 100644 --- a/src/lib/extract/extractBrush.ts +++ b/src/lib/extract/extractBrush.ts @@ -4,6 +4,8 @@ import { Color } from './types'; const urlIdPattern = /^url\(#(.+)\)$/; const currentColorBrush = [2]; +const contextFillBrush = [3]; +const contextStrokeBrush = [4]; export default function extractBrush(color?: Color) { if (typeof color === 'number') { @@ -20,6 +22,14 @@ export default function extractBrush(color?: Color) { return currentColorBrush; } + if (color === 'context-fill') { + return contextFillBrush; + } + + if (color === 'context-stroke') { + return contextStrokeBrush; + } + const brush = typeof color === 'string' && color.match(urlIdPattern); if (brush) { return [1, brush[1]];