UNPKG

react-native-art-extra

Version:

Useful React Components like tags used in svg file for ReactNativeART's Surface Component.

476 lines (383 loc) 12.4 kB
/** * @art-extra.js * * * Copyright 2017 程巍巍 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import React from 'react'; import PropTypes from 'prop-types'; import { ART } from 'react-native'; export default ART; const { Transform, Shape, Path } = ART; const CIRCLE = Math.PI*2; const RADIANS_PER_DEGREE = Math.PI/180; /** * Transform 类添加 skew skewX skewY skewTo skewXTo skewYTo 方法 * @return {void} */ !function (proto) { /** * 原来的 scale 方法,不能指定锚点,只参基于原点(0,0); * * 扩展方法可以指定锚点 * * @param {[type]} sx [description] * @param {[type]} sy [description] * @param {[type]} x [description] * @param {[type]} y [description] * @return {[type]} [description] */ proto.scale = function (sx, sy, x, y) { sx = Number(sx), sy = Number(sy), x = Number(x), y = Number(y); x = isNaN(x) ? 0 : x; y = isNaN(y) ? 0 : y; if (__DEV__ && invalidNumbers(sx, sy, x, y)) return this; this.transform(1, 0, 0, 1, x, y); var m = this; return this.transformTo( m.xx * sx, 0, 0, m.yy * sy, m.x, m.y ) .transform(1, 0, 0, 1, -x, -y); }; proto.scaleX = function (sx, x, y) { return this.scale(sx, 1, x, y); }; proto.scaleY = function (sy, x, y) { return this.scale(1, sy, x, y); } proto.scaleTo = function (sx, sy, x, y) { sx = Number(sx), sy = Number(sy), x = Number(x), y = Number(y); x = isNaN(x) ? 0 : x; y = isNaN(y) ? 0 : y; if (__DEV__ && invalidNumbers(sx, sy, x, y)) return this; // Normalize var m = this; var h = Math.sqrt(m.xx * m.xx + m.yx * m.yx); m.xx /= h; m.yx /= h; h = Math.sqrt(m.yy * m.yy + m.xy * m.xy); m.yy /= h; m.xy /= h; return this.scale(sx, sy, x, y); } proto.scaleXTo = function (sx, x, y) { return this.scaleTo(sx, 1, x, y); }; proto.scaleYTo = function (sy, x, y) { return this.scaleTo(1, sy, x, y); } /** * @param {Number} degx * @param {Number} degy * @param {Number} x * @param {Number} y * * @return {this} The Transform instance. */ proto.skew = function (degx, degy, x, y) { var [radx, rady, x, y] = extractSkewArgs(degx, degy, x, y); if (__DEV__ && invalidNumbers(radx, rady, x, y)) return this; this.transform(1, 0, 0, 1, x, y); var m = this; return this.transformTo( 1, m.yx + Math.tan(rady), m.xy + Math.tan(radx), 1, m.x, m.y ) .transform(1, 0, 0, 1, -x, -y); } proto.skewX = function (deg, x, y) { return this.skew(deg, 0, x, y); } proto.skewY = function (deg, x, y) { return this.skew(0, deg, x, y); } proto.skewTo = function(degx, degy, x, y) { var [radx, rady, x, y] = extractSkewArgs(degx, degy, x, y); if (__DEV__ && invalidNumbers(radx, rady, x, y)) return this; var m = this; var flipx = m.xy == 0 ? 0 : m.xy > 0 ? 1 : -1; var flipy = m.yx == 0 ? 0 : m.yx > 0 ? 1 : -1; return this.skew( degx - Math.atan(flipx * m.xy) * 180 / Math.PI, degy - Math.atan(flipy * m.yx) * 180 / Math.PI, x, y); } proto.skewXTo = function (deg, x, y) { return this.skewTo(deg, 0, x, y); } proto.skewYTo = function (deg, x, y) { return this.skewTo(0, deg, x, y); } function extractSkewArgs(degx, degy, x, y) { degx = Number(degx); degy = Number(degy); x = Number(x); y = Number(y); degx = !isNaN(degx) ? degx : !isNaN(degy) ? degy : 0; degy = !isNaN(degy) ? degy : !isNaN(degx) ? degx : 0; degx = degx * RADIANS_PER_DEGREE % CIRCLE; degy = degy * RADIANS_PER_DEGREE % CIRCLE; x = isNaN(x) ? 0 : x; y = isNaN(y) ? 0 : y; return [degx, degy, x, y]; } }(Transform.prototype); /** * ART 库添加基本的 SVG 图形 * Line Rect Ellipse Circle Polyline Polygon */ !function (ART) { const NumRegex = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g; const NumberPropType = _NumberPropType.bind(null, false); NumberPropType.isRequired = _NumberPropType.bind(null, true); class ExtShape extends React.Component { render() { return <Shape {...extractProps(this)}/> } } class Line extends ExtShape { static propTypes = { x1: NumberPropType.isRequired, y1: NumberPropType.isRequired, x2: NumberPropType.isRequired, y2: NumberPropType.isRequired }; _extractPath(path) { const x1 = Number(this.props.x1); const y1 = Number(this.props.y1); const x2 = Number(this.props.x2); const y2 = Number(this.props.y2); if (__DEV__ && invalidNumbers(x1, y1, x2, y2)) return; path.moveTo(x1, y1) .lineTo(x2, y2); } } class Rect extends ExtShape { static propsTyps = { x: NumberPropType.isRequired, y: NumberPropType.isRequired, width: NumberPropType.isRequired, height: NumberPropType.isRequired, rx: NumberPropType, ry: NumberPropType }; static defaultProps = { x: 0, y: 0 }; _extractPath(path) { const x = Number(this.props.x); const y = Number(this.props.y); const width = Number(this.props.width); const height = Number(this.props.height); let rx = Number(this.props.rx); let ry = Number(this.props.ry); rx = !isNaN(rx) ? rx : !isNaN(ry) ? ry : 0; ry = !isNaN(ry) ? ry : !isNaN(rx) ? rx : 0; if (__DEV__ && invalidNumbers(x, y, width, height, rx, ry)) return; if (rx == 0 || ry == 0) { path.moveTo(x, y) .line(width, 0) .line(0, height) .line(-width, 0); }else{ path.moveTo(x, y+ry) .arc(rx, -ry, rx, ry, 0, 0, 1) .line(width - 2*rx, 0) .arc(rx, ry, rx, ry, 0, 0, 1) .line(0, height - 2*ry) .arc(-rx, ry, rx, ry, 0, 0, 1) .line(-width + 2*rx, 0) .arc(-rx, -ry, rx, ry, 0, 0, 1); } path.close(); } } class Ellipse extends ExtShape { static propTypes = { cx: NumberPropType.isRequired, cy: NumberPropType.isRequired, rx: NumberPropType.isRequired, ry: NumberPropType.isRequired }; _extractPath(path) { const cx = Number(this.props.cx); const cy = Number(this.props.cy); let rx = Number(this.props.rx); let ry = Number(this.props.ry); rx = !isNaN(rx) ? rx : !isNaN(ry) ? ry : 0; ry = !isNaN(ry) ? ry : !isNaN(rx) ? rx : 0; if (__DEV__ && invalidNumbers(cx, cy , rx, ry)) return; path.moveTo(cx - rx, cy) .arc(2 * rx, 0, rx, ry, 0, 0, 1) .arc(-2 * rx, 0, rx, ry, 0, 0, 1) .close(); } } class Circle extends ExtShape { static propTypes = { cx: NumberPropType.isRequired, cy: NumberPropType.isRequired, r: NumberPropType.isRequired }; _extractPath(path) { const cx = Number(this.props.cx); const cy = Number(this.props.cy); const r = Number(this.props.r); if (__DEV__ && invalidNumbers(cx, cy , r)) return; path.moveTo(cx - r, cy) .arc(2 * r, 0, r, r, 0, 0, 1) .arc(-2 * r, 0, r, r, 0, 0, 1) .close(); } } class Wedge extends ExtShape { static propTypes = { cx: NumberPropType.isRequired, cy: NumberPropType.isRequired, or: NumberPropType.isRequired, // 外圆半径 ir: NumberPropType.isRequired, // 内圆半径 sa: NumberPropType.isRequired, ea: NumberPropType.isRequired }; static defaultProps = { ir: 0 } _extractPath(path) { const cx = Number(this.props.cx); const cy = Number(this.props.cy); const or = Number(this.props.or); const ir = Number(this.props.ir); const sa = Number(this.props.sa) * RADIANS_PER_DEGREE % CIRCLE; const ea = Number(this.props.ea) * RADIANS_PER_DEGREE % CIRCLE; if (__DEV__ && invalidNumbers(cx, cy, or, ir, sa, ea)) return; const a = sa > ea ? (CIRCLE - sa + ea) : (ea - sa); if (a >= CIRCLE){ path.moveTo(cx, cy+or) .arc(or * 2, 0, or) .arc(-or * 2, 0, or); if (ir) path.move(or - ir, 0) .counterArc(ir * 2, 0, ir) .counterArc(-ir * 2, 0, ir); } else { const ss = Math.sin(sa), es = Math.sin(ea); const sc = Math.cos(sa), ec = Math.cos(ea); const large = a > Math.PI; const ds = es - ss, dc = ec - sc, dr = ir - or; path.moveTo(cx + or * ss, cy - or * sc) .arc(or * ds, or * -dc, or, or, large) .line(dr * es, dr * -ec); if (ir) path.counterArc(ir * -ds, ir * dc, ir, ir, large); } path.close(); } } class Polyline extends ExtShape { static propTypes = { points: PointsPropTypes.bind(null, true) } _extractPath(path) { const points = Array.isArray(this.props.points) ? this.props.points : this.props.points.match(NumRegex).map(Number); path.moveTo(points.shift(), points.shift()); while (points.length > 1) { path.lineTo(points.shift(), points.shift()); } } } class Polygon extends Polyline { _extractPath(path) { super._extractPath(path); path.close(); } } return Object.assign(ART, { Line, Rect, Ellipse, Circle, Wedge, Polyline, Polygon }); /* * Utils */ function extractProps(self) { return [ 'fill', 'opacity', 'stroke', 'strokeCap', 'strokeDash', 'strokeJoin', 'strokeWidth', 'transform' ].reduce(function (props, name) { if (self.props[name] !== undefined) props[name] = self.props[name]; return props; }, {d: extractPath(self, new Path())}); } function extractPath(self, path) { self._extractPath(path); return path; } function _NumberPropType(isRequired, props, propName, componentName, location, propFullName) { const undefinedOrNull = checkUndefinedOrNull(isRequired, props, propName, componentName, location, propFullName); if (undefinedOrNull) return undefinedOrNull; var number = props[propName]; if (typeof number === 'number') { return ; } if (isNaN(Number(number))) { return new Error( 'Invalid ' + location + ' `' + (propFullName || propName) + '` supplied to `' + componentName + '`: ' + number + '\n' ); } } function PointsPropTypes(isRequired, props, propName, componentName, location, propFullName) { const undefinedOrNull = checkUndefinedOrNull(isRequired, props, propName, componentName, location, propFullName); if (undefinedOrNull) return undefinedOrNull; var points = props[propName]; if (typeof points == 'string') { points = points.match(NumRegex); } if (points && Array.isArray(points) && points.length >= 6 && points.map(Number).filter(isNaN).length == 0) { return; } return new Error( 'Invalid ' + location + ' `' + (propFullName || propName) + '` supplied to `' + componentName + '`: ' + number + '\n' + 'Invalid points must be: ' + ' Array of Number or String with numberic slice, and must contain at least six element.' ); } function checkUndefinedOrNull(isRequired, props, propName, componentName, location, propFullName) { var prop = props[propName]; if (prop === undefined || prop === null) { if (isRequired) { return new Error( 'Required ' + location + ' `' + (propFullName || propName) + '` was not specified in `' + componentName + '`.' ); } } } }(ART); function invalidNumbers(...argv) { return argv.filter(invalidNumber).length > 0; } function invalidNumber(number) { return isNaN(Number(number)); }