diff --git a/Example/examples/Clipping.js b/Example/examples/Clipping.js index 8cc4d1b3..3195e8a6 100644 --- a/Example/examples/Clipping.js +++ b/Example/examples/Clipping.js @@ -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 + + + + + + + + + + + + + + ; + } +} + const icon = ; -const samples = [ClipPathExample, ClipRulePathExample]; +const samples = [ClipPathAttr, ClipRule, ClipPathElement]; export { icon, diff --git a/elements/Circle.js b/elements/Circle.js index 52a02bea..ea11e311 100644 --- a/elements/Circle.js +++ b/elements/Circle.js @@ -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 diff --git a/elements/ClipPath.js b/elements/ClipPath.js index 2410c657..cb9644e7 100644 --- a/elements/ClipPath.js +++ b/elements/ClipPath.js @@ -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 ; } } diff --git a/elements/Defs.js b/elements/Defs.js index abb53c22..64e6e9e3 100644 --- a/elements/Defs.js +++ b/elements/Defs.js @@ -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 }); diff --git a/elements/Ellipse.js b/elements/Ellipse.js index a35e7ed6..3fcdbc1c 100644 --- a/elements/Ellipse.js +++ b/elements/Ellipse.js @@ -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 ; } diff --git a/elements/Line.js b/elements/Line.js index aad6119b..bb41d19a 100644 --- a/elements/Line.js +++ b/elements/Line.js @@ -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 ; } } diff --git a/elements/Path.js b/elements/Path.js index cad066d2..f50b1c0d 100644 --- a/elements/Path.js +++ b/elements/Path.js @@ -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 => { diff --git a/elements/Polygon.js b/elements/Polygon.js index d8d918fe..0e867041 100644 --- a/elements/Polygon.js +++ b/elements/Polygon.js @@ -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 ; } } diff --git a/elements/Polyline.js b/elements/Polyline.js index ca607adc..6eb6f5ab 100644 --- a/elements/Polyline.js +++ b/elements/Polyline.js @@ -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 ; } } diff --git a/elements/Rect.js b/elements/Rect.js index e15e8c8c..f8838b19 100644 --- a/elements/Rect.js +++ b/elements/Rect.js @@ -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 ; } } diff --git a/index.js b/index.js index 6b44e21f..682798b6 100644 --- a/index.js +++ b/index.js @@ -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; diff --git a/lib/extract/extractClipping.js b/lib/extract/extractClipping.js index 2624e51e..31477e20 100644 --- a/lib/extract/extractClipping.js +++ b/lib/extract/extractClipping.js @@ -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 +} diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js index 302fe9ed..77dbdf52 100644 --- a/lib/extract/extractFill.js +++ b/lib/extract/extractFill.js @@ -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; diff --git a/lib/extract/patternReg.js b/lib/extract/patternReg.js new file mode 100644 index 00000000..f8c58fad --- /dev/null +++ b/lib/extract/patternReg.js @@ -0,0 +1 @@ +export default /^url\(#(\w+?)\)$/;