diff --git a/.eslintrc b/.eslintrc index 9cc29e43..63f0ee05 100644 --- a/.eslintrc +++ b/.eslintrc @@ -89,7 +89,6 @@ "no-caller": 1, // disallow use of arguments.caller or arguments.callee "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) "no-else-return": 0, // disallow else after a return in an if (off by default) - "no-empty-label": 1, // disallow use of labels for anything other then loops and switches "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) "no-eval": 1, // disallow use of eval() "no-extend-native": 1, // disallow adding to native types @@ -184,7 +183,7 @@ "quote-props": 0, // require quotes around object literal property names (off by default) "semi": 1, // require or disallow use of semicolons instead of ASI "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-after-keywords": 1, // require a space after certain keywords (off by default) + "keyword-spacing": 1, // require a space after certain keywords (off by default) "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) "space-infix-ops": 1, // require spaces around operators diff --git a/Example/examples/Circle.js b/Example/examples/Circle.js index f29951cd..41664ce1 100644 --- a/Example/examples/Circle.js +++ b/Example/examples/Circle.js @@ -78,7 +78,6 @@ const icon = ; const samples = [CircleExample, StrokeCircle, StrokeOpacityCircle]; - export { icon, samples diff --git a/Example/examples/Svg.js b/Example/examples/Svg.js index 12fc3aba..81f0a3fd 100644 --- a/Example/examples/Svg.js +++ b/Example/examples/Svg.js @@ -23,7 +23,8 @@ import Svg, { Circle, Rect, Path, - Line + Line, + G } from 'react-native-svg'; class SvgExample extends Component{ @@ -84,19 +85,21 @@ class SvgOpacity extends Component{ } class SvgViewbox extends Component{ - static title = 'SVG with `viewbox="40 20 100 40"`'; + static title = 'SVG with `viewBox="40 20 100 40" and preserveAspectRatio="none"`'; render() { return - - - - - + + + + + + + ; } } diff --git a/elements/Svg.js b/elements/Svg.js index db22ca09..6d388ebb 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -18,11 +18,9 @@ class Svg extends Component{ opacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - viewbox: PropTypes.string, - // TODO: complete other values of preserveAspectRatio - // http://www.justinmccandless.com/demos/viewbox/index.html - // http://tutorials.jenkov.com/svg/svg-viewport-view-box.html - preserveAspectRatio: PropTypes.string // preserveAspectRatio only supports 'none' for now + // more detail https://svgwg.org/svg2-draft/coords.html#ViewBoxAttribute + viewBox: PropTypes.string, + preserveAspectRatio: PropTypes.string }; constructor() { @@ -51,13 +49,25 @@ class Svg extends Component{ let opacity = +props.opacity; let width = +props.width; let height = +props.height; - let flexLayout = isNaN(width) || isNaN(height); + let viewBox = props.viewBox; + let dimensions; - let content = (props.viewbox && !flexLayout) ? {props.children} : props.children; return ( @@ -66,7 +76,7 @@ class Svg extends Component{ opacity={null} width={null} height={null} - viewbox={null} + viewBox={null} preserveAspectRatio={null} ref={ele => this.root = ele} style={[ @@ -75,11 +85,7 @@ class Svg extends Component{ !isNaN(opacity) && { opacity }, - !flexLayout && { - width, - height, - flex: 0 - } + dimensions ]} > {content} diff --git a/elements/ViewBox.js b/elements/ViewBox.js index 018f7f4c..d5153c70 100644 --- a/elements/ViewBox.js +++ b/elements/ViewBox.js @@ -1,38 +1,73 @@ -import React, {Component} from 'react'; - +import React, {Component, PropTypes} from 'react'; +import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import {ViewBoxAttributes} from '../lib/attributes'; import G from './G'; -import extractViewbox from '../lib/extract/extractViewbox'; +import _ from 'lodash'; + +const meetOrSliceTypes = { + meet: 0, + slice: 1, + none: 2 +}; + +const alignEnum = _.reduce([ + 'xMinMin', 'xMidYMin', 'xMaxYMin', + 'xMinYMid', 'xMidYMid', 'xMaxYMid', + 'xMinYMax', 'xMidYMax', 'xMaxYMax', + 'none' +], (prev, name) => { + prev[name] = name; + return prev; +}, {}); + +const numberRegExp = /^\d*\.?\d*%?$/; +const spacesRegExp = /\s+/; + class ViewBox extends Component{ static displayName = 'ViewBox'; + static propTypes = { + viewBox: PropTypes.string.isRequired, + preserveAspectRatio: PropTypes.string + }; + + static defaultProps = { + preserveAspectRatio: 'xMidYMid meet' + }; + render() { - let viewbox = extractViewbox(this.props); - let scaleX = 1; - let scaleY = 1; - let x = 0; - let y = 0; - if (viewbox) { - scaleX = viewbox.scaleX; - scaleY = viewbox.scaleY; - x = viewbox.x; - y = viewbox.y; + let {viewBox, preserveAspectRatio} = this.props; + + let params = viewBox.trim().split(spacesRegExp); + + if (params.length !== 4 || !_.some(params, param => param && numberRegExp.test(param))) { + console.warn('`viewBox` expected a string like `minX minY width height`, but got:' + viewBox); + return + {this.props.children} + } + let modes = preserveAspectRatio.trim().split(spacesRegExp); - console.log(viewbox); - return - {(!scaleX || !scaleY) ? null : this.props.children} - ; + {this.props.children} + } } +const RNSVGViewBox = createReactNativeComponentClass({ + validAttributes: ViewBoxAttributes, + uiViewClassName: 'RNSVGViewBox' +}); + + export default ViewBox; diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index fab47103..ddd07439 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -7,7 +7,6 @@ */ #import "RNSVGGroup.h" -#import @implementation RNSVGGroup diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index 560c6055..3b8eaf00 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -69,33 +69,37 @@ - (void)renderLayerTo:(CGContextRef)context { - CGRect box = CGContextGetClipBoundingBox(context); - float height = CGRectGetHeight(box); - float width = CGRectGetWidth(box); - - RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init]; - CGFloat x = [convert stringToFloat:self.x relative:width offset:0]; - CGFloat y = [convert stringToFloat:self.y relative:height offset:0]; - CGFloat w = [convert stringToFloat:self.width relative:width offset:0]; - CGFloat h = [convert stringToFloat:self.height relative:height offset:0]; - + CGRect rect = [self getRect:context]; // add hit area self.hitArea = CGPathCreateMutable(); - CGPathRef rect = CGPathCreateWithRect(CGRectMake(x, y, w, h), nil); - CGPathAddPath(self.hitArea, nil, rect); - CGPathRelease(rect); - - if (self.opacity == 0) { - return; - } - + CGPathRef path = CGPathCreateWithRect(rect, nil); + CGPathAddPath(self.hitArea, nil, path); + CGPathRelease(path); [self clip:context]; + CGContextSaveGState(context); - CGContextTranslateCTM(context, 0, h); + CGContextTranslateCTM(context, 0, rect.size.height); CGContextScaleCTM(context, 1.0, -1.0); - CGContextDrawImage(context, CGRectMake(x, -y, w, h), image); + CGContextDrawImage(context, rect, image); CGContextRestoreGState(context); } +- (CGRect)getRect:(CGContextRef)context +{ + [self setBoundingBox:context]; + CGFloat x = [self getWidthRelatedValue:self.x]; + CGFloat y = [self getHeightRelatedValue:self.y]; + CGFloat width = [self getWidthRelatedValue:self.width]; + CGFloat height = [self getHeightRelatedValue:self.height]; + return CGRectMake(x, y, width, height); +} + +- (CGPathRef)getPath:(CGContextRef)context +{ + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, nil, [self getRect:context]); + return (CGPathRef)CFAutorelease(path); +} + @end diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 17d0490d..4165561d 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2901CE71EC2001E90A8 /* RNSVGText.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 */; }; + 10ABC7331D435915006CCF6E /* RNSVGViewBoxManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */; }; + 10ABC7361D43595E006CCF6E /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ABC7351D43595E006CCF6E /* RNSVGViewBox.m */; }; 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; }; 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1F1CE74E3100887C2B /* RNSVGEllipseManager.m */; }; 10BA0D361CE74E3100887C2B /* RNSVGGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D211CE74E3100887C2B /* RNSVGGroupManager.m */; }; @@ -94,11 +96,16 @@ 1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextFrame.h; path = Text/RNSVGTextFrame.h; 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 = ""; }; 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGCGFloatArray.h; path = Utils/RNSVGCGFloatArray.h; sourceTree = ""; }; 1039D2A11CE721A7001E90A8 /* RNSVGContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGContainer.h; sourceTree = ""; }; 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPercentageConverter.h; path = Utils/RNSVGPercentageConverter.h; sourceTree = ""; }; 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPercentageConverter.m; path = Utils/RNSVGPercentageConverter.m; sourceTree = ""; }; + 10ABC7311D435915006CCF6E /* RNSVGViewBoxManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGViewBoxManager.h; sourceTree = ""; }; + 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGViewBoxManager.m; sourceTree = ""; }; + 10ABC7341D43595E006CCF6E /* RNSVGViewBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGViewBox.h; sourceTree = ""; }; + 10ABC7351D43595E006CCF6E /* RNSVGViewBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGViewBox.m; sourceTree = ""; }; + 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGCGFCRule.h; path = Utils/RNSVGCGFCRule.h; sourceTree = ""; }; + 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGVBMOS.h; path = Utils/RNSVGVBMOS.h; sourceTree = ""; }; 10BA0D1C1CE74E3100887C2B /* RNSVGCircleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGCircleManager.h; sourceTree = ""; }; 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGCircleManager.m; sourceTree = ""; }; 10BA0D1E1CE74E3100887C2B /* RNSVGEllipseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGEllipseManager.h; sourceTree = ""; }; @@ -175,6 +182,8 @@ 10ED4AA11CF078830078BC02 /* RNSVGNode.m */, 0CF68AE11AF0549300FF9E5C /* RNSVGRenderable.h */, 0CF68AE21AF0549300FF9E5C /* RNSVGRenderable.m */, + 10ABC7341D43595E006CCF6E /* RNSVGViewBox.h */, + 10ABC7351D43595E006CCF6E /* RNSVGViewBox.m */, 0CF68AC21AF0540F00FF9E5C /* Products */, ); sourceTree = ""; @@ -208,6 +217,8 @@ 0CF68AF81AF0549300FF9E5C /* ViewManagers */ = { isa = PBXGroup; children = ( + 10ABC7311D435915006CCF6E /* RNSVGViewBoxManager.h */, + 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */, 10BEC1BE1D3F680F00FDCB19 /* RNSVGLinearGradientManager.h */, 10BEC1BF1D3F680F00FDCB19 /* RNSVGLinearGradientManager.m */, 10BEC1C01D3F680F00FDCB19 /* RNSVGRadialGradientManager.h */, @@ -299,11 +310,12 @@ 1039D29A1CE7212C001E90A8 /* Utils */ = { isa = PBXGroup; children = ( + 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */, + 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */, 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */, 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, - 1039D29D1CE72177001E90A8 /* RNSVGCGFCRule.h */, 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, ); name = Utils; @@ -365,12 +377,14 @@ buildActionMask = 2147483647; files = ( 10BA0D3F1CE74E3100887C2B /* RNSVGTextManager.m in Sources */, + 10ABC7361D43595E006CCF6E /* RNSVGViewBox.m in Sources */, 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */, 10BA0D4B1CE74E3D00887C2B /* RNSVGRect.m in Sources */, 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */, 10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */, 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */, 10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */, + 10ABC7331D435915006CCF6E /* RNSVGViewBoxManager.m in Sources */, 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */, 0CF68B0D1AF0549300FF9E5C /* RNSVGPattern.m in Sources */, 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */, diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 77a9de6a..9f97f493 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -29,6 +29,19 @@ @property (nonatomic, assign) CGMutablePathRef hitArea; @property (nonatomic, copy) NSArray *propList; -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; +- (void)setBoundingBox:(CGContextRef)context; + +- (CGFloat)getWidthRelatedValue:(NSString *)string; + +- (CGFloat)getHeightRelatedValue:(NSString *)string; + +- (CGFloat)getContextWidth; + +- (CGFloat)getContextHeight; + +- (CGFloat)getContextX; + +- (CGFloat)getContextY; + @end diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index aa7366a2..a1fe88ab 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -7,11 +7,17 @@ */ #import "RNSVGRenderable.h" +#import "RNSVGPercentageConverter.h" @implementation RNSVGRenderable { - NSMutableDictionary *originProperties; - NSArray *changedList; + NSMutableDictionary *_originProperties; + NSArray *_changedList; + RNSVGPercentageConverter *_widthConverter; + RNSVGPercentageConverter *_heightConverter; + CGFloat _contextWidth; + CGFloat _contextHeight; + CGRect _boundingBox; } - (id)init @@ -137,6 +143,44 @@ } } + +- (void)setBoundingBox:(CGContextRef)context +{ + _boundingBox = CGContextGetClipBoundingBox(context); + _widthConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:CGRectGetWidth(_boundingBox) offset:0]; + _heightConverter = [[RNSVGPercentageConverter alloc] initWithRelativeAndOffset:CGRectGetHeight(_boundingBox) offset:0]; +} + +- (CGFloat)getWidthRelatedValue:(NSString *)string +{ + return [_widthConverter stringToFloat:string]; +} + +- (CGFloat)getHeightRelatedValue:(NSString *)string +{ + return [_heightConverter stringToFloat:string]; +} + +- (CGFloat)getContextWidth +{ + return CGRectGetWidth(_boundingBox); +} + +- (CGFloat)getContextHeight +{ + return CGRectGetHeight(_boundingBox); +} + +- (CGFloat)getContextX +{ + return CGRectGetMinX(_boundingBox); +} + +- (CGFloat)getContextY +{ + return CGRectGetMinY(_boundingBox); +} + - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList { @@ -150,15 +194,15 @@ } if (!inherited) { - originProperties = [[NSMutableDictionary alloc] init]; - changedList = mergeList; + _originProperties = [[NSMutableDictionary alloc] init]; + _changedList = mergeList; } for (NSString *key in mergeList) { if (inherited) { [self inheritProperty:target propName:key]; } else { - [originProperties setValue:[self valueForKey:key] forKey:key]; + [_originProperties setValue:[self valueForKey:key] forKey:key]; [self setValue:[target valueForKey:key] forKey:key]; } } @@ -166,13 +210,13 @@ - (void)resetProperties { - if (changedList) { - for (NSString *key in changedList) { - [self setValue:[originProperties valueForKey:key] forKey:key]; + if (_changedList) { + for (NSString *key in _changedList) { + [self setValue:[_originProperties valueForKey:key] forKey:key]; } } [super resetProperties]; - changedList = nil; + _changedList = nil; } - (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName diff --git a/ios/RNSVGViewBox.h b/ios/RNSVGViewBox.h new file mode 100644 index 00000000..bc54ac6b --- /dev/null +++ b/ios/RNSVGViewBox.h @@ -0,0 +1,21 @@ +/** + * 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 "RNSVGGroup.h" +#import "RNSVGVBMOS.h" + +@interface RNSVGViewBox : RNSVGGroup + +@property (nonatomic, strong) NSString *minX; +@property (nonatomic, strong) NSString *minY; +@property (nonatomic, strong) NSString *vbWidth; +@property (nonatomic, strong) NSString *vbHeight; +@property (nonatomic, strong) NSString *align; +@property (nonatomic, assign) RNSVGVBMOS meetOrSlice; + +@end diff --git a/ios/RNSVGViewBox.m b/ios/RNSVGViewBox.m new file mode 100644 index 00000000..2ca9889a --- /dev/null +++ b/ios/RNSVGViewBox.m @@ -0,0 +1,102 @@ +/** + * 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 +#import "RNSVGViewBox.h" + +@implementation RNSVGViewBox + +- (void)renderTo:(CGContextRef)context +{ + // based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform + [self setBoundingBox:context]; + + // Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively. + CGFloat vbX = [self getWidthRelatedValue:self.minX]; + CGFloat vbY = [self getHeightRelatedValue:self.minY]; + CGFloat vbWidth = [self getWidthRelatedValue:self.vbWidth]; + CGFloat vbHeight = [self getHeightRelatedValue:self.vbHeight]; + + // Let e-x, e-y, e-width, e-height be the position and size of the element respectively. + CGFloat eX = [self getContextX]; + CGFloat eY = [self getContextY]; + CGFloat eWidth = [self getContextWidth]; + CGFloat eHeight = [self getContextHeight]; + + // Let align be the align value of preserveAspectRatio, or 'xMidyMid' if preserveAspectRatio is not defined. + NSString *align = self.align; + + // Let meetOrSlice be the meetOrSlice value of preserveAspectRatio, or 'meet' if preserveAspectRatio is not defined or if meetOrSlice is missing from this value. + RNSVGVBMOS meetOrSlice = self.meetOrSlice; + + // Initialize scale-x to e-width/vb-width. + CGFloat scaleX = eWidth / vbWidth; + + // Initialize scale-y to e-height/vb-height. + CGFloat scaleY = eHeight / vbHeight; + + + // Initialize translate-x to vb-x - e-x. + // Initialize translate-y to vb-y - e-y. + CGFloat translateX = vbX - eX; + CGFloat translateY = vbY - eY; + + // If align is 'none' + if (meetOrSlice == kRNSVGVBMOSNone) { + // Let scale be set the smaller value of scale-x and scale-y. + // Assign scale-x and scale-y to scale. + CGFloat scale = scaleX = scaleY = fmin(scaleX, scaleY); + + // If scale is greater than 1 + if (scale > 1) { + // Minus translateX by (eWidth / scale - vbWidth) / 2 + // Minus translateY by (eHeight / scale - vbHeight) / 2 + translateX -= (eWidth / scale - vbWidth) / 2; + translateY -= (eHeight / scale - vbHeight) / 2; + } else { + translateX -= (eWidth - vbWidth * scale) / 2; + translateY -= (eHeight - vbHeight * scale) / 2; + } + } else { + // If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller. + // Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the larger. + if (![align isEqualToString: @"none"] && meetOrSlice == kRNSVGVBMOSMeet) { + scaleX = scaleY = fmin(scaleX, scaleY); + } else if (![align isEqualToString: @"none"] && meetOrSlice == kRNSVGVBMOSSlice) { + scaleX = scaleY = fmax(scaleX, scaleY); + } + + // If align contains 'xMid', minus (e-width / scale-x - vb-width) / 2 from transform-x. + if ([align containsString:@"xMid"]) { + translateX -= (eWidth / scaleX - vbWidth) / 2; + } + + // If align contains 'xMax', minus (e-width / scale-x - vb-width) from transform-x. + if ([align containsString:@"xMax"]) { + translateX -= eWidth / scaleX - vbWidth; + } + + // If align contains 'yMid', minus (e-height / scale-y - vb-height) / 2 from transform-y. + if ([align containsString:@"YMid"]) { + translateY -= (eHeight / scaleY - vbHeight) / 2; + } + + // If align contains 'yMax', minus (e-height / scale-y - vb-height) from transform-y. + if ([align containsString:@"YMax"]) { + translateY -= eHeight / scaleY - vbHeight; + } + } + + CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY); + transform = CGAffineTransformTranslate(transform, -translateX, -translateY); + + CGContextConcatCTM(context, transform); + [super renderTo:context]; +} + +@end diff --git a/ios/Shapes/RNSVGCircle.m b/ios/Shapes/RNSVGCircle.m index 6bc13c91..ba016e85 100644 --- a/ios/Shapes/RNSVGCircle.m +++ b/ios/Shapes/RNSVGCircle.m @@ -46,22 +46,18 @@ - (CGPathRef)getPath:(CGContextRef)context { + [self setBoundingBox:context]; CGMutablePathRef path = CGPathCreateMutable(); - CGRect box = CGContextGetClipBoundingBox(context); - float height = CGRectGetHeight(box); - float width = CGRectGetWidth(box); - RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init]; - CGFloat cx = [convert stringToFloat:self.cx relative:width offset:0]; - CGFloat cy = [convert stringToFloat:self.cy relative:height offset:0]; + CGFloat cx = [self getWidthRelatedValue:self.cx]; + CGFloat cy = [self getHeightRelatedValue:self.cy]; CGFloat r; - // radius percentage calculate formula: // radius = sqrt(pow((width*percent), 2) + pow((height*percent), 2)) / sqrt(2) if ([convert isPercentage:self.r]) { CGFloat radiusPercent = [convert percentageToFloat:self.r relative:1 offset:0]; - r = sqrt(pow((width * radiusPercent), 2) + pow((height * radiusPercent), 2)) / sqrt(2); + r = sqrt(pow(([self getContextWidth] * radiusPercent), 2) + pow(([self getContextHeight] * radiusPercent), 2)) / sqrt(2); } else { r = [self.r floatValue]; } diff --git a/ios/Shapes/RNSVGEllipse.m b/ios/Shapes/RNSVGEllipse.m index 07f51292..048ecfec 100644 --- a/ios/Shapes/RNSVGEllipse.m +++ b/ios/Shapes/RNSVGEllipse.m @@ -55,17 +55,12 @@ - (CGPathRef)getPath:(CGContextRef)context { - + [self setBoundingBox:context]; CGMutablePathRef path = CGPathCreateMutable(); - CGRect box = CGContextGetClipBoundingBox(context); - float height = CGRectGetHeight(box); - float width = CGRectGetWidth(box); - - RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init]; - CGFloat cx = [convert stringToFloat:self.cx relative:width offset:0]; - CGFloat cy = [convert stringToFloat:self.cy relative:height offset:0]; - CGFloat rx = [convert stringToFloat:self.rx relative:width offset:0]; - CGFloat ry = [convert stringToFloat:self.ry relative:height offset:0]; + CGFloat cx = [self getWidthRelatedValue:self.cx]; + CGFloat cy = [self getHeightRelatedValue:self.cy]; + CGFloat rx = [self getWidthRelatedValue:self.rx]; + CGFloat ry = [self getHeightRelatedValue:self.ry]; CGPathAddEllipseInRect(path, nil, CGRectMake(cx - rx, cy - ry, rx * 2, ry * 2)); return (CGPathRef)CFAutorelease(path); } diff --git a/ios/Shapes/RNSVGLine.m b/ios/Shapes/RNSVGLine.m index f01f2146..f9e978a5 100644 --- a/ios/Shapes/RNSVGLine.m +++ b/ios/Shapes/RNSVGLine.m @@ -55,17 +55,12 @@ - (CGPathRef)getPath:(CGContextRef)context { - + [self setBoundingBox:context]; CGMutablePathRef path = CGPathCreateMutable(); - CGRect box = CGContextGetClipBoundingBox(context); - float height = CGRectGetHeight(box); - float width = CGRectGetWidth(box); - - RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init]; - CGFloat x1 = [convert stringToFloat:self.x1 relative:width offset:0]; - CGFloat y1 = [convert stringToFloat:self.y1 relative:height offset:0]; - CGFloat x2 = [convert stringToFloat:self.x2 relative:width offset:0]; - CGFloat y2 = [convert stringToFloat:self.y2 relative:height offset:0]; + CGFloat x1 = [self getWidthRelatedValue:self.x1]; + CGFloat y1 = [self getHeightRelatedValue:self.y1]; + CGFloat x2 = [self getWidthRelatedValue:self.x2]; + CGFloat y2 = [self getHeightRelatedValue:self.y2]; CGPathMoveToPoint(path, nil, x1, y1); CGPathAddLineToPoint(path, nil, x2, y2); diff --git a/ios/Shapes/RNSVGRect.m b/ios/Shapes/RNSVGRect.m index cb9b1785..ca25c245 100644 --- a/ios/Shapes/RNSVGRect.m +++ b/ios/Shapes/RNSVGRect.m @@ -73,21 +73,14 @@ - (CGPathRef)getPath:(CGContextRef)context { - + [self setBoundingBox:context]; CGMutablePathRef path = CGPathCreateMutable(); - - CGRect box = CGContextGetClipBoundingBox(context); - float height = CGRectGetHeight(box); - float width = CGRectGetWidth(box); - - RNSVGPercentageConverter* convert = [[RNSVGPercentageConverter alloc] init]; - CGFloat x = [convert stringToFloat:self.x relative:width offset:0]; - CGFloat y = [convert stringToFloat:self.y relative:height offset:0]; - CGFloat w = [convert stringToFloat:self.width relative:width offset:0]; - CGFloat h = [convert stringToFloat:self.height relative:height offset:0]; - CGFloat rx = [convert stringToFloat:self.rx relative:width offset:0]; - CGFloat ry = [convert stringToFloat:self.ry relative:height offset:0]; - + CGFloat x = [self getWidthRelatedValue:self.x]; + CGFloat y = [self getHeightRelatedValue:self.y]; + CGFloat width = [self getWidthRelatedValue:self.width]; + CGFloat height = [self getHeightRelatedValue:self.height]; + CGFloat rx = [self getWidthRelatedValue:self.rx]; + CGFloat ry = [self getHeightRelatedValue:self.ry]; if (rx != 0 || ry != 0) { if (rx == 0) { @@ -96,17 +89,17 @@ ry = rx; } - if (rx > w / 2) { - rx = w / 2; + if (rx > width / 2) { + rx = width / 2; } - if (ry > h / 2) { - ry = h / 2; + if (ry > height / 2) { + ry = height / 2; } - CGPathAddRoundedRect(path, nil, CGRectMake(x, y, w, h), rx, ry); + CGPathAddRoundedRect(path, nil, CGRectMake(x, y, width, height), rx, ry); } else { - CGPathAddRect(path, nil, CGRectMake(x, y, w, h)); + CGPathAddRect(path, nil, CGRectMake(x, y, width, height)); } return (CGPathRef)CFAutorelease(path); diff --git a/ios/Text/RNSVGBezierPath.m b/ios/Text/RNSVGBezierPath.m index f2a0416d..f5a18cf3 100644 --- a/ios/Text/RNSVGBezierPath.m +++ b/ios/Text/RNSVGBezierPath.m @@ -7,7 +7,7 @@ */ /** - * based on + * based on CurvyText by iosptl: https://github.com/iosptl/ios7ptl/blob/master/ch21-Text/CurvyText/CurvyText/CurvyTextView.m */ #import "RNSVGBezierPath.h" diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index e9c65c5c..bac7d127 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -13,6 +13,7 @@ #import "RNSVGSolidColorBrush.h" #import "RCTLog.h" #import "RNSVGCGFCRule.h" +#import "RNSVGVBMOS.h" @implementation RCTConvert (RNSVG) @@ -73,6 +74,13 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ @"nonzero": @(kRNSVGCGFCRuleNonzero), }), kRNSVGCGFCRuleNonzero, intValue) +RCT_ENUM_CONVERTER(RNSVGVBMOS, (@{ + @"meet": @(kRNSVGVBMOSMeet), + @"slice": @(kRNSVGVBMOSSlice), + @"none": @(kRNSVGVBMOSNone) + }), kRNSVGVBMOSMeet, intValue) + + // This takes a tuple of text lines and a font to generate a CTLine for each text line. // This prepares everything for rendering a frame of text in RNSVGText. + (RNSVGTextFrame)RNSVGTextFrame:(id)json diff --git a/ios/Utils/RNSVGPercentageConverter.h b/ios/Utils/RNSVGPercentageConverter.h index 189b2c2b..4eb94878 100644 --- a/ios/Utils/RNSVGPercentageConverter.h +++ b/ios/Utils/RNSVGPercentageConverter.h @@ -7,14 +7,21 @@ */ #import +#import @interface RNSVGPercentageConverter : NSObject - (NSRegularExpression *) getPercentageRegularExpression; -- (float) percentageToFloat:(NSString *)percentage relative:(float)relative offset:(float)offset; +- (instancetype) initWithRelativeAndOffset:(CGFloat)relative offset:(CGFloat)offset; -- (float) stringToFloat:(NSString *)string relative:(float)relative offset:(float)offset; +- (CGFloat) percentageToFloat:(NSString *)percentage relative:(CGFloat)relative offset:(CGFloat)offset; + +- (CGFloat) percentageToFloat:(NSString *)percentage; + +- (CGFloat) stringToFloat:(NSString *)string relative:(CGFloat)relative offset:(CGFloat)offset; + +- (CGFloat) stringToFloat:(NSString *)string; - (BOOL) isPercentage:(NSString *) string; diff --git a/ios/Utils/RNSVGPercentageConverter.m b/ios/Utils/RNSVGPercentageConverter.m index 3ea777ae..6b2b0a60 100644 --- a/ios/Utils/RNSVGPercentageConverter.m +++ b/ios/Utils/RNSVGPercentageConverter.m @@ -10,13 +10,24 @@ @implementation RNSVGPercentageConverter { + CGFloat _relative; + CGFloat _offset; NSRegularExpression *percentageRegularExpression; } +- (instancetype) initWithRelativeAndOffset:(CGFloat)relative offset:(CGFloat)offset +{ + if (self = [super init]) { + _relative = relative; + _offset = offset; + percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; + } + return self; +} + - (id)init { - self = [super init]; - if (self) { + if (self = [super init]) { percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; } return self; @@ -27,20 +38,29 @@ return percentageRegularExpression; } -- (float) stringToFloat:(NSString *)percentage relative:(float)relative offset:(float)offset +- (CGFloat) stringToFloat:(NSString *)string { - if ([self isPercentage:percentage] == NO) { - return [percentage floatValue]; + return [self stringToFloat:string relative:_relative offset:_offset]; +} + +- (CGFloat) stringToFloat:(NSString *)string relative:(CGFloat)relative offset:(CGFloat)offset +{ + if ([self isPercentage:string] == NO) { + return [string floatValue]; } else { - return [self percentageToFloat:percentage relative:relative offset:offset]; + return [self percentageToFloat:string relative:relative offset:offset]; } } -- (float) percentageToFloat:(NSString *)percentage relative:(float)relative offset:(float)offset +- (CGFloat) percentageToFloat:(NSString *)percentage { + return [self percentageToFloat:percentage relative:_relative offset:_offset]; +} +- (CGFloat) percentageToFloat:(NSString *)percentage relative:(CGFloat)relative offset:(CGFloat)offset +{ + __block CGFloat matched; - __block float matched; [percentageRegularExpression enumerateMatchesInString:percentage options:0 range:NSMakeRange(0, percentage.length) diff --git a/ios/Utils/RNSVGVBMOS.h b/ios/Utils/RNSVGVBMOS.h new file mode 100644 index 00000000..24c404a2 --- /dev/null +++ b/ios/Utils/RNSVGVBMOS.h @@ -0,0 +1,13 @@ +/** + * 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. + */ + +typedef CF_ENUM(int32_t, RNSVGVBMOS) { + kRNSVGVBMOSMeet, + kRNSVGVBMOSSlice, + kRNSVGVBMOSNone +}; diff --git a/ios/ViewManagers/RNSVGViewBoxManager.h b/ios/ViewManagers/RNSVGViewBoxManager.h new file mode 100644 index 00000000..f7647bec --- /dev/null +++ b/ios/ViewManagers/RNSVGViewBoxManager.h @@ -0,0 +1,13 @@ +/** + * 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 "RNSVGGroupManager.h" + +@interface RNSVGViewBoxManager : RNSVGGroupManager + +@end diff --git a/ios/ViewManagers/RNSVGViewBoxManager.m b/ios/ViewManagers/RNSVGViewBoxManager.m new file mode 100644 index 00000000..03e1df7b --- /dev/null +++ b/ios/ViewManagers/RNSVGViewBoxManager.m @@ -0,0 +1,29 @@ +/** + * 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 "RNSVGViewBoxManager.h" +#import "RNSVGViewBox.h" +#import "RNSVGVBMOS.h" + +@implementation RNSVGViewBoxManager + +RCT_EXPORT_MODULE() + +- (RNSVGViewBox *)node +{ + return [RNSVGViewBox new]; +} + +RCT_EXPORT_VIEW_PROPERTY(minX, NSString) +RCT_EXPORT_VIEW_PROPERTY(minY, NSString) +RCT_EXPORT_VIEW_PROPERTY(vbWidth, NSString) +RCT_EXPORT_VIEW_PROPERTY(vbHeight, NSString) +RCT_EXPORT_VIEW_PROPERTY(align, NSString) +RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS) + +@end diff --git a/lib/attributes.js b/lib/attributes.js index bc582ff2..b56b7ce5 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -41,6 +41,15 @@ function fontAndLinesDiffer(a, b) { return arrayDiffer(a.lines, b.lines); } +const ViewBoxAttributes = { + minX: true, + minY: true, + vbWidth: true, + vbHeight: true, + align: true, + meetOrSlice: true +}; + const NodeAttributes = { name: true, transform: { @@ -180,5 +189,6 @@ export { UseAttributes, RenderableOnlyAttributes, LinearGradientAttributes, - RadialGradientAttributes + RadialGradientAttributes, + ViewBoxAttributes }; diff --git a/lib/extract/extractClipping.js b/lib/extract/extractClipping.js index 7f22ea38..6b03a60d 100644 --- a/lib/extract/extractClipping.js +++ b/lib/extract/extractClipping.js @@ -1,4 +1,4 @@ -import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath'; +import SerializablePath from '../SerializablePath'; import clipReg from './patternReg'; const clipRules = { diff --git a/lib/extract/extractViewbox.js b/lib/extract/extractViewbox.js deleted file mode 100644 index 2797e454..00000000 --- a/lib/extract/extractViewbox.js +++ /dev/null @@ -1,71 +0,0 @@ -export default function({viewbox, width, height, preserveAspectRatio, x: dx, y: dy, dScale, dScaleX, dScaleY}) { - if (!viewbox || !width || !height) { - return false; - } - - if (typeof viewbox === 'string') { - let parts = viewbox.trim().split(/\s+/); - let vw = +parts[2]; - let vh = +parts[3]; - - // width or height can`t be negative - if (vw < 0 || vh < 0 || parts.length !== 4) { - return false; - } - - // width or height equals zero disable render - if (!vw || !vh) { - return { - x: 0, - y: 0, - scaleX: 0, - scaleY: 0 - }; - } - - let vx = +parts[0] || 0; - let vy = +parts[1] || 0; - let preserve = preserveAspectRatio !== 'none'; - let scaleX = 1; - let scaleY = 1; - let x = 0; - let y = 0; - let sx = width / vw; - let sy = height / vh; - if (preserve) { - scaleX = scaleY = Math.min(sx, sy); - x = width / 2 - Math.min(vw, vh) * scaleX / 2 - vx * scaleX; - y = 0 - vy * scaleX; - - if (sx < sy) { - [x, y] = [y, x]; - } - } else { - scaleX = sx; - scaleY = sy; - x = -vx * sx; - y = -vy * sy; - } - - //if (shouldTransform) { - x += (+dx || 0); - y += (+dy || 0); - - if (dScale) { - scaleX *= (+dScale || 1); - scaleY *= (+dScale || 1); - } else { - scaleX *= (+dScaleX || 1); - scaleY *= (+dScaleY || 1); - } - //} - - return { - x, - y, - scaleX, - scaleY - }; - } - return false; -}