refactor Use and Defs element with native code support(iOS)

This commit is contained in:
Horcrux
2016-07-19 23:09:51 +08:00
parent 4eddfc6885
commit dd6cb80e84
37 changed files with 535 additions and 282 deletions

View File

@@ -14,24 +14,9 @@ import * as Symbol from './examples/Symbol';
import * as Gradients from './examples/Gradients';
import * as Clipping from './examples/Clipping';
import * as Image from './examples/Image';
import * as Definations from './examples/Definations';
import * as TouchEvents from './examples/TouchEvents';
export {
Svg,
Rect,
Circle,
Ellipse,
Line,
Polygon,
Polyline,
Path,
Text,
Stroking,
G,
Use,
Symbol,
Gradients,
Clipping,
Image,
TouchEvents
Definations
};

View File

@@ -0,0 +1,51 @@
import React, {
Component
} from 'react';
import Svg, {
Defs,
G,
Path,
Use,
Rect
} from 'react-native-svg';
class DefsExample extends Component{
static title = 'basic Defs usage';
render() {
return <Svg
height="100"
width="100"
>
<Defs>
<G id="path" x="5" y="2">
<Path id="test" fill='red' d="M38.459,1.66A0.884,0.884,0,0,1,39,2.5a0.7,0.7,0,0,1-.3.575L23.235,16.092,27.58,26.1a1.4,1.4,0,0,1,.148.3,1.3,1.3,0,0,1,0,.377,1.266,1.266,0,0,1-2.078.991L15.526,20.6l-7.58,4.35a1.255,1.255,0,0,1-.485,0,1.267,1.267,0,0,1-1.277-1.258q0-.01,0-0.02a1.429,1.429,0,0,1,0-.446C7.243,20.253,8.6,16.369,8.6,16.29L3.433,13.545A0.743,0.743,0,0,1,2.9,12.822a0.822,0.822,0,0,1,.623-0.773l8.164-2.972,3.018-8.5A0.822,0.822,0,0,1,15.427,0a0.752,0.752,0,0,1,.752.555l2.563,6.936S37.65,1.727,37.792,1.685A1.15,1.15,0,0,1,38.459,1.66Z"/>
</G>
</Defs>
<Use href="url(#path)" x="0" fill="blue" />
<Use href="url(#path)" x="10" fill="#3a8" />
</Svg>;
}
}
const icon = <Svg
height="20"
width="20"
>
<Defs>
<G id="path">
<Path fill='red' d="M38.459,1.66A0.884,0.884,0,0,1,39,2.5a0.7,0.7,0,0,1-.3.575L23.235,16.092,27.58,26.1a1.4,1.4,0,0,1,.148.3,1.3,1.3,0,0,1,0,.377,1.266,1.266,0,0,1-2.078.991L15.526,20.6l-7.58,4.35a1.255,1.255,0,0,1-.485,0,1.267,1.267,0,0,1-1.277-1.258q0-.01,0-0.02a1.429,1.429,0,0,1,0-.446C7.243,20.253,8.6,16.369,8.6,16.29L3.433,13.545A0.743,0.743,0,0,1,2.9,12.822a0.822,0.822,0,0,1,.623-0.773l8.164-2.972,3.018-8.5A0.822,0.822,0,0,1,15.427,0a0.752,0.752,0,0,1,.752.555l2.563,6.936S37.65,1.727,37.792,1.685A1.15,1.15,0,0,1,38.459,1.66Z"/>
</G>
</Defs>
<Use href="url(#path)" x="10" fill="#3a8" />
</Svg>;
const samples = [DefsExample];
export {
icon,
samples
};

View File

