diff --git a/Example/examples.js b/Example/examples.js index bf0f1a5b..4b519b11 100644 --- a/Example/examples.js +++ b/Example/examples.js @@ -29,8 +29,8 @@ export { Text, Stroking, G, - //Use, - //Symbol, + Use, + Symbol, Gradients, Clipping, Image, diff --git a/Example/examples/G.js b/Example/examples/G.js index 1d118ffb..655bd6e4 100644 --- a/Example/examples/G.js +++ b/Example/examples/G.js @@ -68,6 +68,7 @@ class GTransform extends Component{ Text grouped with shapes - + ; } } @@ -106,30 +107,35 @@ const icon = ; diff --git a/Example/examples/Stroking.js b/Example/examples/Stroking.js index 453a0a23..a7772196 100644 --- a/Example/examples/Stroking.js +++ b/Example/examples/Stroking.js @@ -19,7 +19,7 @@ class StrokeExample extends Component{ static title = 'The stroke property defines the color of a line, text or outline of an element'; render() { return - + @@ -32,10 +32,12 @@ class StrokeLinecap extends Component{ static title = 'The strokeLinecap property defines different types of endings to an open path'; render() { return - - - - + + + + + + ; } @@ -142,7 +144,6 @@ const icon = ; const samples = [StrokeExample, StrokeLinecap, StrokeDasharray, StrokeDashoffset, StrokePattern]; - export { icon, samples diff --git a/Example/examples/Symbol.js b/Example/examples/Symbol.js index 3c628f83..0af324af 100644 --- a/Example/examples/Symbol.js +++ b/Example/examples/Symbol.js @@ -21,19 +21,19 @@ class SymbolExample extends Component{ - We go up, then we go down, then up again + a 50 50 0 1 1 20 110 + `; + + return + + We go up, then we go down, then up again + + ; } } diff --git a/Example/examples/Use.js b/Example/examples/Use.js index 3b1208f1..64ac50ae 100644 --- a/Example/examples/Use.js +++ b/Example/examples/Use.js @@ -27,8 +27,8 @@ class UseExample extends Component{ - - + + ; } } @@ -43,9 +43,9 @@ class UseShapes extends Component{ - - - + + + ; } } @@ -60,7 +60,7 @@ const icon = - + ; const samples = [UseExample, UseShapes]; diff --git a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java index 9f5f98d7..a08748d3 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java @@ -49,7 +49,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { protected @Nullable Path mClipPath; protected @Nullable String mClipPathRef; - private static final int PATH_TYPE_ARC = 4; private static final int PATH_TYPE_CLOSE = 1; private static final int PATH_TYPE_CURVETO = 3; private static final int PATH_TYPE_LINETO = 2; @@ -233,30 +232,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { data[i++] * mScale, data[i++] * mScale); break; - case PATH_TYPE_ARC: - { - float x = data[i++] * mScale; - float y = data[i++] * mScale; - float r = data[i++] * mScale; - float start = (float) Math.toDegrees(data[i++]); - float end = (float) Math.toDegrees(data[i++]); - - boolean clockwise = data[i++] == 1f; - float sweep = end - start; - if (Math.abs(sweep) > 360) { - sweep = 360; - } else { - sweep = modulus(sweep, 360); - } - if (!clockwise && sweep < 360) { - start = end; - sweep = 360 - sweep; - } - - RectF oval = new RectF(x - r, y - r, x + r, y + r); - path.addArc(oval, start, sweep); - break; - } default: throw new JSApplicationIllegalArgumentException( "Unrecognized drawing instruction " + type); diff --git a/elements/G.js b/elements/G.js index 79935f3e..8def1065 100644 --- a/elements/G.js +++ b/elements/G.js @@ -4,7 +4,6 @@ import createReactNativeComponentClass from 'react/lib/createReactNativeComponen import {transformProps} from '../lib/props'; import {GroupAttributes} from '../lib/attributes'; import extractProps from '../lib/extract/extractProps'; -import reusableProps from '../lib/reusableProps'; class G extends Component{ static displayName = 'G'; @@ -27,7 +26,6 @@ class G extends Component{ return this.root = ele} - mergeList={reusableProps(extractedProps, props)} > {this.props.children} ; diff --git a/elements/Symbol.js b/elements/Symbol.js index 510eb8fb..70908f3a 100644 --- a/elements/Symbol.js +++ b/elements/Symbol.js @@ -1,7 +1,8 @@ import React, {Component, PropTypes} from 'react'; import ViewBox from './ViewBox'; -import Defs from './Defs'; +import G from './G'; + class SymbolElement extends Component{ static displayName = 'Symbol'; static propType = { @@ -9,10 +10,8 @@ class SymbolElement extends Component{ }; render() { let {props} = this; - return + + return {props.children} - ; + ; } } diff --git a/elements/Use.js b/elements/Use.js index 84c40397..ae5a862b 100644 --- a/elements/Use.js +++ b/elements/Use.js @@ -5,7 +5,6 @@ import Shape from './Shape'; import React from 'react'; import patternReg from '../lib/extract/patternReg'; import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; -import reusableProps from '../lib/reusableProps'; import _ from 'lodash'; class Defs extends Shape { @@ -44,7 +43,6 @@ class Defs extends Shape { return this.root = ele} {...extractedProps} - mergeList={reusableProps(extractedProps, props)} href={href} >{props.children}; } diff --git a/elements/ViewBox.js b/elements/ViewBox.js index ace5300d..018f7f4c 100644 --- a/elements/ViewBox.js +++ b/elements/ViewBox.js @@ -18,6 +18,8 @@ class ViewBox extends Component{ y = viewbox.y; } + + console.log(viewbox); return {(!scaleX || !scaleY) ? null : this.props.children} ; diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 9091519f..12b035d5 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -11,11 +11,9 @@ #import "RNSVGContainer.h" #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" -#import "RNSVGNode.h" +#import "RNSVGRenderable.h" -@interface RNSVGGroup : RNSVGNode - -@property (nonatomic, copy) NSArray *mergeList; +@interface RNSVGGroup : RNSVGRenderable - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index ba1b8e91..fab47103 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -17,7 +17,7 @@ [self clip:context]; for (RNSVGNode *node in self.subviews) { - //[node mergeProperties:self mergeList:self.mergeList]; + [node inheritProperties:self inheritedList:self.propList]; [node renderTo:context]; if (node.responsible && !svg.responsible) { @@ -79,4 +79,11 @@ } } +- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray *)inheritedList; +{ + for (NSString *key in inheritedList) { + [self inheritProperty:parent propName:key]; + } +} + @end diff --git a/ios/Elements/RNSVGPath.m b/ios/Elements/RNSVGPath.m index 3692d18c..3b14b7be 100644 --- a/ios/Elements/RNSVGPath.m +++ b/ios/Elements/RNSVGPath.m @@ -66,6 +66,7 @@ } } } + if (self.stroke) { CGContextSetLineWidth(context, self.strokeWidth); CGContextSetLineCap(context, self.strokeLinecap); diff --git a/ios/Elements/RNSVGUse.h b/ios/Elements/RNSVGUse.h index 91413cf8..54a4578f 100644 --- a/ios/Elements/RNSVGUse.h +++ b/ios/Elements/RNSVGUse.h @@ -15,6 +15,5 @@ @interface RNSVGUse : RNSVGRenderable @property (nonatomic, strong) NSString *href; -@property (nonatomic, copy) NSArray *mergeList; @end diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m index 9efe19c6..df38956a 100644 --- a/ios/Elements/RNSVGUse.m +++ b/ios/Elements/RNSVGUse.m @@ -10,22 +10,24 @@ @implementation RNSVGUse -- (void)setMergeList:(NSArray *)mergeList +- (void)setHref:(NSString *)href { - if (mergeList == _mergeList) { + if (href == _href) { return; } - _mergeList = mergeList; + [self invalidate]; + _href = href; } + - (void)renderLayerTo:(CGContextRef)context { RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href]; if (template) { [self beginTransparencyLayer:context]; [self clip:context]; - [template mergeProperties:self mergeList:self.mergeList]; + [template mergeProperties:self mergeList:self.propList]; [template renderTo:context]; [template resetProperties]; [self endTransparencyLayer:context]; diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index fa74592f..17d0490d 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -15,12 +15,12 @@ 1023B4901D3DF4C40051496D /* RNSVGDefination.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48F1D3DF4C40051496D /* RNSVGDefination.m */; }; 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4921D3DF5060051496D /* RNSVGUse.m */; }; 1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */; }; + 103371321D41C5C90028AF13 /* RNSVGBezierPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 103371311D41C5C90028AF13 /* RNSVGBezierPath.m */; }; 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */; }; 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2861CE71EB7001E90A8 /* RNSVGPath.m */; }; 1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2881CE71EB7001E90A8 /* RNSVGSvgView.m */; }; 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2901CE71EC2001E90A8 /* RNSVGText.m */; }; - 1039D2961CE71EC2001E90A8 /* UIBezierPath-Points.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */; }; 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */; }; 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */; }; 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; }; @@ -79,6 +79,8 @@ 1023B4921D3DF5060051496D /* RNSVGUse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGUse.m; path = Elements/RNSVGUse.m; sourceTree = ""; }; 1023B4941D3DF57D0051496D /* RNSVGUseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGUseManager.h; sourceTree = ""; }; 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGUseManager.m; sourceTree = ""; }; + 103371311D41C5C90028AF13 /* RNSVGBezierPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGBezierPath.m; path = Text/RNSVGBezierPath.m; sourceTree = ""; }; + 103371331D41D3400028AF13 /* RNSVGBezierPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGBezierPath.h; path = Text/RNSVGBezierPath.h; sourceTree = ""; }; 1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGGroup.h; path = Elements/RNSVGGroup.h; sourceTree = ""; }; 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGroup.m; path = Elements/RNSVGGroup.m; sourceTree = ""; }; 1039D2831CE71EB7001E90A8 /* RNSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGImage.h; path = Elements/RNSVGImage.h; sourceTree = ""; }; @@ -90,8 +92,6 @@ 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGText.h; path = Text/RNSVGText.h; sourceTree = ""; }; 1039D2901CE71EC2001E90A8 /* RNSVGText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGText.m; path = Text/RNSVGText.m; sourceTree = ""; }; 1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextFrame.h; path = Text/RNSVGTextFrame.h; sourceTree = ""; }; - 1039D2921CE71EC2001E90A8 /* UIBezierPath-Points.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath-Points.h"; path = "Text/UIBezierPath-Points.h"; sourceTree = ""; }; - 1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath-Points.m"; path = "Text/UIBezierPath-Points.m"; sourceTree = ""; }; 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTConvert+RNSVG.h"; path = "Utils/RCTConvert+RNSVG.h"; sourceTree = ""; }; 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+RNSVG.m"; path = "Utils/RCTConvert+RNSVG.m"; sourceTree = ""; }; 1039D29D1CE72177001E90A8 /* RNSVGCGFCRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGCGFCRule.h; path = Utils/RNSVGCGFCRule.h; sourceTree = ""; }; @@ -262,11 +262,11 @@ 1039D27F1CE71D9B001E90A8 /* Text */ = { isa = PBXGroup; children = ( + 103371331D41D3400028AF13 /* RNSVGBezierPath.h */, + 103371311D41C5C90028AF13 /* RNSVGBezierPath.m */, 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, 1039D2901CE71EC2001E90A8 /* RNSVGText.m */, 1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */, - 1039D2921CE71EC2001E90A8 /* UIBezierPath-Points.h */, - 1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */, ); name = Text; sourceTree = ""; @@ -386,7 +386,7 @@ 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, 0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColorBrush.m in Sources */, 10BA0D3A1CE74E3100887C2B /* RNSVGPathManager.m in Sources */, - 1039D2961CE71EC2001E90A8 /* UIBezierPath-Points.m in Sources */, + 103371321D41C5C90028AF13 /* RNSVGBezierPath.m in Sources */, 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, 10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */, diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 029262f3..b588c167 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -67,15 +67,22 @@ - (void)removeDefination; /** - * Just for template node to merge target node`s properties into owned properties + * just for template node to merge target node`s properties into owned properties */ - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList; /** - * Just for template node to reset all owned properties once after rendered. + * just for template node to reset all owned properties once after rendered. */ - (void)resetProperties; +/** + * inherit properties from parent g element + */ +- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray *)inheritedList; + +- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName; + - (void)beginTransparencyLayer:(CGContextRef)context; - (void)endTransparencyLayer:(CGContextRef)context; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 6f901719..3a184d2f 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -211,6 +211,16 @@ // abstract } +- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName +{ + // abstract +} + +- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray *)inheritedList; +{ + // abstract +} + - (void)dealloc { CGPathRelease(_clipPath); diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 1049b042..77a9de6a 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -27,6 +27,7 @@ @property (nonatomic, assign) RNSVGCGFloatArray strokeDasharray; @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, assign) CGMutablePathRef hitArea; +@property (nonatomic, copy) NSArray *propList; - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 66f8105e..aa7366a2 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -88,6 +88,15 @@ _strokeMiterlimit = strokeMiterlimit; } +- (void)setPropList:(NSArray *)propList +{ + if (propList == _propList) { + return; + } + _propList = propList; + [self invalidate]; +} + - (void)dealloc { CGPathRelease(_hitArea); @@ -131,16 +140,27 @@ - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList { + [self mergeProperties:target mergeList:mergeList inherited:NO]; +} + +- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList inherited:(BOOL)inherited +{ if (mergeList.count == 0) { return; } - originProperties = [[NSMutableDictionary alloc] init]; + if (!inherited) { + originProperties = [[NSMutableDictionary alloc] init]; + changedList = mergeList; + } - changedList = mergeList; for (NSString *key in mergeList) { - [originProperties setValue:[self valueForKey:key] forKey:key]; - [self setValue:[target valueForKey:key] forKey:key]; + if (inherited) { + [self inheritProperty:target propName:key]; + } else { + [originProperties setValue:[self valueForKey:key] forKey:key]; + [self setValue:[target valueForKey:key] forKey:key]; + } } } @@ -155,6 +175,23 @@ changedList = nil; } +- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName +{ + if (![self.propList containsObject:propName]) { + // add prop to propList + NSMutableArray *copy = [self.propList mutableCopy]; + [copy addObject:propName]; + self.propList = [copy copy]; + + [self setValue:[parent valueForKey:propName] forKey:propName]; + } +} + +- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray *)inheritedList +{ + [self mergeProperties:parent mergeList:inheritedList inherited:YES]; +} + - (void)renderLayerTo:(CGContextRef)context { // abstract diff --git a/ios/Text/RNSVGBezierPath.h b/ios/Text/RNSVGBezierPath.h new file mode 100644 index 00000000..bd34196a --- /dev/null +++ b/ios/Text/RNSVGBezierPath.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * 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 + +@interface RNSVGBezierPath : NSObject + +- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves; +- (CGAffineTransform)transformAtDistance:(CGFloat)distance; + +@end diff --git a/ios/Text/RNSVGBezierPath.m b/ios/Text/RNSVGBezierPath.m new file mode 100644 index 00000000..f2a0416d --- /dev/null +++ b/ios/Text/RNSVGBezierPath.m @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * 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. + */ + +/** + * based on + */ + +#import "RNSVGBezierPath.h" +#import +#import + +@implementation RNSVGBezierPath +{ + NSArray *_bezierCurves; + NSUInteger _bezierIndex; + CGFloat _offset; + CGFloat _lastX; + CGFloat _lastDistance; + CGPoint _lastPoint; + CGPoint _P0; + CGPoint _P1; + CGPoint _P2; + CGPoint _P3; +} + +- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves +{ + if (self = [super init]) { + _bezierCurves = bezierCurves; + _bezierIndex = 0; + _offset = 0; + _lastX = 0; + _lastDistance = 0; + } + return self; +} + + +- (instancetype)initWithControlPoints:(CGPoint)P0 P1:(CGPoint)P1 P2:(CGPoint)P2 P3:(CGPoint)P3 +{ + if (self = [super init]) { + _P0 = P0; + _P1 = P1; + _P2 = P2; + _P3 = P3; + } + return self; +} + +static CGFloat Bezier(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) { + return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3; +} + +- (CGPoint)pointForOffset:(CGFloat)t { + CGFloat x = Bezier(t, _P0.x, _P1.x, _P2.x, _P3.x); + CGFloat y = Bezier(t, _P0.y, _P1.y, _P2.y, _P3.y); + return CGPointMake(x, y); +} + +static CGFloat BezierPrime(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) { + return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3; +} + +- (CGFloat)angleForOffset:(CGFloat)t { + CGFloat dx = BezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x); + CGFloat dy = BezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y); + return atan2(dy, dx); +} + +static CGFloat Distance(CGPoint a, CGPoint b) { + CGFloat dx = a.x - b.x; + CGFloat dy = a.y - b.y; + return hypot(dx, dy); +} + +// Simplistic routine to find the offset along Bezier that is +// `distance` away from `point`. `offset` is the offset used to +// generate `point`, and saves us the trouble of recalculating it +// This routine just walks forward until it finds a point at least +// `distance` away. Good optimizations here would reduce the number +// of guesses, but this is tricky since if we go too far out, the +// curve might loop back on leading to incorrect results. Tuning +// kStep is good start. +- (CGFloat)offsetAtDistance:(CGFloat)distance + fromPoint:(CGPoint)point + offset:(CGFloat)offset { + const CGFloat kStep = 0.0005; // 0.0001 - 0.001 work well + CGFloat newDistance = 0; + CGFloat newOffset = offset + kStep; + while (newDistance <= distance && newOffset < 1.0) { + newOffset += kStep; + newDistance = Distance(point, [self pointForOffset:newOffset]); + } + + _lastDistance = newDistance; + return newOffset; +} + +- (void)setControlPoints +{ + NSArray *bezier = [_bezierCurves objectAtIndex:_bezierIndex]; + _bezierIndex++; + if (bezier.count == 1) { + _lastPoint = _P0 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue]; + [self setControlPoints]; + } else if (bezier.count == 3) { + _P1 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue]; + _P2 = [(NSValue *)[bezier objectAtIndex:1] CGPointValue]; + _P3 = [(NSValue *)[bezier objectAtIndex:2] CGPointValue]; + } +} + +- (CGAffineTransform)transformAtDistance:(CGFloat)distance +{ + if (_offset == 0) { + [self setControlPoints]; + } + + CGFloat offset = [self offsetAtDistance:distance - _lastX + fromPoint:_lastPoint + offset:_offset]; + CGPoint glyphPoint = [self pointForOffset:offset]; + CGFloat angle = [self angleForOffset:offset]; + + if (offset < 1) { + _offset = offset; + _lastPoint = glyphPoint; + } else { + _offset = 0; + _lastPoint = _P0 = _P3; + _lastX += _lastDistance; + return [self transformAtDistance:distance]; + } + + _lastX = distance; + CGAffineTransform transform = CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y); + transform = CGAffineTransformRotate(transform, angle); + + return transform; +} + +@end diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index 05e249e1..8c5da647 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -7,7 +7,6 @@ */ #import -#import "UIBezierPath-Points.h" #import "RNSVGPath.h" #import "RNSVGTextFrame.h" @@ -15,6 +14,6 @@ @property (nonatomic, assign) CTTextAlignment alignment; @property (nonatomic, assign) RNSVGTextFrame textFrame; -@property (nonatomic, assign) CGPathRef path; +@property (nonatomic, copy) NSArray *path; @end diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index d7984667..d4c294b3 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -7,7 +7,7 @@ */ #import "RNSVGText.h" - +#import "RNSVGBezierPath.h" #import @implementation RNSVGText @@ -39,19 +39,17 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame) _textFrame = frame; } -- (void)setPath:(CGPathRef)path +- (void)setPath:(NSArray *)path { if (path == _path) { return; } [self invalidate]; - CGPathRelease(_path); - _path = CGPathRetain(path); + _path = path; } - (void)dealloc { - CGPathRelease(_path); RNSVGFreeTextFrame(_textFrame); } @@ -95,59 +93,41 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame) { CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0); CGMutablePathRef path = CGPathCreateMutable(); - CTLineGetGlyphRuns(line); - CFArrayRef glyphRuns = CTLineGetGlyphRuns(line); - CFIndex runCount = CFArrayGetCount(glyphRuns); - CFIndex glyphIndex = 0; - for(CFIndex i = 0; i < runCount; ++i) { - // For each run, we need to get the glyphs, their font (to get the path) and their locations. - CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, i); - CFIndex runGlyphCount = CTRunGetGlyphCount(run); - CGPoint positions[runGlyphCount]; - CGGlyph glyphs[runGlyphCount]; + CFArrayRef glyphRuns = CTLineGetGlyphRuns(line); + CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, 0); + + CFIndex runGlyphCount = CTRunGetGlyphCount(run); + CGPoint positions[runGlyphCount]; + CGGlyph glyphs[runGlyphCount]; + + // Grab the glyphs, positions, and font + CTRunGetPositions(run, CFRangeMake(0, 0), positions); + CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); + CFDictionaryRef attributes = CTRunGetAttributes(run); + CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); + + RNSVGBezierPath *bezierPath = [[RNSVGBezierPath alloc] initWithBezierCurves:self.path]; + + for(CFIndex i = 0; i < runGlyphCount; ++i) { + CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); + CGPoint point = positions[i]; - // Grab the glyphs, positions, and font - CTRunGetPositions(run, CFRangeMake(0, 0), positions); - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - CFDictionaryRef attributes = CTRunGetAttributes(run); - CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); - - for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) { - CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[j], nil); - CGPoint point = positions[j]; + if (letter) { + CGAffineTransform transform; - if (letter) { - CGAffineTransform transform; - - // draw glyphs along path - if (self.path) { - CGPoint slope; - CGRect bounding = CGPathGetBoundingBox(letter); - UIBezierPath* pathAlong = [UIBezierPath bezierPathWithCGPath:self.path]; - CGFloat percentConsumed = (point.x + bounding.size.width) / pathAlong.length; - if (percentConsumed >= 1.0f) { - CGPathRelease(letter); - continue; - } - - CGPoint targetPoint = [pathAlong pointAtPercent:percentConsumed withSlope: &slope]; - float angle = atan(slope.y / slope.x); // + M_PI; - if (slope.x < 0) { - angle += M_PI; // going left, update the angle - } - transform = CGAffineTransformMakeTranslation(targetPoint.x - bounding.size.width, targetPoint.y); - transform = CGAffineTransformRotate(transform, angle); - transform = CGAffineTransformScale(transform, 1.0, -1.0); - } else { - transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); - } - - CGPathAddPath(path, &transform, letter); + // draw glyphs along path + if (self.path) { + transform = [bezierPath transformAtDistance:point.x]; + transform = CGAffineTransformScale(transform, 1.0, -1.0); + } else { + transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); } - CGPathRelease(letter); + CGPathAddPath(path, &transform, letter); } + + CGPathRelease(letter); } return path; diff --git a/ios/Text/UIBezierPath-Points.h b/ios/Text/UIBezierPath-Points.h deleted file mode 100644 index 7f77604c..00000000 --- a/ios/Text/UIBezierPath-Points.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015-present, Horcrux. - * 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. - */ - -/* - Erica Sadun, http://ericasadun.com - iPhone Developer's Cookbook, 6.x Edition - BSD License, Use at your own risk - */ - -#import -#import - -@interface UIBezierPath (Points) -@property (nonatomic, readonly) NSArray *points; -@property (nonatomic, readonly) NSArray *bezierElements; -@property (nonatomic, readonly) CGFloat length; - -- (NSArray *) pointPercentArray; -- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope; -+ (UIBezierPath *) pathWithPoints: (NSArray *) points; -+ (UIBezierPath *) pathWithElements: (NSArray *) elements; -@end \ No newline at end of file diff --git a/ios/Text/UIBezierPath-Points.m b/ios/Text/UIBezierPath-Points.m deleted file mode 100644 index 17a3c996..00000000 --- a/ios/Text/UIBezierPath-Points.m +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright (c) 2015-present, Horcrux. - * 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. - */ - -/* - Erica Sadun, http://ericasadun.com - iPhone Developer's Cookbook, 6.x Edition - BSD License, Use at your own risk - */ - -#import "UIBezierPath-Points.h" - -#define POINTSTRING(_CGPOINT_) (NSStringFromCGPoint(_CGPOINT_)) -#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] -#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] - -// Return distance between two points -static float distance (CGPoint p1, CGPoint p2) -{ - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - - return sqrt(dx*dx + dy*dy); -} - -@implementation UIBezierPath (Points) -void getPointsFromBezier(void *info, const CGPathElement *element) -{ - NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info; - CGPathElementType type = element->type; - CGPoint *points = element->points; - if (type != kCGPathElementCloseSubpath) - { - if ((type == kCGPathElementAddLineToPoint) || - (type == kCGPathElementMoveToPoint)) - [bezierPoints addObject:VALUE(0)]; - else if (type == kCGPathElementAddQuadCurveToPoint) - [bezierPoints addObject:VALUE(1)]; - else if (type == kCGPathElementAddCurveToPoint) - [bezierPoints addObject:VALUE(2)]; - } -} - -- (NSArray *)points -{ - NSMutableArray *points = [NSMutableArray array]; - CGPathApply(self.CGPath, (__bridge void *)points, getPointsFromBezier); - return points; -} - -// Return a Bezier path buit with the supplied points -+ (UIBezierPath *) pathWithPoints: (NSArray *) points -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - if (points.count == 0) return path; - [path moveToPoint:POINT(0)]; - for (int i = 1; i < points.count; i++) - [path addLineToPoint:POINT(i)]; - return path; -} - -- (CGFloat) length -{ - NSArray *points = self.points; - float totalPointLength = 0.0f; - for (int i = 1; i < points.count; i++) - totalPointLength += distance(POINT(i), POINT(i-1)); - return totalPointLength; -} - -- (NSArray *) pointPercentArray -{ - // Use total length to calculate the percent of path consumed at each control point - NSArray *points = self.points; - NSUInteger pointCount = points.count; - - float totalPointLength = self.length; - float distanceTravelled = 0.0f; - - NSMutableArray *pointPercentArray = [NSMutableArray array]; - [pointPercentArray addObject:@(0.0)]; - - for (int i = 1; i < pointCount; i++) - { - distanceTravelled += distance(POINT(i), POINT(i-1)); - [pointPercentArray addObject:@(distanceTravelled / totalPointLength)]; - } - - // Add a final item just to stop with. Probably not needed. - [pointPercentArray addObject:[NSNumber numberWithFloat:1.1f]]; // 110% - - return pointPercentArray; -} - -- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope -{ - NSArray *points = self.points; - NSArray *percentArray = self.pointPercentArray; - CFIndex lastPointIndex = points.count - 1; - - if (!points.count) { - return CGPointZero; - } - - // Check for 0% and 100% - if (percent <= 0.0f) { - return POINT(0); - } - if (percent >= 1.0f) { - return POINT(lastPointIndex); - } - - // Find a corresponding pair of points in the path - CFIndex index = 1; - while ((index < percentArray.count) && - (percent > ((NSNumber *)percentArray[index]).floatValue)) { - index++; - } - - // This should not happen. - if (index > lastPointIndex) { - return POINT(lastPointIndex); - } - - // Calculate the intermediate distance between the two points - CGPoint point1 = POINT(index -1); - CGPoint point2 = POINT(index); - - float percent1 = [[percentArray objectAtIndex:index - 1] floatValue]; - float percent2 = [[percentArray objectAtIndex:index] floatValue]; - float percentOffset = (percent - percent1) / (percent2 - percent1); - - float dx = point2.x - point1.x; - float dy = point2.y - point1.y; - - // Store dy, dx for retrieving arctan - if (slope) { - *slope = CGPointMake(dx, dy); - } - - // Calculate new point - CGFloat newX = point1.x + (percentOffset * dx); - CGFloat newY = point1.y + (percentOffset * dy); - CGPoint targetPoint = CGPointMake(newX, newY); - - return targetPoint; -} - -void getBezierElements(void *info, const CGPathElement *element) -{ - NSMutableArray *bezierElements = (__bridge NSMutableArray *)info; - CGPathElementType type = element->type; - CGPoint *points = element->points; - - switch (type) - { - case kCGPathElementCloseSubpath: - [bezierElements addObject:@[@(type)]]; - break; - case kCGPathElementMoveToPoint: - case kCGPathElementAddLineToPoint: - [bezierElements addObject:@[@(type), VALUE(0)]]; - break; - case kCGPathElementAddQuadCurveToPoint: - [bezierElements addObject:@[@(type), VALUE(0), VALUE(1)]]; - break; - case kCGPathElementAddCurveToPoint: - [bezierElements addObject:@[@(type), VALUE(0), VALUE(1), VALUE(2)]]; - break; - } -} - -- (NSArray *) bezierElements -{ - NSMutableArray *elements = [NSMutableArray array]; - CGPathApply(self.CGPath, (__bridge void *)elements, getBezierElements); - return elements; -} - -+ (UIBezierPath *) pathWithElements: (NSArray *) elements -{ - UIBezierPath *path = [UIBezierPath bezierPath]; - if (elements.count == 0) return path; - - for (NSArray *points in elements) - { - if (!points.count) continue; - CGPathElementType elementType = [points[0] integerValue]; - switch (elementType) - { - case kCGPathElementCloseSubpath: - [path closePath]; - break; - case kCGPathElementMoveToPoint: - if (points.count == 2) - [path moveToPoint:POINT(1)]; - break; - case kCGPathElementAddLineToPoint: - if (points.count == 2) - [path addLineToPoint:POINT(1)]; - break; - case kCGPathElementAddQuadCurveToPoint: - if (points.count == 3) - [path addQuadCurveToPoint:POINT(2) controlPoint:POINT(1)]; - break; - case kCGPathElementAddCurveToPoint: - if (points.count == 4) - [path addCurveToPoint:POINT(3) controlPoint1:POINT(1) controlPoint2:POINT(2)]; - break; - } - } - - return path; -} -@end diff --git a/ios/Utils/RCTConvert+RNSVG.h b/ios/Utils/RCTConvert+RNSVG.h index 9a8c265e..744b1a43 100644 --- a/ios/Utils/RCTConvert+RNSVG.h +++ b/ios/Utils/RCTConvert+RNSVG.h @@ -24,6 +24,7 @@ + (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json; + (RNSVGBrush *)RNSVGBrush:(id)json; ++ (NSArray *)RNSVGBezier:(id)json; + (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset; + (CGRect)CGRect:(id)json offset:(NSUInteger)offset; + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index 7638f078..e9c65c5c 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -19,14 +19,14 @@ + (CGPathRef)CGPath:(id)json { NSArray *arr = [self NSNumberArray:json]; - + NSUInteger count = [arr count]; - + #define NEXT_VALUE [self double:arr[i++]] - + CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, 0, 0); - + @try { NSUInteger i = 0; while (i < count) { @@ -44,9 +44,6 @@ case 3: CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE); break; - case 4: - CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0); - break; default: RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr); CGPathRelease(path); @@ -59,7 +56,7 @@ CGPathRelease(path); return NULL; } - + return (CGPathRef)CFAutorelease(path); } @@ -83,25 +80,25 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ NSDictionary *dict = [self NSDictionary:json]; RNSVGTextFrame frame; frame.count = 0; - + NSArray *lines = [self NSArray:dict[@"lines"]]; NSUInteger lineCount = [lines count]; if (lineCount == 0) { return frame; } - + NSDictionary *fontDict = dict[@"font"]; CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"] scaleMultiplier:1.0]; if (!font) { return frame; } - + // Create a dictionary for this font CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{ (NSString *)kCTFontAttributeName: (__bridge id)font, (NSString *)kCTForegroundColorFromContextAttributeName: @YES }; - + // Set up text frame with font metrics CGFloat size = CTFontGetSize(font); frame.count = lineCount; @@ -109,18 +106,18 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ frame.lineHeight = size * 1.1; // Base on RNSVG canvas line height estimate frame.lines = malloc(sizeof(CTLineRef) * lineCount); frame.widths = malloc(sizeof(CGFloat) * lineCount); - + [lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) { - + CFStringRef string = (__bridge CFStringRef)text; CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attrString); CFRelease(attrString); - + frame.lines[i] = line; frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL); }]; - + return frame; } @@ -128,11 +125,11 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ { NSArray *arr = [self NSNumberArray:json]; NSUInteger count = arr.count; - + RNSVGCGFloatArray array; array.count = count; array.array = NULL; - + if (count) { // Ideally, these arrays should already use the same memory layout. // In that case we shouldn't need this new malloc. @@ -141,7 +138,7 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ array.array[i] = [arr[i] doubleValue]; } } - + return array; } @@ -149,7 +146,7 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ { NSArray *arr = [self NSArray:json]; NSUInteger type = [self NSUInteger:arr.firstObject]; - + switch (type) { case 0: // solid color // These are probably expensive allocations since it's often the same value. @@ -163,6 +160,63 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ } } ++ (NSArray *)RNSVGBezier:(id)json +{ + NSArray *arr = [self NSNumberArray:json]; + + NSMutableArray *beziers = [[NSMutableArray alloc] init]; + + NSUInteger count = [arr count]; + +#define NEXT_VALUE [self double:arr[i++]] + @try { + NSValue *startPoint = [NSValue valueWithCGPoint: CGPointMake(0, 0)]; + NSUInteger i = 0; + while (i < count) { + NSUInteger type = [arr[i++] unsignedIntegerValue]; + switch (type) { + case 0: + { + startPoint = [NSValue valueWithCGPoint: CGPointMake(NEXT_VALUE, NEXT_VALUE)]; + [beziers addObject: @[startPoint]]; + break; + } + case 1: + [beziers addObject: @[]]; + break; + case 2: + { + double x = NEXT_VALUE; + double y = NEXT_VALUE; + NSValue * destination = [NSValue valueWithCGPoint:CGPointMake(x, y)]; + [beziers addObject: @[ + destination, + startPoint, + destination + ]]; + break; + } + case 3: + [beziers addObject: @[ + [NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)], + [NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)], + [NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)], + ]]; + break; + default: + RCTLogError(@"Invalid RNSVGBezier type %zd at element %zd of %@", type, i, arr); + return NULL; + } + } + } + @catch (NSException *exception) { + RCTLogError(@"Invalid RNSVGBezier format: %@", arr); + return NULL; + } + + return beziers; +} + + (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset { NSArray *arr = [self NSArray:json]; diff --git a/ios/ViewManagers/RNSVGGroupManager.m b/ios/ViewManagers/RNSVGGroupManager.m index 02635f74..bb2ef64b 100644 --- a/ios/ViewManagers/RNSVGGroupManager.m +++ b/ios/ViewManagers/RNSVGGroupManager.m @@ -19,6 +19,4 @@ RCT_EXPORT_MODULE() return [RNSVGGroup new]; } -RCT_EXPORT_VIEW_PROPERTY(mergeList, NSArray) - @end diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m index ae3c594d..4291886b 100644 --- a/ios/ViewManagers/RNSVGRenderableManager.m +++ b/ios/ViewManagers/RNSVGRenderableManager.m @@ -31,5 +31,6 @@ RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin) RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, RNSVGCGFloatArray) RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(propList, NSArray) @end diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index 76280e35..7df0b2eb 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -22,6 +22,6 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment) RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame) -RCT_EXPORT_VIEW_PROPERTY(path, CGPath) +RCT_EXPORT_VIEW_PROPERTY(path, RNSVGBezier) @end diff --git a/ios/ViewManagers/RNSVGUseManager.m b/ios/ViewManagers/RNSVGUseManager.m index 920effe5..59e8ae9a 100644 --- a/ios/ViewManagers/RNSVGUseManager.m +++ b/ios/ViewManagers/RNSVGUseManager.m @@ -19,6 +19,5 @@ RCT_EXPORT_MODULE() } RCT_EXPORT_VIEW_PROPERTY(href, NSString) -RCT_EXPORT_VIEW_PROPERTY(mergeList, NSArray) @end diff --git a/lib/SerializablePath.js b/lib/SerializablePath.js index e0e9904b..2b78c59f 100644 --- a/lib/SerializablePath.js +++ b/lib/SerializablePath.js @@ -38,7 +38,7 @@ export default class SerializablePath { case 's': this.curve(p[i++], p[i++], null, null, p[i++], p[i++]); break; case 'q': this.curve(p[i++], p[i++], p[i++], p[i++]); break; case 't': this.curve(p[i++], p[i++]); break; - case 'a': this.arc(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], p[i + 2]); i += 7; break; + case 'a': this.arc(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], +p[i + 2]); i += 7; break; case 'h': this.line(p[i++], 0); break; case 'v': this.line(0, p[i++]); break; @@ -48,7 +48,7 @@ export default class SerializablePath { case 'S': this.curveTo(p[i++], p[i++], null, null, p[i++], p[i++]); break; case 'Q': this.curveTo(p[i++], p[i++], p[i++], p[i++]); break; case 'T': this.curveTo(p[i++], p[i++]); break; - case 'A': this.arcTo(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], p[i + 2]); i += 7; break; + case 'A': this.arcTo(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], +p[i + 2]); i += 7; break; case 'H': this.lineTo(p[i++], this.penY); break; case 'V': this.lineTo(this.penX, p[i++]); break; @@ -276,12 +276,9 @@ export default class SerializablePath { }; onArc = (sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) => { - if (rx !== ry || rotation) { - return this._arcToBezier( - sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation - ); - } - this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1); + return this._arcToBezier( + sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation + ); }; onClose = () => { diff --git a/lib/attributes.js b/lib/attributes.js index d183b75b..bc582ff2 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -52,6 +52,9 @@ const NodeAttributes = { clipPath: { diff: arrayDiffer }, + propList: { + diff: arrayDiffer + }, responsible: true }; @@ -77,16 +80,9 @@ const RenderableOnlyAttributes = { const RenderableAttributes = merge({}, NodeAttributes, RenderableOnlyAttributes); -const GroupAttributes = merge({ - mergeList: { - diff: arrayDiffer - } -}, NodeAttributes); +const GroupAttributes = RenderableAttributes; const UseAttributes = merge({ - mergeList: { - diff: arrayDiffer - }, href: true }, RenderableAttributes); diff --git a/lib/extract/extractBrush.js b/lib/extract/extractBrush.js index fa05b099..7e1c4778 100644 --- a/lib/extract/extractBrush.js +++ b/lib/extract/extractBrush.js @@ -3,20 +3,22 @@ import _ from 'lodash'; import patternReg from './patternReg'; export default function (colorOrBrush) { - if (colorOrBrush === 'none') { + if (colorOrBrush === 'none' || _.isNil(colorOrBrush)) { return null; - } else if (_.isNil(colorOrBrush)) { - colorOrBrush = '#000'; } - let matched = colorOrBrush.match(patternReg); - - // brush - if (matched) { - return [1, matched[1]]; - //todo: - } else { // solid color - let c = new Color(colorOrBrush).rgbaArray(); - return [0, c[0] / 255, c[1] / 255, c[2] / 255, c[3]]; + try { + let matched = colorOrBrush.match(patternReg); + // brush + if (matched) { + return [1, matched[1]]; + //todo: + } else { // solid color + let c = new Color(colorOrBrush).rgbaArray(); + return [0, c[0] / 255, c[1] / 255, c[2] / 255, c[3]]; + } + } catch(err) { + console.warn(`"${colorOrBrush}" is not a valid color or brush`); + return null; } } diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js index 0e8ebfe2..dc26ab94 100644 --- a/lib/extract/extractFill.js +++ b/lib/extract/extractFill.js @@ -1,5 +1,6 @@ import extractBrush from './extractBrush'; import extractOpacity from './extractOpacity'; +import _ from 'lodash'; const fillRules = { evenodd: 0, @@ -8,7 +9,8 @@ const fillRules = { export default function(props) { return { - fill: extractBrush(props.fill), + // default fill is black + fill: extractBrush(_.isNil(props.fill) ? '#000' : props.fill), fillOpacity: extractOpacity(props.fillOpacity), fillRule: fillRules[props.fillRule] === 0 ? 0 : 1 }; diff --git a/lib/extract/extractProps.js b/lib/extract/extractProps.js index ecdd15be..f8f1d079 100644 --- a/lib/extract/extractProps.js +++ b/lib/extract/extractProps.js @@ -4,11 +4,29 @@ import extractTransform from './extractTransform'; import extractClipping from './extractClipping'; import extractResponder from './extractResponder'; import extractOpacity from './extractOpacity'; +import {RenderableOnlyAttributes} from '../attributes'; import _ from 'lodash'; export default function(props, options = {stroke: true, transform: true, fill: true, responder: true}) { + let propList = []; + Object.keys(RenderableOnlyAttributes).forEach(name => { + if (!_.isNil(props[name])) { + // clipPath prop may provide `clipPathRef` as native prop + if (name === 'clipPath') { + if (extractedProps[name]) { + propList.push(name); + } else if (extractedProps.clipPathRef) { + propList.push('clipPathRef'); + } + } else { + propList.push(name); + } + } + }); + let extractedProps = { - opacity: extractOpacity(props.opacity) + opacity: extractOpacity(props.opacity), + propList }; if (props.id) { diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 671cd649..94b08568 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -17,16 +17,11 @@ const joins = { export default function(props) { let {stroke} = props; - if (!stroke) { - return null; - } let strokeWidth = +props.strokeWidth; if (_.isNil(props.strokeWidth)) { - strokeWidth = 1; - } else if (!strokeWidth) { - return; + strokeWidth = null; } let strokeDasharray = props.strokeDasharray; @@ -40,10 +35,6 @@ export default function(props) { strokeDasharray.push(strokeDasharray[0]); } - if (!stroke) { - stroke = '#000'; - } - return { stroke: extractBrush(stroke), strokeOpacity: extractOpacity(props.strokeOpacity), diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 4fe0b561..514e6284 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -1,4 +1,4 @@ -import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath'; +import SerializablePath from '../SerializablePath'; import _ from 'lodash'; const newLine = /\n/g; const defaultFontFamily = '"Helvetica Neue", "Helvetica", Arial'; @@ -82,6 +82,7 @@ const anchord = { }; export default function(props) { + return { alignment: anchord[props.textAnchor] || 0, frame: extractFontAndLines( diff --git a/lib/reusableProps.js b/lib/reusableProps.js deleted file mode 100644 index 07be0dc2..00000000 --- a/lib/reusableProps.js +++ /dev/null @@ -1,22 +0,0 @@ -import {RenderableOnlyAttributes} from '../lib/attributes'; -import _ from 'lodash'; - -export default function (extractedProps, originProps) { - let reusableProps = []; - Object.keys(RenderableOnlyAttributes).forEach(name => { - if (!_.isNil(originProps[name])) { - // clipPath prop may provide `clipPathRef` as native prop - if (name === 'clipPath') { - if (extractedProps[name]) { - reusableProps.push(name); - } else if (extractedProps.clipPathRef) { - reusableProps.push('clipPathRef'); - } - } else { - reusableProps.push(name); - } - } - }); - - return reusableProps; -}