diff --git a/Example/examples.js b/Example/examples.js
index 77ccf860..a90ced1b 100644
--- a/Example/examples.js
+++ b/Example/examples.js
@@ -10,6 +10,7 @@ import * as Text from './examples/Text';
import * as G from './examples/G';
import * as Stroking from './examples/Stroking';
import * as Use from './examples/Use';
+import * as Gradients from './examples/Gradients';
export {
Svg,
@@ -23,5 +24,6 @@ export {
Text,
Stroking,
G,
- Use
+ Use,
+ Gradients
};
diff --git a/Example/examples/Gradients.js b/Example/examples/Gradients.js
new file mode 100644
index 00000000..4b591a72
--- /dev/null
+++ b/Example/examples/Gradients.js
@@ -0,0 +1,121 @@
+import React, {
+ Component
+} from 'react-native';
+
+import Svg, {
+ Defs,
+ LinearGradient,
+ RadialGradient,
+ Stop,
+ Ellipse,
+ Circle
+} from 'react-native-art-svg';
+
+class LinearGradientHorizontal extends Component{
+ static title = 'Define an ellipse with a horizontal linear gradient from yellow to red';
+ render() {
+ return ;
+ }
+}
+
+class LinearGradientVertical extends Component{
+ static title = 'Define an ellipse with a horizontal linear gradient from yellow to red';
+ render() {
+ return ;
+ }
+}
+
+class RadialGradientExample extends Component{
+ static title = 'Define an ellipse with a radial gradient from yellow to purple';
+ render() {
+ return ;
+ }
+}
+
+class RadialGradientExample2 extends Component{
+ static title = 'Define another ellipse with a radial gradient from white to blue';
+ render() {
+ return ;
+ }
+}
+
+const icon = ;
+
+
+const samples = [LinearGradientHorizontal, LinearGradientVertical, RadialGradientExample, RadialGradientExample2];
+
+export {
+ icon,
+ samples
+};
diff --git a/Example/main.js b/Example/main.js
index 70a44d11..4ebf7373 100644
--- a/Example/main.js
+++ b/Example/main.js
@@ -104,7 +104,7 @@ const styles = StyleSheet.create({
}
});
-const names = ['Svg', 'Stroking', 'Path', 'Line', 'Rect', 'Polygon', 'Polyline', 'Circle', 'Ellipse', 'G', 'Text', 'Use'];
+const names = ['Svg', 'Stroking', 'Path', 'Line', 'Rect', 'Polygon', 'Polyline', 'Circle', 'Ellipse', 'G', 'Text', 'Use', 'Gradients'];
class ArtSvgExample extends Component {
constructor() {
diff --git a/Example/package.json b/Example/package.json
index 373ca2d7..dbc0b6ff 100644
--- a/Example/package.json
+++ b/Example/package.json
@@ -6,8 +6,8 @@
"start": "react-native start"
},
"dependencies": {
- "react-native": "^0 .18.1",
- "react-native-art-svg": "^1.0.1",
+ "react-native": "^0.18.1",
+ "react-native-art-svg": "^1.0.3",
"react-native-root-modal": "^1.0.2"
}
}
diff --git a/README.md b/README.md
index 80906afa..c9b435e1 100644
--- a/README.md
+++ b/README.md
@@ -9,15 +9,7 @@ TODO:
5. animations https://github.com/maxwellito/vivus
elements:
-1.defs
-2.tref !
-3.tspan !
-4.clipPath (wait for official todo)
-5.glyph ? missing-glyph?
-6.linearGradient
-7.radialGradient
-8.marker?
-9.pattern
-10.textPath
-11.stop
-12.symbol
+1.clipPath (wait for official complete)
+2.textPath (wait for official complete)
+3.symbol
+4.pattern
diff --git a/elements/Defs.js b/elements/Defs.js
index 9fd4b121..290d80f3 100644
--- a/elements/Defs.js
+++ b/elements/Defs.js
@@ -8,6 +8,8 @@ import React, {
let {Group} = ART;
let map = {};
+import LinearGradient from './LinearGradient';
+import RadialGradient from './RadialGradient';
let onlyChild = Children.only;
class DefsItem extends Component{
@@ -81,6 +83,11 @@ class Defs extends Component{
getChildren = () => {
return Children.map(this.props.children, child => {
+ if (child.type === LinearGradient || child.type === RadialGradient) {
+ return cloneElement(child, {
+ svgId: this.props.svgId
+ });
+ }
if (child.props.id) {
return {child};
}
diff --git a/elements/Ellipse.js b/elements/Ellipse.js
index 25f0f051..af794433 100644
--- a/elements/Ellipse.js
+++ b/elements/Ellipse.js
@@ -3,8 +3,6 @@ import React, {
PropTypes,
ART
} from 'react-native';
-import fillFilter from '../lib/fillFilter';
-import strokeFilter from '../lib/strokeFilter';
import Path from './Path';
let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
diff --git a/elements/LinearGradient.js b/elements/LinearGradient.js
new file mode 100644
index 00000000..fe215de7
--- /dev/null
+++ b/elements/LinearGradient.js
@@ -0,0 +1,71 @@
+import React, {
+ Component,
+ PropTypes,
+ ART,
+ Children
+} from 'react-native';
+let {
+ Group,
+ LinearGradient: ARTLinearGradient
+} = ART;
+import {set, remove} from '../lib/fillFilter';
+import Stop from './Stop';
+import Color from 'color';
+let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
+let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
+class LinearGradient extends Component{
+ static displayName = 'LinearGradient';
+ static propTypes = {
+ x1: propType,
+ x2: propType,
+ y1: propType,
+ y2: propType,
+ id: PropTypes.string
+ };
+
+ constructor() {
+ super(...arguments);
+ this.id = this.props.id + ':' + this.props.svgId;
+ }
+
+ componentWillReceiveProps = nextProps => {
+ let id = nextProps.id + ':' + nextProps.svgId;
+ if (id !== this.id) {
+ remove(this.id);
+ }
+ };
+
+ componentWillUnmount = () => {
+ remove(this.id);
+ };
+
+ render() {
+ let {
+ x1,
+ y1,
+ x2,
+ y2
+ } = this.props;
+ let stops = {};
+ Children.forEach(this.props.children, child => {
+ if (child.type === Stop && child.props.stopColor && child.props.offset) {
+
+ // convert percent to float.
+ let matched = child.props.offset.match(percentReg);
+ let offset = matched[2] ? matched[1] / 100 : matched[1];
+
+ // add stop
+ stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
+
+ // TODO: convert percent to float.
+ set(this.id, new ARTLinearGradient(stops, x1, y1, x2, y2));
+ } else {
+ console.warn(`'LinearGradient' can only receive 'Stop' elements as children`);
+ }
+ });
+ return ;
+ }
+}
+
+export default LinearGradient;
+
diff --git a/elements/Polygon.js b/elements/Polygon.js
index 5984e75d..e4d8323e 100644
--- a/elements/Polygon.js
+++ b/elements/Polygon.js
@@ -3,8 +3,6 @@ import React, {
PropTypes,
ART
} from 'react-native';
-import fillFilter from '../lib/fillFilter';
-import strokeFilter from '../lib/strokeFilter';
import Path from './Path';
class Polygon extends Component{
diff --git a/elements/Polyline.js b/elements/Polyline.js
index bab6b9d9..b99bbc0b 100644
--- a/elements/Polyline.js
+++ b/elements/Polyline.js
@@ -5,10 +5,6 @@ import React, {
} from 'react-native';
import Path from './Path';
-
-import strokeFilter from '../lib/strokeFilter';
-import fillFilter from '../lib/fillFilter';
-import transformFilter from '../lib/transformFilter';
class Polyline extends Component{
static displayName = 'Polyline';
static propTypes = {
diff --git a/elements/RadialGradient.js b/elements/RadialGradient.js
new file mode 100644
index 00000000..0c20d447
--- /dev/null
+++ b/elements/RadialGradient.js
@@ -0,0 +1,83 @@
+import React, {
+ Component,
+ PropTypes,
+ ART,
+ Children
+} from 'react-native';
+let {
+ Group,
+ RadialGradient: ARTRadialGradient
+} = ART;
+import {set, remove} from '../lib/fillFilter';
+import Stop from './Stop';
+import Color from 'color';
+let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
+let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
+class RadialGradient extends Component{
+ static displayName = 'RadialGradient';
+ static propTypes = {
+ fx: propType,
+ fy: propType,
+ rx: propType,
+ ry: propType,
+ cx: propType,
+ cy: propType,
+ r: propType,
+ id: PropTypes.string
+ };
+
+ constructor() {
+ super(...arguments);
+ this.id = this.props.id + ':' + this.props.svgId;
+ }
+
+ componentWillReceiveProps = nextProps => {
+ let id = nextProps.id + ':' + nextProps.svgId;
+ if (id !== this.id) {
+ remove(this.id);
+ }
+ };
+
+ componentWillUnmount = () => {
+ remove(this.id);
+ };
+
+ render() {
+ let {
+ fx,
+ fy,
+ rx,
+ ry,
+ cx,
+ cy,
+ r
+ } = this.props;
+
+ // TODO: render differently from svg in html
+ if (r) {
+ rx = ry = +r;
+ }
+ let stops = {};
+ Children.forEach(this.props.children, child => {
+ if (child.type === Stop && child.props.stopColor && child.props.offset) {
+
+ // convert percent to float.
+ let matched = child.props.offset.match(percentReg);
+ let offset = matched[2] ? matched[1] / 100 : matched[1];
+
+ // add stop
+ stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
+
+ // TODO: convert percent to float.
+ set(this.id, new ARTRadialGradient(stops, fx, fy, rx, ry, cx, cy));
+
+ } else {
+ console.warn(`'RadialGradient' can only receive 'Stop' elements as children`);
+ }
+ });
+ return ;
+ }
+}
+
+export default RadialGradient;
+
diff --git a/elements/Rect.js b/elements/Rect.js
index 237c1565..19abd30e 100644
--- a/elements/Rect.js
+++ b/elements/Rect.js
@@ -4,9 +4,6 @@ import React, {
ART
} from 'react-native';
-import fillFilter from '../lib/fillFilter';
-import strokeFilter from '../lib/strokeFilter';
-import transformFilter from '../lib/transformFilter';
import Path from './Path';
let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
diff --git a/elements/Stop.js b/elements/Stop.js
new file mode 100644
index 00000000..0903d179
--- /dev/null
+++ b/elements/Stop.js
@@ -0,0 +1,21 @@
+import React, {
+ Component,
+ PropTypes,
+ ART
+} from 'react-native';
+let {
+ Group
+} = ART;
+class Stop extends Component{
+ static displayName = 'Stop';
+ static propTypes = {
+ stopColor: PropTypes.string,
+ stopOpacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ };
+ render() {
+ return null;
+ }
+}
+
+export default Stop;
+
diff --git a/elements/Text.js b/elements/Text.js
index eab26c8c..5e77f4a0 100644
--- a/elements/Text.js
+++ b/elements/Text.js
@@ -3,6 +3,7 @@ import React, {
Component,
PropTypes
} from 'react-native';
+
let {
Text:ARTText
} = ART;
@@ -38,6 +39,7 @@ class Text extends Component{
id={this.props.id}
svgId={this.props.svgId}
visible={true}
+ text={true}
>
;
diff --git a/elements/Use.js b/elements/Use.js
index 261fe9a5..e06227eb 100644
--- a/elements/Use.js
+++ b/elements/Use.js
@@ -1,9 +1,9 @@
import React, {
Component,
PropTypes,
- ART
+ ART,
+ cloneElement
} from 'react-native';
-
import Defs from './Defs';
class Use extends Component{
static displayName = 'Use';
diff --git a/index.js b/index.js
index 3346cbc9..fa60fbca 100644
--- a/index.js
+++ b/index.js
@@ -14,10 +14,13 @@ import G from './elements/G';
import Text from './elements/Text';
import Use from './elements/Use';
import Defs from './elements/Defs';
+import LinearGradient from './elements/LinearGradient';
+import RadialGradient from './elements/RadialGradient';
+import Stop from './elements/Stop';
let {
Group
- } = ART;
+} = ART;
export {
Svg,
@@ -32,7 +35,10 @@ export {
Line,
Rect,
Use,
- Defs
+ Defs,
+ LinearGradient,
+ RadialGradient,
+ Stop
};
export default Svg;
diff --git a/lib/fillFilter.js b/lib/fillFilter.js
index d0e78117..7a684bea 100644
--- a/lib/fillFilter.js
+++ b/lib/fillFilter.js
@@ -1,4 +1,47 @@
import rgba from './rgba';
+import {
+ ART
+} from 'react-native';
+
+let {
+ LinearGradient,
+ RadialGradient
+} = ART;
+
+let fillPatterns = {};
+let fillReg = /^url\(#(\w+?)\)$/;
export default function (props) {
+ let {fill} = props;
+
+ if (fill) {
+ if (fill instanceof LinearGradient || fill instanceof RadialGradient) {
+ return fill;
+ }
+
+ // 尝试匹配 fill="url(#pattern)"
+ let matched = fill.match(fillReg);
+ if (matched) {
+ let patternName = `${matched[1]}:${props.svgId}`;
+ if (fillPatterns[patternName]) {
+ return fillPatterns[patternName];
+ } else {
+ return null;
+ }
+ }
+ }
+
return rgba(props.fill === undefined ? '#000' : props.fill, props.fillOpacity);
}
+
+function set(id, pattern) {
+ fillPatterns[id] = pattern;
+}
+
+function remove(id) {
+ delete fillPatterns[id];
+}
+
+export {
+ set,
+ remove
+}
diff --git a/lib/transformFilter.js b/lib/transformFilter.js
index 00193802..37f95c0d 100644
--- a/lib/transformFilter.js
+++ b/lib/transformFilter.js
@@ -1,14 +1,14 @@
export default function (props) {
- let coords = props.origin ? props.origin.split(',') : [];
+ let coords = props.origin ? props.origin.split(/\s*,\s*/) : [];
let originX = coords.length === 2 ? coords[0] : props.originX;
- let originY = coords.length === 2 ? coords[1] :props.originY;
-
+ let originY = coords.length === 2 ? coords[1] : props.originY;
+ let scale = props.scale == 0 ? 0 : props.scale;
return {
- rotation: props.rotation || props.rotate || 0,
- scale: props.scale || 1,
- originX,
- originY,
+ rotation: +props.rotation / 2 || +props.rotate / 2 || 0,
+ scale: isNaN(scale) ? 1 : scale,
+ originX: +originX || 0,
+ originY: +originY || 0,
x: +props.x || 0,
y: +props.y || 0
}