@@ -111,7 +111,8 @@ const styles = StyleSheet.create({
}
});
const names = ['Svg', 'Stroking', 'Path', 'Line', 'Rect', 'Polygon', 'Polyline', 'Circle', 'Ellipse', 'G', 'Text', 'Use', 'Symbol', 'Gradients', 'Clipping', 'Image', 'TouchEvents'];
//const names = ['Svg', 'Stroking', 'Path', 'Line', 'Rect', 'Polygon', 'Polyline', 'Circle', 'Ellipse', 'G', 'Text', 'Use', 'Symbol', 'Gradients', 'Clipping', 'Image', 'TouchEvents'];
const names = ['Definations'];
class SvgExample extends Component {
constructor() {

View File

@@ -15,9 +15,7 @@ class Circle extends Shape {
static contextTypes = {
...fillProps,
...strokeProps,
...circleProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...circleProps
};
setNativeProps = (...args) => {

View File

@@ -1,102 +1,19 @@
import React, {Children, Component, cloneElement, PropTypes} from 'react';
import {NativeGroup} from './G';
let map = {};
import React, {
Component,
} from 'react'
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
import LinearGradient from './LinearGradient';
import RadialGradient from './RadialGradient';
import ClipPath from './ClipPath';
let onlyChild = Children.only;
class DefsItem extends Component{
static displayName = 'DefsItem';
static propType = {
visible: PropTypes.bool
};
static defaultProps = {
visible: false
};
constructor() {
super(...arguments);
this.id = this.props.id + ':' + this.props.svgId;
map[this.id] = cloneElement(onlyChild(this.props.children), {
id: null
});
}
componentWillReceiveProps = nextProps => {
let id = nextProps.id + ':' + nextProps.svgId;
if (id !== this.id) {
delete map[this.id];
}
map[id] = cloneElement(onlyChild(nextProps.children), {
id: null
});
};
componentWillUnmount = () => {
delete map[this.id];
};
render() {
return this.props.visible ? onlyChild(this.props.children) : <NativeGroup />;
}
}
let idReg = /^#(.+)/;
class DefsUse extends Component{
static displayName = 'DefsUse';
static propType = {
href: PropTypes.string
};
render() {
let href = this.props.href;
if (href) {
let matched = href.match(idReg);
if (matched) {
let template = map[matched[1] + ':' + this.props.svgId];
if (template) {
return cloneElement(template, {
...this.props,
href: null
});
}
}
}
console.warn(`Invalid href: '${href}' for Use element.\n Please check if '${href}' if defined`);
return <NativeGroup />;
}
}
// TODO: defination scope, global or local?
class Defs extends Component{
class Defs extends Component {
static displayName = 'Defs';
static Item = DefsItem;
static Use = DefsUse;
getChildren = () => {
return Children.map(this.props.children, child => {
let {type} = child;
if (type === LinearGradient || type === RadialGradient || type === ClipPath) {
return cloneElement(child, {
svgId: this.props.svgId
});
}
if (child.props.id) {
return <DefsItem
{...child.props}
svgId={this.props.svgId}
>{child}</DefsItem>;
}
});
};
render() {
return <NativeGroup>{this.getChildren()}</NativeGroup>;
return <RNSVGDefination>{this.props.children}</RNSVGDefination>;
}
}
const RNSVGDefination = createReactNativeComponentClass({
validAttributes: {},
uiViewClassName: 'RNSVGDefination'
});
export default Defs;

View File

@@ -15,9 +15,7 @@ class Ellipse extends Shape{
static contextTypes = {
...fillProps,
...strokeProps,
...ellipseProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...ellipseProps
};
setNativeProps = (...args) => {

View File

@@ -1,5 +1,4 @@
import React, {Component, PropTypes} from 'react';
import Defs from './Defs';
import _ from 'lodash';
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
import {numberProp, contextProps} from '../lib/props';
@@ -10,13 +9,10 @@ class G extends Component{
static displayName = 'G';
static contextTypes = {
svgId: numberProp,
...contextProps
};
static childContextTypes = {
svgId: numberProp,
isInGroup: PropTypes.bool,
...contextProps
};
@@ -26,44 +22,24 @@ class G extends Component{
props[key] = this.props[key];
}
return props;
}, {
svgId: this.props.svgId || this.context.svgId,
isInGroup: true
});
}, {});
return _.defaults({}, this.context, context);
};
setNativeProps = (...args) => {
this.getNativeElement().setNativeProps(...args);
};
getNativeElement = () => {
return this.refs.root || this.root;
this.root.setNativeProps(...args);
};
render() {
if (this.props.id) {
return <Defs.Item
id={this.props.id}
svgId={this.props.svgId}
visible={true}
>
<G
{...this.props}
ref={ele => this.root = ele.refs.root}
id={null}
/>
</Defs.Item>;
} else {
return <RNSVGGroup
{...extractProps(this.props, {transform: true})}
ref="root"
ref={ele => this.root = ele}
asClipPath={this.props.asClipPath}
>
{this.props.children}
</RNSVGGroup>;
}
}
}
const RNSVGGroup = createReactNativeComponentClass({

View File

@@ -15,9 +15,7 @@ class Line extends Shape {
static contextTypes = {
...fillProps,
...strokeProps,
...lineProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...lineProps
};
setNativeProps = (...args) => {

View File

@@ -1,5 +1,4 @@
import React, {PropTypes} from 'react';
import Defs from './Defs';
import SerializablePath from '../lib/SerializablePath';
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
import {PathAttributes} from '../lib/attributes';
@@ -16,9 +15,7 @@ class Path extends Shape {
};
static contextTypes = {
...pathProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...pathProps
};
@@ -31,34 +28,16 @@ class Path extends Shape {
};
setNativeProps = (...args) => {
this.getNativeElement().setNativeProps(...args);
};
getNativeElement = () => {
return this.refs.root || this.root;
this.root.setNativeProps(...args);
};
render() {
let props = mergeContext(this.props, this.context);
if (props.id) {
return <Defs.Item
id={props.id}
svgId={props.svgId}
visible={true}
>
<Path
ref={ele => this.root = ele.refs.root}
{...props}
id={null}
/>
</Defs.Item>;
}
let d = new SerializablePath(props.d).toJSON();
return (
<RNSVGPath
ref="root"
ref={ele => this.root = ele}
{...this.extractProps(props)}
d={d}
/>

View File

@@ -16,9 +16,7 @@ class Rect extends Shape {
static contextTypes = {
...fillProps,
...strokeProps,
...rectProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...rectProps
};
setNativeProps = (...args) => {

View File

@@ -12,8 +12,8 @@ class Shape extends Component {
this.state = this.touchableGetInitialState();
}
extractProps = (props) => {
let extractedProps = extractProps(props);
extractProps = (props, options = {stroke: true, fill: true, responder: true}) => {
let extractedProps = extractProps(props, options);
if (extractedProps.touchable && !extractedProps.disabled) {
_.assign(extractedProps, {
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,

View File

@@ -20,17 +20,11 @@ class Text extends Shape {
static contextTypes = {
...textProps,
...fillProps,
...strokeProps,
isInGroup: PropTypes.bool,
svgId: numberProp
...strokeProps
};
setNativeProps = (...args) => {
this.getNativeElement().setNativeProps(...args);
};
getNativeElement = () => {
return this.refs.root || this.root;
this.root.setNativeProps(...args);
};
render() {
@@ -45,21 +39,9 @@ class Text extends Shape {
y = props.dy ? +props.y + (+props.dy) : +props.y;
}
if (this.props.id) {
return <Defs.Item
ref={ele => this.root = ele.refs.root}
id={this.props.id}
svgId={this.props.svgId}
visible={true}
text={true}
>
<Text {...this.props} id={null} />
</Defs.Item>;
}
return (
<RNSVGText
ref="root"
ref={ele => this.root = ele}
{...extractProps({...props, x, y})}
{...extractText(props)}
/>

View File

@@ -1,14 +1,54 @@
import React, {Component, PropTypes} from 'react';
import Defs from './Defs';
class Use extends Component{
import {PropTypes} from 'react';
import {pathProps} from '../lib/props';
import {UseAttributes} from '../lib/attributes';
import Shape from './Shape';
import React from 'react';
import patternReg from '../lib/extract/patternReg';
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
class Defs extends Shape {
static displayName = 'Use';
static propType = {
href: PropTypes.string
static propTypes = {
href: PropTypes.string.isRequired,
...pathProps
};
setNativeProps = (...args) => {
this.root.setNativeProps(...args);
};
render() {
return <Defs.Use {...this.props} />;
let {props} = this;
// 尝试匹配 "url(#pattern)"
let matched = props.href.match(patternReg);
let href;
if (matched) {
href = matched[1];
}
if (!href) {
console.warn('Invalid `href` prop for `Use` element, expected a href like `"url(#id)"`, but got: "' + props.href + '"');
}
return <RNSVGUse
ref={ele => this.root = ele}
{...this.extractProps(props, {
stroke: true,
fill: true,
responder: true,
transform: true
})}
href={href}
>{props.children}</RNSVGUse>;
}
}
export default Use;
const RNSVGUse = createReactNativeComponentClass({
validAttributes: UseAttributes,
uiViewClassName: 'RNSVGUse'
});
export default Defs;

View File

@@ -13,7 +13,6 @@
#import "RNSVGSvgView.h"
@interface RNSVGClipPath : RNSVGGroup
@property (nonatomic, strong) NSString *name;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

View File

@@ -10,16 +10,23 @@
@implementation RNSVGClipPath
- (void)renderLayerTo:(CGContextRef)context
- (void)saveDefination:(CGContextRef)context
{
[[self getSvgView] defineClipPath:[self getPath:context] clipPathRef:self.name];
}
// hitTest delagate
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
- (void)removeDefination
{
if (self.name) {
[[self getSvgView] removeClipPath: self.name];
}
}
@end

View File

@@ -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 "RNSVGNode.h"
/**
* RNSVG defination are implemented as abstract UIViews for all elements inside Defs.
*/
@interface RNSVGDefination : RNSVGNode
- (void)renderTo:(CGContextRef)context;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
@end

View File

@@ -0,0 +1,27 @@
/**
* 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 "RNSVGDefination.h"
@class RNSVGNode;
@implementation RNSVGDefination
- (void)renderTo:(CGContextRef)context
{
for (RNSVGNode *node in self.subviews) {
[node saveDefination: context];
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
@end

View File

@@ -7,6 +7,7 @@
*/
#import "RNSVGGroup.h"
#import <objc/runtime.h>
@implementation RNSVGGroup
@@ -35,17 +36,39 @@
}
// hitTest delagate
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) {
UIView *view = [node hitTest: point withEvent:event];
if (view != NULL) {
if (view) {
return view;
}
}
return nil;
}
- (void)saveDefination:(CGContextRef)context
{
if (self.name) {
RNSVGSvgView* svg = [self getSvgView];
[svg defineTemplate:self templateRef:self.name];
}
for (RNSVGNode *node in self.subviews) {
[node saveDefination:context];
}
}
- (void)willRemoveSubview:(UIView *)subview
{
[super willRemoveSubview:subview];
}
- (void)mergeProperties:(__kindof RNSVGNode *)target
{
for (RNSVGNode *node in self.subviews) {
[node mergeProperties:target];
}
}
@end

View File

@@ -10,6 +10,8 @@
#import "RNSVGContainer.h"
@class RNSVGNode;
@interface RNSVGSvgView : UIView <RNSVGContainer>
@property (nonatomic, assign) BOOL responsible;
@@ -17,10 +19,16 @@
/**
* define <ClipPath></ClipPath> content as clipPath template.
*/
- (void)defineClipPath:(CGPathRef)clipPath clipPathRef:(NSString *)clipPathId;
- (void)defineClipPath:(CGPathRef)clipPath clipPathRef:(NSString *)clipPathRef;
- (void)removeClipPath:(NSString *)clipPathRef;
- (CGPathRef)getDefinedClipPath:(NSString *)clipPathRef;
- (void)defineTemplate:(RNSVGNode *)template templateRef:(NSString *)templateRef;
- (void)removeTemplate:(NSString *)tempalteRef;
- (RNSVGNode *)getDefinedTemplate:(NSString *)tempalteRef;
@end

View File

@@ -13,7 +13,8 @@
@implementation RNSVGSvgView
{
NSMutableDictionary *clipPaths;
NSMutableDictionary<NSString *, NSValue *> *clipPaths;
NSMutableDictionary<NSString *, RNSVGNode *> *templates;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
@@ -44,6 +45,7 @@
CGContextRef context = UIGraphicsGetCurrentContext();
for (RNSVGNode *node in self.subviews) {
[node saveDefination:context];
[node renderTo:context];
if (node.responsible && !self.responsible) {
@@ -64,7 +66,7 @@
- (void)defineClipPath:(CGPathRef)clipPath clipPathRef:(NSString *)clipPathRef
{
if (clipPaths == NULL) {
if (!clipPaths) {
clipPaths = [[NSMutableDictionary alloc] init];
}
[clipPaths setValue:[NSValue valueWithPointer:clipPath] forKey:clipPathRef];
@@ -72,14 +74,34 @@
- (void)removeClipPath:(NSString *)clipPathRef
{
if (clipPaths != NULL) {
if (clipPaths) {
[clipPaths removeObjectForKey:clipPathRef];
}
}
- (CGPathRef)getDefinedClipPath:(NSString *)clipPathRef
{
return [[clipPaths valueForKey:clipPathRef] pointerValue];
return clipPaths ? [[clipPaths valueForKey:clipPathRef] pointerValue] : nil;
}
- (void)defineTemplate:(RNSVGNode *)template templateRef:(NSString *)templateRef
{
if (!templates) {
templates = [[NSMutableDictionary alloc] init];
}
[templates setObject:template forKey:templateRef];
}
- (void)removeTemplate:(NSString *)tempalteRef
{
if (templates) {
[templates removeObjectForKey:tempalteRef];
}
}
- (RNSVGNode *)getDefinedTemplate:(NSString *)tempalteRef
{
return templates ? [templates objectForKey:tempalteRef] : nil;
}
@end

19
ios/Elements/RNSVGUse.h Normal file
View File

@@ -0,0 +1,19 @@
/**
* 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 "RNSVGRenderable.h"
/**
* RNSVG defination are implemented as abstract UIViews for all elements inside Defs.
*/
@interface RNSVGUse : RNSVGRenderable
@property (nonatomic, strong) NSString *href;
@end

26
ios/Elements/RNSVGUse.m Normal file
View File

@@ -0,0 +1,26 @@
/**
* 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 "RNSVGUse.h"
#import "RCTLog.h"
@implementation RNSVGUse
- (void)renderLayerTo:(CGContextRef)context
{
RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href];
if (template) {
[template mergeProperties:self];
[template renderTo:context];
} else if (self.href) {
// TODO: calling yellow box here
RCTLogWarn(@"`Use` element expected a pre-defined svg template as `href` prop, template named: %@ is not defined.", self.href);
}
}
@end

View File

@@ -13,6 +13,10 @@
0CF68B0D1AF0549300FF9E5C /* RNSVGPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* RNSVGPattern.m */; };
0CF68B0E1AF0549300FF9E5C /* RNSVGRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* RNSVGRadialGradient.m */; };
0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* RNSVGSolidColor.m */; };
1023B48D1D3DDCCE0051496D /* RNSVGDefinationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48C1D3DDCCE0051496D /* RNSVGDefinationManager.m */; };
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 */; };
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 */; };
@@ -68,6 +72,14 @@
0CF68AF21AF0549300FF9E5C /* RNSVGRadialGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGRadialGradient.m; sourceTree = "<group>"; };
0CF68AF31AF0549300FF9E5C /* RNSVGSolidColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSolidColor.h; sourceTree = "<group>"; };
0CF68AF41AF0549300FF9E5C /* RNSVGSolidColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSolidColor.m; sourceTree = "<group>"; };
1023B48B1D3DDCCE0051496D /* RNSVGDefinationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGDefinationManager.h; sourceTree = "<group>"; };
1023B48C1D3DDCCE0051496D /* RNSVGDefinationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGDefinationManager.m; sourceTree = "<group>"; };
1023B48E1D3DF4C40051496D /* RNSVGDefination.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGDefination.h; path = Elements/RNSVGDefination.h; sourceTree = "<group>"; };
1023B48F1D3DF4C40051496D /* RNSVGDefination.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGDefination.m; path = Elements/RNSVGDefination.m; sourceTree = "<group>"; };
1023B4911D3DF5060051496D /* RNSVGUse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGUse.h; path = Elements/RNSVGUse.h; sourceTree = "<group>"; };
1023B4921D3DF5060051496D /* RNSVGUse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGUse.m; path = Elements/RNSVGUse.m; sourceTree = "<group>"; };
1023B4941D3DF57D0051496D /* RNSVGUseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGUseManager.h; sourceTree = "<group>"; };
1023B4951D3DF57D0051496D /* RNSVGUseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGUseManager.m; sourceTree = "<group>"; };
1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGGroup.h; path = Elements/RNSVGGroup.h; sourceTree = "<group>"; };
1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGroup.m; path = Elements/RNSVGGroup.m; sourceTree = "<group>"; };
1039D2831CE71EB7001E90A8 /* RNSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGImage.h; path = Elements/RNSVGImage.h; sourceTree = "<group>"; };
@@ -185,6 +197,10 @@
0CF68AF81AF0549300FF9E5C /* ViewManagers */ = {
isa = PBXGroup;
children = (
1023B4941D3DF57D0051496D /* RNSVGUseManager.h */,
1023B4951D3DF57D0051496D /* RNSVGUseManager.m */,
1023B48B1D3DDCCE0051496D /* RNSVGDefinationManager.h */,
1023B48C1D3DDCCE0051496D /* RNSVGDefinationManager.m */,
10ED4A9C1CF0656A0078BC02 /* RNSVGClipPathManager.h */,
10ED4A9D1CF0656A0078BC02 /* RNSVGClipPathManager.m */,
10BA0D1C1CE74E3100887C2B /* RNSVGCircleManager.h */,
@@ -245,6 +261,10 @@
1039D2801CE71DCF001E90A8 /* Elements */ = {
isa = PBXGroup;
children = (
1023B4911D3DF5060051496D /* RNSVGUse.h */,
1023B4921D3DF5060051496D /* RNSVGUse.m */,
1023B48E1D3DF4C40051496D /* RNSVGDefination.h */,
1023B48F1D3DF4C40051496D /* RNSVGDefination.m */,
10ED4A991CF065260078BC02 /* RNSVGClipPath.h */,
10ED4A9A1CF065260078BC02 /* RNSVGClipPath.m */,
1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */,
@@ -336,6 +356,7 @@
10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */,
1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */,
0CF68B0D1AF0549300FF9E5C /* RNSVGPattern.m in Sources */,
1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */,
0CF68B0E1AF0549300FF9E5C /* RNSVGRadialGradient.m in Sources */,
1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */,
10BA0D3B1CE74E3100887C2B /* RNSVGRectManager.m in Sources */,
@@ -352,6 +373,7 @@
0CF68B0C1AF0549300FF9E5C /* RNSVGLinearGradient.m in Sources */,
10BA0D371CE74E3100887C2B /* RNSVGImageManager.m in Sources */,
10BA0D391CE74E3100887C2B /* RNSVGNodeManager.m in Sources */,
1023B4901D3DF4C40051496D /* RNSVGDefination.m in Sources */,
10BA0D381CE74E3100887C2B /* RNSVGLineManager.m in Sources */,
10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */,
10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */,
@@ -360,6 +382,8 @@
10BA0D361CE74E3100887C2B /* RNSVGGroupManager.m in Sources */,
10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */,
1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */,
1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */,
1023B48D1D3DDCCE0051496D /* RNSVGDefinationManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -11,21 +11,22 @@
#import "RNSVGSvgView.h"
/**
* RNSVG nodes are implemented as empty UIViews but this is just an implementation detail to fit
* into the existing view management. They should also be shadow views and painted on a background
* thread.
* RNSVG nodes are implemented as base UIViews. They should be implementation for all basic
interfaces for all non-defination nodes.
*/
@interface RNSVGNode : UIView
@property (nonatomic, assign) CGRect rect;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) CGFloat opacity;
@property (nonatomic, assign) RNSVGCGFCRule clipRule;
@property (nonatomic, assign) CGPathRef clipPath; // convert clipPath="M0,0 L0,10 L10,10z" into path
@property (nonatomic, strong) NSString *clipPathRef; // use clipPath="url(#clip)" as ClipPath
@property (nonatomic, assign) BOOL responsible;
- (void)invalidate;
- (void)renderTo:(CGContextRef)context;
/**
@@ -36,20 +37,41 @@
- (void)renderLayerTo:(CGContextRef)context;
/**
* clip node by clipPath or clipPathId.
* clip node by clipPath or clipPathRef.
*/
- (void)clip:(CGContextRef)context;
/**
* get clip path for current node.
*/
- (CGPathRef)getClipPath;
/**
* getPath will return the path inside node as a ClipPath.
*/
- (CGPathRef)getPath: (CGContextRef) context;
- (CGPathRef)getPath:(CGContextRef) context;
/**
* run hitTest
*/
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (RNSVGSvgView *)getSvgView;
/**
* save element`s defination into svg element.
*/
- (void)saveDefination:(CGContextRef)context;
/**
* remove element`s defination from svg element.
*/
- (void)removeDefination;
/**
* merge owned properties into target element`s properties
*/
- (void)mergeProperties:(__kindof RNSVGNode *)target;
@end

View File

@@ -30,6 +30,17 @@
// Do nothing, as subviews are inserted by insertReactSubview:
}
- (void)invalidate
{
id<RNSVGContainer> container = (id<RNSVGContainer>)self.superview;
[container invalidate];
}
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
{
self.backgroundColor = inheritedBackgroundColor;
}
- (void)setOpacity:(CGFloat)opacity
{
if (opacity == _opacity) {
@@ -52,12 +63,6 @@
super.transform = transform;
}
- (void)invalidate
{
id<RNSVGContainer> container = (id<RNSVGContainer>)self.superview;
[container invalidate];
}
- (void)renderTo:(CGContextRef)context
{
float opacity = self.opacity;
@@ -111,7 +116,7 @@
{
CGPathRef clipPath = [self getClipPath];
if (clipPath != NULL) {
if (clipPath) {
CGContextAddPath(context, [self getClipPath]);
if (self.clipRule == kRNSVGCGFCRuleEvenodd) {
CGContextEOClip(context);
@@ -121,33 +126,68 @@
}
}
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
{
self.backgroundColor = inheritedBackgroundColor;
}
- (void)renderLayerTo:(CGContextRef)context
{
// abstract
}
- (RNSVGSvgView *)getSvgView
{
UIView *parent = self.superview;
while ([parent class] != [RNSVGSvgView class]) {
parent = parent.superview;
}
return (RNSVGSvgView *)parent;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
{
// abstract
return nil;
}
- (RNSVGSvgView *)getSvgView
{
UIView *parent = self.superview;
while (parent && [parent class] != [RNSVGSvgView class]) {
parent = parent.superview;
}
return (RNSVGSvgView *)parent;
}
- (void)willRemoveFromSuperView
{
if (self.subviews) {
for (RNSVGNode *node in self.subviews) {
[node willRemoveFromSuperView];
}
}
[self removeDefination];
[super removeFromSuperview];
}
/**
* reverse removeFromSuperview calling order.
* calling it from subviews to superview.
*/
- (void)removeFromSuperview
{
[self willRemoveFromSuperView];
}
- (void)saveDefination:(CGContextRef)context
{
if (self.name) {
RNSVGSvgView* svg = [self getSvgView];
[svg defineTemplate:self templateRef:self.name];
}
}
- (void)removeDefination
{
if (self.name) {
[[self getSvgView] removeTemplate:self.name];
}
}
- (void)mergeProperties:(__kindof RNSVGNode *)target
{
self.opacity = target.opacity * self.opacity;
}
- (void)dealloc
{
CGPathRelease(_clipPath);

View File

@@ -101,8 +101,8 @@
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPathRef clipPath = [self getClipPath];
if (self.nodeArea != NULL && CGPathContainsPoint(self.nodeArea, nil, point, NO)) {
if (clipPath == NULL) {
if (self.nodeArea && CGPathContainsPoint(self.nodeArea, nil, point, NO)) {
if (!clipPath) {
return self;
} else {
return CGPathContainsPoint(clipPath, nil, point, NO) ? self : nil;
@@ -112,6 +112,40 @@
}
}
- (void)mergeProperties:(__kindof RNSVGNode *)target
{
RNSVGRenderable* renderableTarget = target;
if (renderableTarget.fill) {
self.fill = renderableTarget.fill;
}
if (renderableTarget.fillRule) {
self.fillRule = renderableTarget.fillRule;
}
if (renderableTarget.stroke) {
self.stroke = renderableTarget.stroke;
}
if (renderableTarget.strokeWidth) {
self.strokeWidth = renderableTarget.strokeWidth;
}
if (renderableTarget.strokeLinecap) {
self.strokeLinecap = renderableTarget.strokeLinecap;
}
if (renderableTarget.strokeLinejoin) {
self.strokeLinejoin = renderableTarget.strokeLinejoin;
}
if (renderableTarget.strokeMiterlimit) {
self.strokeMiterlimit = renderableTarget.strokeMiterlimit;
}
if (renderableTarget.strokeDasharray.count != 0) {
self.strokeDasharray = renderableTarget.strokeDasharray;
}
if (renderableTarget.strokeDashoffset) {
self.strokeDashoffset = renderableTarget.strokeDashoffset;
}
[super mergeProperties:target];
}
- (void)renderLayerTo:(CGContextRef)context
{
// abstract

View File

@@ -81,7 +81,7 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
}
// We should consider snapping this shift to device pixels to improve rendering quality
// when a line has subpixel width.
CGAffineTransform offset = CGAffineTransformMakeTranslation(-shift, frame.baseLine + frame.lineHeight * i + (self.path == NULL ? 0 : -frame.lineHeight));
CGAffineTransform offset = CGAffineTransformMakeTranslation(-shift, frame.baseLine + frame.lineHeight * i + (self.path ? -frame.lineHeight : 0));
CGPathAddPath(path, &offset, [self setLinePath:frame.lines[i]]);
}
@@ -115,11 +115,11 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) {
CGPathRef letter = [cache pathForGlyph:glyphs[j] fromFont:runFont];
CGPoint point = positions[j];
if (letter != NULL) {
if (letter) {
CGAffineTransform transform;
// draw glyphs along path
if (self.path != NULL) {
if (self.path) {
CGPoint slope;
CGRect bounding = CGPathGetBoundingBox(letter);
UIBezierPath* path = [UIBezierPath bezierPathWithCGPath:self.path];

View File

@@ -13,7 +13,7 @@
-(id)init
{
self = [super init];
if(self != nil)
if(self)
{
cache = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
}
@@ -29,7 +29,7 @@
{
// First we lookup the font to get to its glyph dictionary
CFMutableDictionaryRef glyphDict = (CFMutableDictionaryRef)CFDictionaryGetValue(cache, font);
if(glyphDict == NULL)
if(!glyphDict)
{
// And if this font hasn't been seen before, we'll create and set the dictionary for it
glyphDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
@@ -38,11 +38,11 @@
}
// Next we try to get a path for the given glyph from the glyph dictionary
CGPathRef path = (CGPathRef)CFDictionaryGetValue(glyphDict, (const void *)(uintptr_t)glyph);
if(path == NULL)
if(!path)
{
// If the path hasn't been seen before, then we'll create the path from the font & glyph and cache it.
path = CTFontCreatePathForGlyph(font, glyph, NULL);
if(path == NULL)
if(!path)
{
// If a glyph does not have a path, then we need a placeholder to set in the dictionary
path = (CGPathRef)kCFNull;

View File

@@ -56,7 +56,7 @@
- (BOOL) isPercentage:(NSString *) string
{
return [percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != NULL;
return [percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != nil;
}
@end

View File

@@ -0,0 +1,15 @@
/**
* 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 "RCTViewManager.h"
@interface RNSVGDefinationManager : RCTViewManager
@end
#import "RNSVGNode.h"

View File

@@ -0,0 +1,31 @@
/**
* 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 "RNSVGDefination.h"
#import "RNSVGDefinationManager.h"
@implementation RNSVGDefinationManager
RCT_EXPORT_MODULE()
- (RNSVGDefination *)node
{
return [RNSVGDefination new];
}
- (UIView *)view
{
return [self node];
}
- (RCTShadowView *)shadowView
{
return nil;
}
@end

View File

@@ -29,8 +29,9 @@ RCT_EXPORT_MODULE()
return nil;
}
RCT_EXPORT_VIEW_PROPERTY(name, NSString)
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(trans, CGAffineTransform)
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
RCT_EXPORT_VIEW_PROPERTY(clipPathRef, NSString)
RCT_EXPORT_VIEW_PROPERTY(responsible, BOOL)

View File

@@ -8,6 +8,6 @@
#import "RNSVGRenderableManager.h"
@interface RNSVGShapeManager : RNSVGRenderableManager
@interface RNSVGUseManager : RNSVGRenderableManager
@end

View File

@@ -6,20 +6,19 @@
* LICENSE file in the root directory of this source tree.
*/
#import "RNSVGShapeManager.h"
#import "RNSVGUseManager.h"
#import "RNSVGUse.h"
#import "RNSVGShape.h"
#import "RCTConvert+RNSVG.h"
@implementation RNSVGShapeManager
@implementation RNSVGUseManager
RCT_EXPORT_MODULE()
- (RNSVGRenderable *)node
- (RNSVGNode *)node
{
return [RNSVGShape new];
return [RNSVGUse new];
}
RCT_EXPORT_VIEW_PROPERTY(shape, NSDictionary)
RCT_EXPORT_VIEW_PROPERTY(href, NSString)
@end

View File

@@ -3,7 +3,7 @@ import _ from 'lodash';
const merge = _.assign;
function arrayDiffer(a, b) {
if (a == null || b == null) {
if (_.isNil(a) || _.isNil(b) ) {
return true;
}
if (a.length !== b.length) {
@@ -79,6 +79,10 @@ const GroupAttributes = merge(NodeAttributes, {
clipRule: true
});
const UseAttributes = merge(RenderableAttributes, {
href: true
});
const PathAttributes = merge(RenderableAttributes, {
d: {
diff: arrayDiffer
@@ -146,5 +150,6 @@ export {
EllipseAttributes,
ImageAttributes,
LineAttributes,
RectAttributes
RectAttributes,
UseAttributes
};

View File

@@ -6,16 +6,14 @@ import extractResponder from './extractResponder';
import _ from 'lodash';
export default function(props, options = {stroke: true, transform: true, fill: true, responder: true}) {
if (props.visible === false) {
return {
opacity: 0
};
}
let extractedProps = {
opacity: +props.opacity || 1
};
if (props.id) {
extractedProps.name = props.id;
}
if (props.clipPath) {
_.assign(extractedProps, extractClipping(props));
}
@@ -30,6 +28,10 @@ export default function(props, options = {stroke: true, transform: true, fill: t
if (options.transform) {
extractedProps.transform = extractTransform(props);
} else if (props.transform) {
// todo: add support for transform prop like this:
// {scale: 1.5, translate: '10 10'}
extractedProps.transform = props.transform;
}
if (options.responder) {

View File

@@ -37,6 +37,10 @@ const clipProps = {
clipPath: PropTypes.string
};
const definationProps = {
name: PropTypes.string
};
const strokeProps = {
stroke: PropTypes.string,
strokeWidth: numberProp,
@@ -77,7 +81,8 @@ const pathProps = {
...clipProps,
...transformProps,
...responderProps,
...touchableProps
...touchableProps,
...definationProps
};
const circleProps = {