add ClipPath element support

This commit is contained in:
Horcrux
2016-04-21 17:55:18 +08:00
parent 7859813eac
commit 7d3ef84163
14 changed files with 197 additions and 60 deletions
+47 -5
View File
@@ -7,13 +7,15 @@ import Svg, {
Defs,
RadialGradient,
Stop,
Line,
Rect,
Text,
G
Ellipse,
G,
Polygon,
Circle
} from 'react-native-art-svg';
class ClipPathExample extends Component{
class ClipPathAttr extends Component{
static title = 'Clip by set clip-path with a path data';
render() {
return <Svg
@@ -46,7 +48,7 @@ class ClipPathExample extends Component{
}
}
class ClipRulePathExample extends Component{
class ClipRule extends Component{
static title = 'Clip a group with clipRule="evenodd"';
render() {
return <Svg
@@ -90,6 +92,46 @@ class ClipRulePathExample extends Component{
}
}
class ClipPathElement extends Component{
static title = 'Clip by set clip-path with a path data';
render() {
return <Svg
height="100"
width="100"
>
<Defs>
<RadialGradient id="grad" cx="50%" cy="50%" rx="50%" ry="50%" fx="50%" fy="50%">
<Stop
offset="0%"
stopColor="#ff0"
stopOpacity="1"
/>
<Stop
offset="100%"
stopColor="#00f"
stopOpacity="1"
/>
</RadialGradient>
<ClipPath id="clip">
<Circle cx="30" cy="30" r="20"/>
<Ellipse cx="60" cy="70" rx="20" ry="10" />
<Rect x="65" y="15" width="30" height="30" />
<Polygon points="20,60 20,80 50,70" />
</ClipPath>
</Defs>
<Rect
x="0"
y="0"
width="100"
height="100"
fill="url(#grad)"
clipPath="url(#clip)"
/>
</Svg>;
}
}
const icon = <Svg
height="20"
width="20"
@@ -132,7 +174,7 @@ const icon = <Svg
</G>
</Svg>;
const samples = [ClipPathExample, ClipRulePathExample];
const samples = [ClipPathAttr, ClipRule, ClipPathElement];
export {
icon,
+7 -1
View File
@@ -16,10 +16,16 @@ class Circle extends Component{
cy: 0
};
static getPath = props => Ellipse.getPath({
cx: props.cx,
cy: props.cy,
rx: props.r,
ry: props.r
});
render() {
return <Ellipse
{...this.props}
r={null}
rx={+this.props.r}
ry={+this.props.r}
/>
+35 -2
View File
@@ -4,6 +4,7 @@ import React, {
Children
} from 'react-native';
import {NativeGroup} from './G';
import {set, remove} from '../lib/extract/extractClipping';
class ClipPath extends Component{
static displayName = 'ClipPath';
@@ -11,9 +12,41 @@ class ClipPath extends Component{
id: PropTypes.string.isRequired
};
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);
};
_combinePaths = children => {
// TODO: combine g elements and their children
// TODO: combine text elements
Children.forEach(children, child => {
let {props, type: {getPath}} = child;
if (getPath) {
this._path += getPath(props);
}
this._combinePaths(props.children);
});
};
_path = '';
render() {
// TODO: 合并children路径
// TODO: clip-rule
this._combinePaths(this.props.children);
set(this.id, this._path);
return <NativeGroup />;
}
}
+4 -1
View File
@@ -9,6 +9,7 @@ let map = {};
import LinearGradient from './LinearGradient';
import RadialGradient from './RadialGradient';
import ClipPath from './ClipPath';
let onlyChild = Children.only;
class DefsItem extends Component{
@@ -82,7 +83,9 @@ class Defs extends Component{
getChildren = () => {
return Children.map(this.props.children, child => {
if (child.type === LinearGradient || child.type === RadialGradient) {
let {type} = child;
if (type === LinearGradient || type === RadialGradient || type === ClipPath) {
return cloneElement(child, {
svgId: this.props.svgId
});
+9 -8
View File
@@ -12,21 +12,22 @@ class Ellipse extends Component{
rx: propType,
ry: propType
};
render() {
let {props} = this;
let {cx, cy, rx, ry} = this.props;
let d = `
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
`;
};
render() {
let {props} = this;
let d = Ellipse.getPath(this.props);
return <Path
{...props}
cx={null}
cy={null}
rx={null}
ry={null}
d={d}
/>;
}
+4 -5
View File
@@ -18,16 +18,15 @@ class Line extends Component{
strokeCap: PropTypes.oneOf(['butt', 'square', 'round'])
};
_convertPath = (props = this.props) => {
return `M${props.x1},${props.y1}L${props.x2},${props.y2}Z`;
};
static getPath = (props) => (
`M${props.x1},${props.y1}L${props.x2},${props.y2}Z`
);
render() {
return <Path
{...this.props}
ref="shape"
d={this._convertPath()}
d={Line.getPath(this.props)}
/>;
}
}
+2
View File
@@ -28,6 +28,8 @@ class Path extends Component{
strokeDasharray: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.number)])
};
static getPath = props => props.d;
_dimensions = null;
componentWillReceiveProps = nextProps => {
+4 -5
View File
@@ -9,13 +9,12 @@ class Polygon extends Component{
static propTypes = {
points: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};
static getPath = props => (`M${props.points.trim().replace(/\s+/g, 'L')}z`);
render() {
let props = this.props;
let d = 'M' + props.points.trim().replace(/\s+/g, 'L') + 'z';
return <Path
{...props}
points={null}
d={d}
{...this.props}
d={Polygon.getPath(this.props)}
/>;
}
}
+4 -5
View File
@@ -13,13 +13,12 @@ class Polyline extends Component{
strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']),
strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round'])
};
static getPath = props => (`M${props.points.trim().replace(/\s+/g, 'L')}`);
render() {
let props = this.props;
let d = 'M' + props.points.trim().replace(/\s+/g, 'L');
return <Path
{...props}
points={null}
d={d}
{...this.props}
d={Polyline.getPath(this.props)}
/>;
}
}
+46 -25
View File
@@ -12,6 +12,38 @@ function processRadius(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{
static displayName = 'Rect';
static propTypes = {
@@ -32,36 +64,23 @@ class Rect extends Component{
rx: 0,
ry: 0
};
render() {
let {props} = this;
static getPath = (props, r) => {
let {
x,
y,
width,
height,
rx,
ry
height
} = props;
rx = processRadius(rx);
ry = processRadius(ry);
if (!r) {
r = getR(props);
if ((rx && !ry) || (ry && !rx)) {
if (rx) {
ry = rx;
} else {
rx = ry;
}
}
let {rx, ry} = r;
if (rx > width / 2) {
rx = width / 2;
}
if (ry > height / 2) {
ry = height / 2;
}
let d = (rx || ry) ? `
return (rx || ry) ? `
M ${x}, ${y}
h ${width - 2 * rx}
a ${rx},${ry} 0 0 1 ${rx},${ry}
@@ -79,15 +98,17 @@ class Rect extends Component{
h ${-width}
Z
`;
};
render() {
let r = getR(this.props);
return <Path
{...props}
rx={null}
ry={null}
{...this.props}
width={null}
height={null}
x={rx || null}
x={r.rx || null}
y={null}
d={d}
d={Rect.getPath(this.props, r)}
/>;
}
}
+3 -1
View File
@@ -17,6 +17,7 @@ import Defs from './elements/Defs';
import LinearGradient from './elements/LinearGradient';
import RadialGradient from './elements/RadialGradient';
import Stop from './elements/Stop';
import ClipPath from './elements/ClipPath';
export {
Svg,
@@ -34,7 +35,8 @@ export {
Defs,
LinearGradient,
RadialGradient,
Stop
Stop,
ClipPath
};
export default Svg;
+30 -1
View File
@@ -1,18 +1,47 @@
import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath';
import clipReg from './patternReg';
let clipPatterns = {};
const clipRules = {
evenodd: 0,
nonzero: 1
};
function set(id, pattern) {
clipPatterns[id] = pattern;
}
function remove(id) {
delete clipPatterns[id];
}
export default function (props) {
let {clipPath, clipRule} = props;
let clippingProps = {};
if (clipPath) {
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
clippingProps.clipRule = clipRules[clipRule] === 0 ? 0 : 1;
let matched = clipPath.match(clipReg);
if (matched) {
let patternName = `${matched[1]}:${props.svgId}`;
let pattern = clipPatterns[patternName];
if (pattern) {
clippingProps.clipPath = new SerializablePath(pattern).toJSON();
} else {
// TODO: warn
}
} else {
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
}
}
return clippingProps;
}
export {
set,
remove
}
+1 -1
View File
@@ -2,9 +2,9 @@ import rgba from '../rgba';
import {LinearGradientGenerator} from '../../elements/LinearGradient';
import {RadialGradientGenerator} from '../../elements/RadialGradient';
import extractBrush from './extractBrush';
import fillReg from './patternReg';
let fillPatterns = {};
let fillReg = /^url\(#(\w+?)\)$/;
function isGradient(obj) {
return obj instanceof LinearGradientGenerator || obj instanceof RadialGradientGenerator;
+1
View File
@@ -0,0 +1 @@
export default /^url\(#(\w+?)\)$/;