From 24dcc83a809ea734828b4395954ae2416e74a100 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Tue, 26 Apr 2016 19:10:57 +0800 Subject: [PATCH] complete basic shapes(ios) --- Example/examples/Clipping.js | 2 +- elements/Circle.js | 38 +++++---- elements/Ellipse.js | 43 +++++----- elements/G.js | 44 +++++----- elements/Line.js | 38 ++++----- elements/LinearGradient.js | 2 +- elements/Path.js | 37 ++++----- elements/RadialGradient.js | 2 +- elements/Rect.js | 113 ++++---------------------- elements/Shape.js | 69 +++++++++++----- elements/Text.js | 2 +- elements/Use.js | 3 +- ios/RCTConvert+RNSVG.m | 34 ++++---- ios/RNSVG.xcodeproj/project.pbxproj | 16 +++- ios/RNSVGNode.h | 2 +- ios/RNSVGNode.m | 1 - ios/RNSVGShape.h | 17 ++++ ios/RNSVGShape.m | 115 +++++++++++++++++++++++++++ ios/RNSVGSvgView.m | 2 +- ios/ViewManagers/RNSVGShapeManager.h | 13 +++ ios/ViewManagers/RNSVGShapeManager.m | 25 ++++++ lib/SerializableShape.js | 51 +++++++++++- lib/attributes.js | 20 ++++- lib/extract/extractText.js | 4 +- lib/props.js | 103 ++++++++++++++++++++++++ 25 files changed, 540 insertions(+), 256 deletions(-) create mode 100644 ios/RNSVGShape.h create mode 100644 ios/RNSVGShape.m create mode 100644 ios/ViewManagers/RNSVGShapeManager.h create mode 100644 ios/ViewManagers/RNSVGShapeManager.m create mode 100644 lib/props.js diff --git a/Example/examples/Clipping.js b/Example/examples/Clipping.js index 7b8fcb46..7a3573bb 100644 --- a/Example/examples/Clipping.js +++ b/Example/examples/Clipping.js @@ -213,7 +213,7 @@ const icon = ; -const samples = [ClipPathAttr, ClipRule, ClipPathElement, TextClipping]; +const samples = [ClipPathAttr, ClipRule];//, ClipPathElement, TextClipping export { icon, diff --git a/elements/Circle.js b/elements/Circle.js index ea11e311..b60f9c94 100644 --- a/elements/Circle.js +++ b/elements/Circle.js @@ -2,34 +2,32 @@ import React, { Component, PropTypes } from 'react-native'; -import Ellipse from './Ellipse'; -let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); -class Circle extends Component{ +import Shape, {CIRCLE} from './Shape'; +import {circleProps, pathProps} from '../lib/props'; +import _ from 'lodash'; + +class Circle extends Shape{ static displayName = 'Circle'; static propTypes = { - cx: propType, - cy: propType, - r: propType + ...pathProps, + ...circleProps }; + static defaultProps = { cx: 0, - cy: 0 + cy: 0, + r: 0 }; - static getPath = props => Ellipse.getPath({ - cx: props.cx, - cy: props.cy, - rx: props.r, - ry: props.r - }); + static contextTypes = { + ...circleProps, + isInGroup: PropTypes.bool + }; - render() { - return - } + constructor() { + super(...arguments); + this.type = CIRCLE; + }; } export default Circle; diff --git a/elements/Ellipse.js b/elements/Ellipse.js index 3fcdbc1c..290973cc 100644 --- a/elements/Ellipse.js +++ b/elements/Ellipse.js @@ -2,35 +2,32 @@ import React, { Component, PropTypes } from 'react-native'; -import Path from './Path'; -let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); -class Ellipse extends Component{ +import Shape, {ELLIPSE} from './Shape'; +import {ellipseProps, pathProps} from '../lib/props'; + +class Ellipse extends Shape{ static displayName = 'Ellipse'; static propTypes = { - cx: propType, - cy: propType, - rx: propType, - ry: propType + ...pathProps, + ...ellipseProps }; - static getPath = props => { - let {cx, cy, rx, ry} = props; - return ` - M ${cx - rx} ${cy} - a ${rx}, ${ry} 0 1, 0 ${rx * 2}, 0 - a ${rx}, ${ry} 0 1, 0 ${-rx * 2}, 0 - Z - `; + static defaultProps = { + cx: 0, + cy: 0, + rx: 0, + ry: 0 }; - render() { - let {props} = this; - let d = Ellipse.getPath(this.props); - return ; - } + static contextTypes = { + ...ellipseProps, + isInGroup: PropTypes.bool + }; + + constructor() { + super(...arguments); + this.type = ELLIPSE; + }; } export default Ellipse; diff --git a/elements/G.js b/elements/G.js index 60593eb8..d56c7fd6 100644 --- a/elements/G.js +++ b/elements/G.js @@ -2,42 +2,34 @@ import React, { Component, Children, cloneElement, + PropTypes, requireNativeComponent } from 'react-native'; import createReactNativeComponentClass from 'react-native/Libraries/ReactNative/createReactNativeComponentClass'; import Defs from './Defs'; +import _ from 'lodash'; import {GroupAttributes} from '../lib/attributes'; - -const transformProps = { - scale: null, - scaleX: null, - scaleY: null, - rotate: null, - transform: null, - x: null, - y: null, - originX: null, - originY: null -}; - -const clipProps = { - clipPath: null, - clipRule: null -}; +import {numberProp, shapeProps} from '../lib/props'; import extractProps from '../lib/extract/extractProps'; class G extends Component{ static displayName = 'G'; - getChildren = () => { - return Children.map(this.props.children, child => cloneElement(child, { - ...this.props, - ...transformProps, - ...clipProps, - ...child.props, - id: null - })); + static childContextTypes = { + svgId: numberProp, + isInGroup: PropTypes.bool, + ...shapeProps + }; + + getChildContext = () => { + return _.reduce(shapeProps, (props, value, key) => { + props[key] = this.props[key]; + return props; + }, { + svgId: this.props.svgId, + isInGroup: true + }); }; render() { @@ -56,7 +48,7 @@ class G extends Component{ return - {this.getChildren()} + {this.props.children} ; } } diff --git a/elements/Line.js b/elements/Line.js index 6e6becd0..8720c056 100644 --- a/elements/Line.js +++ b/elements/Line.js @@ -2,30 +2,32 @@ import React, { Component, PropTypes } from 'react-native'; -import Path from './Path'; +import Shape, {LINE} from './Shape'; +import {lineProps, pathProps} from '../lib/props'; -let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); - -class Line extends Component{ +class Line extends Shape{ static displayName = 'Line'; static propTypes = { - x1: propType, - x2: propType, - y1: propType, - y2: propType, - strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']) + ...pathProps, + ...lineProps }; - static getPath = (props) => ( - `M${props.x1},${props.y1} L${props.x2},${props.y2}` - ); + static defaultProps = { + x1: 0, + x2: 0, + y1: 0, + y2: 0 + }; - render() { - return ; - } + static contextTypes = { + ...lineProps, + isInGroup: PropTypes.bool + }; + + constructor() { + super(...arguments); + this.type = LINE; + }; } export default Line; diff --git a/elements/LinearGradient.js b/elements/LinearGradient.js index ce27d2fd..ba30e2c3 100644 --- a/elements/LinearGradient.js +++ b/elements/LinearGradient.js @@ -5,7 +5,7 @@ import React, { } from 'react-native'; import stopsOpacity from '../lib/stopsOpacity'; -import numberProp from '../lib/numberProp'; +import {numberProp} from '../lib/props'; import Gradient from './Gradient'; import {LINEAR_GRADIENT} from '../lib/extract/extractBrush'; import insertColorStopsIntoArray from '../lib/insertProcessor'; diff --git a/elements/Path.js b/elements/Path.js index f50b1c0d..d6ac7dfe 100644 --- a/elements/Path.js +++ b/elements/Path.js @@ -1,8 +1,7 @@ import React, { Component, PropTypes, - requireNativeComponent, - cloneElement + requireNativeComponent } from 'react-native'; import Defs from './Defs'; @@ -11,24 +10,23 @@ import calculateBoundingBox from '../lib/calculateBoundingBox'; import extractProps from '../lib/extract/extractProps'; import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath'; import {PathAttributes} from '../lib/attributes'; - +import {pathProps} from '../lib/props'; let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); class Path extends Component{ static displayName = 'Path'; + static propTypes = { visible: PropTypes.bool, d: PropTypes.string, - x: propType, - y: propType, - strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), - strokeCap: PropTypes.oneOf(['butt', 'square', 'round']), - strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']), - strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round']), - strokeDasharray: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.number)]) + ...pathProps + }; + + static contextTypes = { + ...pathProps, + isInGroup: PropTypes.bool }; - static getPath = props => props.d; _dimensions = null; @@ -38,16 +36,15 @@ class Path extends Component{ } }; - getBoundingBox = () => { - if (!this._dimensions) { - this._dimensions = calculateBoundingBox(this.props.d); - } - - return this._dimensions; - }; - render() { let {props} = this; + + if (this.context.isInGroup) { + props = _.defaults(this.context, props, { + isInGroup: null + }); + } + if (props.id) { return ); diff --git a/elements/RadialGradient.js b/elements/RadialGradient.js index 2ae506e7..4d42d28d 100644 --- a/elements/RadialGradient.js +++ b/elements/RadialGradient.js @@ -4,7 +4,7 @@ import React, { Children } from 'react-native'; import stopsOpacity from '../lib/stopsOpacity'; -import numberProp from '../lib/numberProp'; +import {numberProp} from '../lib/props'; import Gradient from './Gradient'; import {RADIAL_GRADIENT} from '../lib/extract/extractBrush'; import insertColorStopsIntoArray from '../lib/insertProcessor'; diff --git a/elements/Rect.js b/elements/Rect.js index f8838b19..b9da6091 100644 --- a/elements/Rect.js +++ b/elements/Rect.js @@ -2,115 +2,36 @@ import React, { Component, PropTypes } from 'react-native'; +import Shape, {RECT} from './Shape'; +import {rectProps, pathProps} from '../lib/props'; -import Path from './Path'; - -let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); - -function processRadius(radius) { - radius = +radius; - return radius || 0; -} - -function getR(props) { - let { - width, - height, - rx, - ry - } = props; - - rx = processRadius(rx); - ry = processRadius(ry); - - if ((rx && !ry) || (ry && !rx)) { - if (rx) { - ry = rx; - } else { - rx = ry; - } - } - - if (rx > width / 2) { - rx = width / 2; - } - if (ry > height / 2) { - ry = height / 2; - } - - return { - rx, - ry - }; -} - -class Rect extends Component{ +class Rect extends Shape{ static displayName = 'Rect'; static propTypes = { - x: propType, - y: propType, - width: propType, - height: propType, - rx: propType, - ry: propType, - strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), - strokeCap: PropTypes.oneOf(['butt', 'square', 'round']), - strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']), - strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round']) + ...pathProps, + ...rectProps }; + static defaultProps = { x: 0, y: 0, + width: 0, + height: 0, rx: 0, ry: 0 }; - static getPath = (props, r) => { - let { - x, - y, - width, - height - } = props; - if (!r) { - r = getR(props); - - } - - let {rx, ry} = r; - - - return (rx || ry) ? ` - M ${x}, ${y} - h ${width - 2 * rx} - a ${rx},${ry} 0 0 1 ${rx},${ry} - v ${height - 2 * ry} - a ${rx},${ry} 0 0 1 ${-rx},${ry} - h ${2 * rx - width} - a ${rx},${ry} 0 0 1 ${-rx},${-ry} - v ${2 * ry - height} - a ${rx},${ry} 0 0 1 ${rx},${-ry} - Z - ` : ` - M ${x}, ${y} - h ${width} - v ${height} - h ${-width} - Z - `; + static contextTypes = { + ...rectProps, + isInGroup: PropTypes.bool }; - render() { - let r = getR(this.props); - return ; - } + constructor() { + super(...arguments); + this.type = RECT; + }; + + } export default Rect; diff --git a/elements/Shape.js b/elements/Shape.js index 4446b51a..6e5b755b 100644 --- a/elements/Shape.js +++ b/elements/Shape.js @@ -3,32 +3,43 @@ import React, { PropTypes } from 'react-native'; import Path from './Path'; +import _ from 'lodash'; import extractProps from '../lib/extract/extractProps'; import {ShapeAttributes} from '../lib/attributes'; +import SerializableShape from '../lib/SerializableShape'; import createReactNativeComponentClass from 'react-native/Libraries/ReactNative/createReactNativeComponentClass'; +import {circleProps, ellipseProps, lineProps, rectProps} from '../lib/props'; /** * Circle shape type */ const CIRCLE = 0; +/** + * ELLIPSE shape type + */ +const ELLIPSE = 1; +/** + * LINE shape type + */ +const LINE = 2; +/** + * RECT shape type + */ +const RECT = 3; /** * coord props - * - * algorithm for radius in percentage - * radius = Math.sqrt(Math.pow((width*percent), 2) + Math.pow((height*percent), 2)) / Math.sqrt(2); */ -const CIRCLE_COORDS = ['cx', 'cy', 'r']; - -const ELLIPSE = 1; -const ELLIPSE_COORDS = ['cx', 'cy', 'rx', 'ry']; - -const LINE = 2; -const LINE_COORDS = ['x1', 'y1', 'x2', 'y2']; - -const RECT = 5; -const RECT_COORDS = ['x', 'y', 'width', 'height']; - +const COORD_PROPS = { + /** + * algorithm for radius in percentage + * radius = Math.sqrt(Math.pow((width*percent), 2) + Math.pow((height*percent), 2)) / Math.sqrt(2); + */ + [CIRCLE]: Object.keys(circleProps), + [ELLIPSE]: Object.keys(ellipseProps), + [LINE]: Object.keys(lineProps), + [RECT]: Object.keys(rectProps) +}; /** * virtualNode component for shape elements @@ -37,14 +48,29 @@ class Shape extends Component{ static displayName = 'Shape'; render() { - let {props} = this; + let props = this.props; + + if (this.context.isInGroup) { + props = _.defaults(this.context, props, { + isInGroup: null + }); + } + + let shape = new SerializableShape(props, COORD_PROPS[this.type]).toJSON(); return ; - } + }; } let NativePath = createReactNativeComponentClass({ @@ -53,3 +79,10 @@ let NativePath = createReactNativeComponentClass({ }); export default Shape; + +export { + CIRCLE, + ELLIPSE, + LINE, + RECT +}; diff --git a/elements/Text.js b/elements/Text.js index 27a5ee46..e4a247d0 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -6,7 +6,7 @@ import createReactNativeComponentClass from 'react-native/Libraries/ReactNative/ import extractProps from '../lib/extract/extractProps'; import extractText from '../lib/extract/extractText'; import {TextAttributes} from '../lib/attributes'; -import numberProp from '../lib/numberProp'; +import {numberProp} from '../lib/props'; class Text extends Component{ static displayName = 'Text'; diff --git a/elements/Use.js b/elements/Use.js index 10eb27d6..8860f755 100644 --- a/elements/Use.js +++ b/elements/Use.js @@ -1,7 +1,6 @@ import React, { Component, - PropTypes, - cloneElement + PropTypes } from 'react-native'; import Defs from './Defs'; class Use extends Component{ diff --git a/ios/RCTConvert+RNSVG.m b/ios/RCTConvert+RNSVG.m index eadaf585..1348630d 100644 --- a/ios/RCTConvert+RNSVG.m +++ b/ios/RCTConvert+RNSVG.m @@ -20,14 +20,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) { @@ -60,7 +60,7 @@ CGPathRelease(path); return NULL; } - + return (CGPathRef)CFAutorelease(path); } @@ -84,25 +84,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; @@ -110,18 +110,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; } @@ -129,11 +129,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. @@ -142,7 +142,7 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ array.array[i] = [arr[i] doubleValue]; } } - + return array; } @@ -150,7 +150,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. diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index e330791f..ec2ad54f 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 10A062FF1CC732020000CEEF /* RNSVGSvgViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A062FD1CC732020000CEEF /* RNSVGSvgViewManager.m */; }; 10A063041CC7320C0000CEEF /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063011CC7320C0000CEEF /* RNSVGPath.m */; }; 10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */; }; + 10C068671CCF0F87007C6982 /* RNSVGShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068651CCF0F87007C6982 /* RNSVGShape.m */; }; + 10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -80,6 +82,10 @@ 10A063011CC7320C0000CEEF /* RNSVGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGPath.m; sourceTree = ""; }; 10A063021CC7320C0000CEEF /* RNSVGSvgView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSvgView.h; sourceTree = ""; }; 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSvgView.m; sourceTree = ""; }; + 10C068641CCF0F87007C6982 /* RNSVGShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShape.h; sourceTree = SOURCE_ROOT; }; + 10C068651CCF0F87007C6982 /* RNSVGShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGShape.m; sourceTree = SOURCE_ROOT; }; + 10C068681CCF1061007C6982 /* RNSVGShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShapeManager.h; sourceTree = ""; }; + 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGShapeManager.m; sourceTree = ""; }; 10FEAC6A1CC7D05200F1C23C /* RNSVGCGFCRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGCGFCRule.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -104,10 +110,12 @@ 0CF68ADC1AF0549300FF9E5C /* RNSVGContainer.h */, 0CF68ADD1AF0549300FF9E5C /* RNSVGGroup.h */, 0CF68ADE1AF0549300FF9E5C /* RNSVGGroup.m */, - 0CF68ADF1AF0549300FF9E5C /* RNSVGNode.h */, - 0CF68AE01AF0549300FF9E5C /* RNSVGNode.m */, + 10C068641CCF0F87007C6982 /* RNSVGShape.h */, + 10C068651CCF0F87007C6982 /* RNSVGShape.m */, 10A063001CC7320C0000CEEF /* RNSVGPath.h */, 10A063011CC7320C0000CEEF /* RNSVGPath.m */, + 0CF68ADF1AF0549300FF9E5C /* RNSVGNode.h */, + 0CF68AE01AF0549300FF9E5C /* RNSVGNode.m */, 10A063021CC7320C0000CEEF /* RNSVGSvgView.h */, 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */, 0CF68AE11AF0549300FF9E5C /* RNSVGRenderable.h */, @@ -149,6 +157,8 @@ 0CF68AF81AF0549300FF9E5C /* ViewManagers */ = { isa = PBXGroup; children = ( + 10C068681CCF1061007C6982 /* RNSVGShapeManager.h */, + 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */, 10A062FA1CC732020000CEEF /* RNSVGPathManager.h */, 10A062FB1CC732020000CEEF /* RNSVGPathManager.m */, 10A062FC1CC732020000CEEF /* RNSVGSvgViewManager.h */, @@ -220,6 +230,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 10C068671CCF0F87007C6982 /* RNSVGShape.m in Sources */, 0CF68B161AF0549300FF9E5C /* RNSVGTextManager.m in Sources */, 10A063041CC7320C0000CEEF /* RNSVGPath.m in Sources */, 0CF68B111AF0549300FF9E5C /* RNSVGGroupManager.m in Sources */, @@ -233,6 +244,7 @@ 10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */, 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, 0CF68B101AF0549300FF9E5C /* RCTConvert+RNSVG.m in Sources */, + 10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */, 10A062FE1CC732020000CEEF /* RNSVGPathManager.m in Sources */, 0CF68B061AF0549300FF9E5C /* RNSVGNode.m in Sources */, 0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColor.m in Sources */, diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 4627ffdd..40c64830 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -18,8 +18,8 @@ @interface RNSVGNode : UIView +@property (nonatomic, assign) CGRect rect; @property (nonatomic, assign) CGFloat opacity; - @property (nonatomic, assign) CGPathRef clipPath; @property (nonatomic, assign) RNSVGCGFCRule clipRule; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 94f59c84..533c601c 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -80,7 +80,6 @@ _clipPath = CGPathRetain(clipPath); } - - (void)dealloc { CGPathRelease(_clipPath); diff --git a/ios/RNSVGShape.h b/ios/RNSVGShape.h new file mode 100644 index 00000000..17a28727 --- /dev/null +++ b/ios/RNSVGShape.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 + +#import "RNSVGRenderable.h" +#import "RNSVGPath.h" + +@interface RNSVGShape : RNSVGPath +@property (nonatomic, strong) NSDictionary* shape; + +@end diff --git a/ios/RNSVGShape.m b/ios/RNSVGShape.m new file mode 100644 index 00000000..ca28f342 --- /dev/null +++ b/ios/RNSVGShape.m @@ -0,0 +1,115 @@ +/** + * 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 "RNSVGShape.h" +#import "RCTLog.h" + +@implementation RNSVGShape + +- (void)dealloc +{ + +} + +- (void)renderLayerTo:(CGContextRef)context +{ + int type = [[self.shape objectForKey:@"type"] intValue]; + CGRect box = CGContextGetClipBoundingBox(context); + CGMutablePathRef path = CGPathCreateMutable(); + + float height = CGRectGetHeight(box); + float width = CGRectGetWidth(box); + switch (type) { + case 0: + { + // draw circle + CGFloat cx = [self getActualProp:@"cx" relative:width]; + CGFloat cy = [self getActualProp:@"cy" relative:height]; + CGFloat r = [self getActualProp:@"r" relative:height]; + CGPathAddArc(path, nil, cx, cy, r, 0, 2*M_PI, YES); + break; + } + case 1: + { + // draw ellipse + CGFloat cx = [self getActualProp:@"cx" relative:width]; + CGFloat cy = [self getActualProp:@"cy" relative:height]; + CGFloat rx = [self getActualProp:@"rx" relative:height]; + CGFloat ry = [self getActualProp:@"ry" relative:height]; + CGPathAddEllipseInRect(path, nil, CGRectMake(cx - rx / 2, cy - ry / 2, rx * 2, ry * 2)); + break; + } + case 2: + { + // draw line + CGFloat x1 = [self getActualProp:@"x1" relative:height]; + CGFloat y1 = [self getActualProp:@"y1" relative:height]; + CGFloat x2 = [self getActualProp:@"x2" relative:height]; + CGFloat y2 = [self getActualProp:@"y2" relative:height]; + CGPathMoveToPoint(path, nil, x1, y1); + CGPathAddLineToPoint(path, nil, x2, y2); + break; + } + case 3: + { + // draw rect + CGPathMoveToPoint(path, NULL, 0, 0); + CGFloat x = [self getActualProp:@"x" relative:width]; + CGFloat y = [self getActualProp:@"y" relative:height]; + CGFloat w = [self getActualProp:@"width" relative:width]; + CGFloat h = [self getActualProp:@"height" relative:height]; + CGFloat rx = [self getActualProp:@"rx" relative:width]; + CGFloat ry = [self getActualProp:@"ry" relative:height]; + + + if (rx != 0 || ry != 0) { + if (rx == 0) { + rx = ry; + } else if (ry == 0) { + ry = rx; + } + + if (rx > w / 2) { + rx = w / 2; + } + + if (ry > h / 2) { + ry = h / 2; + } + + CGPathAddRoundedRect(path, nil, CGRectMake(x, y, w, h), rx, ry); + } else { + CGPathAddRect(path, nil, CGRectMake(x, y, w, h)); + } + break; + } + default: + RCTLogError(@"Invalid Shape type %d at %@", type, self.shape); + //CGPathRelease(path); + + + } + + self.d = path; + [super renderLayerTo:context]; + //NSLog(@"%@", NSStringFromCGRect(box)); + //NSLog(@"%@", self.shape); +} + +- (CGFloat)getActualProp:(NSString *)name relative:(float)relative +{ + NSDictionary *prop = [self.shape objectForKey:name]; + CGFloat value = [[prop objectForKey:@"value"] floatValue]; + if ([[prop objectForKey:@"percentage"] integerValue] == 1) { + return relative * value; + } else { + return value; + } +} + +@end diff --git a/ios/RNSVGSvgView.m b/ios/RNSVGSvgView.m index 4f789206..053b84c9 100644 --- a/ios/RNSVGSvgView.m +++ b/ios/RNSVGSvgView.m @@ -22,7 +22,7 @@ { CGContextRef context = UIGraphicsGetCurrentContext(); for (RNSVGNode *node in self.subviews) { - [node renderTo:context]; + [node renderTo:context]; } } diff --git a/ios/ViewManagers/RNSVGShapeManager.h b/ios/ViewManagers/RNSVGShapeManager.h new file mode 100644 index 00000000..70455e82 --- /dev/null +++ b/ios/ViewManagers/RNSVGShapeManager.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 "RNSVGRenderableManager.h" + +@interface RNSVGShapeManager : RNSVGRenderableManager + +@end diff --git a/ios/ViewManagers/RNSVGShapeManager.m b/ios/ViewManagers/RNSVGShapeManager.m new file mode 100644 index 00000000..80f5e545 --- /dev/null +++ b/ios/ViewManagers/RNSVGShapeManager.m @@ -0,0 +1,25 @@ +/** + * 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 "RNSVGShapeManager.h" + +#import "RNSVGShape.h" +#import "RCTConvert+RNSVG.h" + +@implementation RNSVGShapeManager + +RCT_EXPORT_MODULE() + +- (RNSVGRenderable *)node +{ + return [RNSVGShape new]; +} + +RCT_EXPORT_VIEW_PROPERTY(shape, NSDictionary) + +@end diff --git a/lib/SerializableShape.js b/lib/SerializableShape.js index fea01a8d..81f4a290 100644 --- a/lib/SerializableShape.js +++ b/lib/SerializableShape.js @@ -1,10 +1,55 @@ -export default class { - constructor(props) { +/** + * Format potential percentage props + * + * convert somet props like those + * width="50%" + * height="500" + * + * to + * { + * width: { + * percentage: true, + * value: 0.5 + * }, + * height: { + * percentage: false, + * value: 500 + * } + * } + * + * + */ +import percentToFloat from './percentToFloat'; +function percentageTransform(value) { + if (typeof value === 'number') { + return { + percentage: false, + value + }; } - toArray = () => { + let float = percentToFloat(value); + return { + percentage: float !== +value, + value: float + } +} + +export default class { + constructor(props, list) { + this.shape = {}; + list.forEach(name => { + if (props[name] != null) { + this.shape[name] = percentageTransform(props[name]); + } + + }); + } + + toJSON = () => { + return this.shape; }; }; diff --git a/lib/attributes.js b/lib/attributes.js index 9e12d216..cc4623e2 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -6,7 +6,7 @@ function arrayDiffer(a, b) { if (a.length !== b.length) { return true; } - for (var i = 0; i < a.length; i++) { + for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return true; } @@ -14,6 +14,22 @@ function arrayDiffer(a, b) { return false; } +function shapeDiffer(a, b) { + if (a === b) { + return false; + } + for (let key in a) { + if (a.hasOwnProperty(key)) { + if (key === 'type' && a.type !== b.type) { + return true; + } else if (a[key].percentage !== b[key].percentage || a[key].value !== b[key].value) { + return true; + } + } + } + return false; +} + function fontAndLinesDiffer(a, b) { if (a === b) { return false; @@ -92,7 +108,7 @@ const TextAttributes = { const ShapeAttributes = { shape: { - diff: arrayDiffer + diff: shapeDiffer }, ...RenderableAttributes }; diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 1ad1ae0f..3ba752a8 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -75,7 +75,7 @@ function extractFont(font) { }; } -const alignments = { +const anchord = { right: 1, center: 2, left: 0 @@ -83,7 +83,7 @@ const alignments = { export default function(props) { return { - alignment: alignments[props.textAnchor] || 0, + alignment: anchord[props.textAnchor] || 0, frame: extractFontAndLines( props, childrenAsString(props.children) diff --git a/lib/props.js b/lib/props.js new file mode 100644 index 00000000..0d77fbb3 --- /dev/null +++ b/lib/props.js @@ -0,0 +1,103 @@ +import { + PropTypes +} from 'react-native'; + +const numberProp = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + +const fillProps = { + fill: PropTypes.string, + fillOpacity: numberProp, + fillRule: PropTypes.oneOf(['evenodd', 'nonzero']) +}; + +const clipProps = { + clipRule: PropTypes.oneOf(['evenodd', 'nonzero']), + clipPath: PropTypes.string +}; + +const strokeProps = { + stroke: PropTypes.string, + strokeWidth: numberProp, + strokeOpacity: numberProp, + strokeDasharray: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.string]), + strokeDashoffset: numberProp, + strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), + strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']) +}; + +const textProps = { + textAnchor: PropTypes.oneOf(['right', 'center', 'left']), + path: PropTypes.string +}; + +const transformProps = { + scale: numberProp, + scaleX: numberProp, + scaleY: numberProp, + rotate: numberProp, + x: numberProp, + y: numberProp, + originX: numberProp, + originY: numberProp, + transform: PropTypes.string +}; + +const pathProps = { + ...fillProps, + ...strokeProps, + ...clipProps, + ...transformProps +}; + +const circleProps = { + cx: numberProp, + cy: numberProp, + r: numberProp +}; + +const ellipseProps = { + cx: numberProp, + cy: numberProp, + rx: numberProp, + ry: numberProp +}; + +const lineProps = { + x1: numberProp, + x2: numberProp, + y1: numberProp, + y2: numberProp +}; + +const rectProps = { + x: numberProp, + y: numberProp, + width: numberProp, + height: numberProp, + rx: numberProp, + ry: numberProp +}; + +const shapeProps = { + ...circleProps, + ...ellipseProps, + ...lineProps, + ...rectProps, + ...fillProps, + ...strokeProps +}; + +export { + numberProp, + fillProps, + clipProps, + strokeProps, + transformProps, + textProps, + circleProps, + ellipseProps, + lineProps, + rectProps, + shapeProps, + pathProps +}