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
JavaScript
/**
* @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));
}