Move ART JS files to FB internal
Summary: This is part of Lean Core. Reviewed By: rubennorte Differential Revision: D17343246 fbshipit-source-id: 1185e6c1f75e8272048ce1a24c2f195728d436c4
This commit is contained in:
Родитель
56fb72b2c8
Коммит
9a0ebe0aba
|
@ -1,84 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// TODO: Move this into an ART mode called "serialized" or something
|
|
||||||
|
|
||||||
const Class = require('art/core/class.js');
|
|
||||||
const Path = require('art/core/path.js');
|
|
||||||
|
|
||||||
const MOVE_TO = 0;
|
|
||||||
const CLOSE = 1;
|
|
||||||
const LINE_TO = 2;
|
|
||||||
const CURVE_TO = 3;
|
|
||||||
const ARC = 4;
|
|
||||||
|
|
||||||
const SerializablePath = Class(Path, {
|
|
||||||
initialize: function(path) {
|
|
||||||
this.reset();
|
|
||||||
if (path instanceof SerializablePath) {
|
|
||||||
this.path = path.path.slice(0);
|
|
||||||
} else if (path) {
|
|
||||||
if (path.applyToPath) {
|
|
||||||
path.applyToPath(this);
|
|
||||||
} else {
|
|
||||||
this.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onReset: function() {
|
|
||||||
this.path = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
onMove: function(sx, sy, x, y) {
|
|
||||||
this.path.push(MOVE_TO, x, y);
|
|
||||||
},
|
|
||||||
|
|
||||||
onLine: function(sx, sy, x, y) {
|
|
||||||
this.path.push(LINE_TO, x, y);
|
|
||||||
},
|
|
||||||
|
|
||||||
onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) {
|
|
||||||
this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y);
|
|
||||||
},
|
|
||||||
|
|
||||||
_arcToBezier: Path.prototype.onArc,
|
|
||||||
|
|
||||||
onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) {
|
|
||||||
if (rx !== ry || rotation) {
|
|
||||||
return this._arcToBezier(
|
|
||||||
sx,
|
|
||||||
sy,
|
|
||||||
ex,
|
|
||||||
ey,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
rx,
|
|
||||||
ry,
|
|
||||||
sa,
|
|
||||||
ea,
|
|
||||||
ccw,
|
|
||||||
rotation,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
onClose: function() {
|
|
||||||
this.path.push(CLOSE);
|
|
||||||
},
|
|
||||||
|
|
||||||
toJSON: function() {
|
|
||||||
return this.path;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = SerializablePath;
|
|
|
@ -1,612 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Color = require('art/core/color');
|
|
||||||
const Path = require('./ARTSerializablePath');
|
|
||||||
const Transform = require('art/core/transform');
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const ReactNativeViewAttributes = require('../Components/View/ReactNativeViewAttributes');
|
|
||||||
|
|
||||||
const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');
|
|
||||||
const merge = require('../vendor/core/merge');
|
|
||||||
const invariant = require('invariant');
|
|
||||||
|
|
||||||
// Diff Helpers
|
|
||||||
|
|
||||||
function arrayDiffer(a, b) {
|
|
||||||
if (a == null || b == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (a.length !== b.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < a.length; i++) {
|
|
||||||
if (a[i] !== b[i]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fontAndLinesDiffer(a, b) {
|
|
||||||
if (a === b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (a.font !== b.font) {
|
|
||||||
if (a.font === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (b.font === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
a.font.fontFamily !== b.font.fontFamily ||
|
|
||||||
a.font.fontSize !== b.font.fontSize ||
|
|
||||||
a.font.fontWeight !== b.font.fontWeight ||
|
|
||||||
a.font.fontStyle !== b.font.fontStyle
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arrayDiffer(a.lines, b.lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native Attributes
|
|
||||||
|
|
||||||
const SurfaceViewAttributes = merge(ReactNativeViewAttributes.UIView, {
|
|
||||||
// This should contain pixel information such as width, height and
|
|
||||||
// resolution to know what kind of buffer needs to be allocated.
|
|
||||||
// Currently we rely on UIViews and style to figure that out.
|
|
||||||
});
|
|
||||||
|
|
||||||
const NodeAttributes = {
|
|
||||||
transform: {diff: arrayDiffer},
|
|
||||||
opacity: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const GroupAttributes = merge(NodeAttributes, {
|
|
||||||
clipping: {diff: arrayDiffer},
|
|
||||||
});
|
|
||||||
|
|
||||||
const RenderableAttributes = merge(NodeAttributes, {
|
|
||||||
fill: {diff: arrayDiffer},
|
|
||||||
stroke: {diff: arrayDiffer},
|
|
||||||
strokeWidth: true,
|
|
||||||
strokeCap: true,
|
|
||||||
strokeJoin: true,
|
|
||||||
strokeDash: {diff: arrayDiffer},
|
|
||||||
});
|
|
||||||
|
|
||||||
const ShapeAttributes = merge(RenderableAttributes, {
|
|
||||||
d: {diff: arrayDiffer},
|
|
||||||
});
|
|
||||||
|
|
||||||
const TextAttributes = merge(RenderableAttributes, {
|
|
||||||
alignment: true,
|
|
||||||
frame: {diff: fontAndLinesDiffer},
|
|
||||||
path: {diff: arrayDiffer},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Native Components
|
|
||||||
|
|
||||||
const NativeSurfaceView = createReactNativeComponentClass(
|
|
||||||
'ARTSurfaceView',
|
|
||||||
() => ({
|
|
||||||
validAttributes: SurfaceViewAttributes,
|
|
||||||
uiViewClassName: 'ARTSurfaceView',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const NativeGroup = createReactNativeComponentClass('ARTGroup', () => ({
|
|
||||||
validAttributes: GroupAttributes,
|
|
||||||
uiViewClassName: 'ARTGroup',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const NativeShape = createReactNativeComponentClass('ARTShape', () => ({
|
|
||||||
validAttributes: ShapeAttributes,
|
|
||||||
uiViewClassName: 'ARTShape',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const NativeText = createReactNativeComponentClass('ARTText', () => ({
|
|
||||||
validAttributes: TextAttributes,
|
|
||||||
uiViewClassName: 'ARTText',
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
|
|
||||||
function childrenAsString(children) {
|
|
||||||
if (!children) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (typeof children === 'string') {
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
if (children.length) {
|
|
||||||
return children.join('\n');
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Surface - Root node of all ART
|
|
||||||
|
|
||||||
class Surface extends React.Component {
|
|
||||||
static childContextTypes = {
|
|
||||||
isInSurface: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
getChildContext() {
|
|
||||||
return {isInSurface: true};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const height = extractNumber(this.props.height, 0);
|
|
||||||
const width = extractNumber(this.props.width, 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NativeSurfaceView style={[this.props.style, {height, width}]}>
|
|
||||||
{this.props.children}
|
|
||||||
</NativeSurfaceView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node Props
|
|
||||||
|
|
||||||
// TODO: The desktop version of ART has title and cursor. We should have
|
|
||||||
// accessibility support here too even though hovering doesn't work.
|
|
||||||
|
|
||||||
function extractNumber(value, defaultValue) {
|
|
||||||
if (value == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return +value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pooledTransform = new Transform();
|
|
||||||
|
|
||||||
function extractTransform(props) {
|
|
||||||
const scaleX =
|
|
||||||
props.scaleX != null ? props.scaleX : props.scale != null ? props.scale : 1;
|
|
||||||
const scaleY =
|
|
||||||
props.scaleY != null ? props.scaleY : props.scale != null ? props.scale : 1;
|
|
||||||
|
|
||||||
pooledTransform
|
|
||||||
.transformTo(1, 0, 0, 1, 0, 0)
|
|
||||||
.move(props.x || 0, props.y || 0)
|
|
||||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
|
||||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
|
||||||
|
|
||||||
if (props.transform != null) {
|
|
||||||
pooledTransform.transform(props.transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
pooledTransform.xx,
|
|
||||||
pooledTransform.yx,
|
|
||||||
pooledTransform.xy,
|
|
||||||
pooledTransform.yy,
|
|
||||||
pooledTransform.x,
|
|
||||||
pooledTransform.y,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractOpacity(props) {
|
|
||||||
// TODO: visible === false should also have no hit detection
|
|
||||||
if (props.visible === false) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (props.opacity == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return +props.opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Groups
|
|
||||||
|
|
||||||
// Note: ART has a notion of width and height on Group but AFAIK it's a noop in
|
|
||||||
// ReactART.
|
|
||||||
|
|
||||||
class Group extends React.Component {
|
|
||||||
static contextTypes = {
|
|
||||||
isInSurface: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
invariant(
|
|
||||||
this.context.isInSurface,
|
|
||||||
'ART: <Group /> must be a child of a <Surface />',
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<NativeGroup
|
|
||||||
opacity={extractOpacity(props)}
|
|
||||||
transform={extractTransform(props)}>
|
|
||||||
{this.props.children}
|
|
||||||
</NativeGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClippingRectangle extends React.Component {
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const x = extractNumber(props.x, 0);
|
|
||||||
const y = extractNumber(props.y, 0);
|
|
||||||
const w = extractNumber(props.width, 0);
|
|
||||||
const h = extractNumber(props.height, 0);
|
|
||||||
const clipping = [x, y, w, h];
|
|
||||||
// The current clipping API requires x and y to be ignored in the transform
|
|
||||||
const propsExcludingXAndY = merge(props);
|
|
||||||
delete propsExcludingXAndY.x;
|
|
||||||
delete propsExcludingXAndY.y;
|
|
||||||
return (
|
|
||||||
<NativeGroup
|
|
||||||
clipping={clipping}
|
|
||||||
opacity={extractOpacity(props)}
|
|
||||||
transform={extractTransform(propsExcludingXAndY)}>
|
|
||||||
{this.props.children}
|
|
||||||
</NativeGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renderables
|
|
||||||
|
|
||||||
const SOLID_COLOR = 0;
|
|
||||||
const LINEAR_GRADIENT = 1;
|
|
||||||
const RADIAL_GRADIENT = 2;
|
|
||||||
const PATTERN = 3;
|
|
||||||
|
|
||||||
function insertColorIntoArray(color, targetArray, atIndex) {
|
|
||||||
const c = new Color(color);
|
|
||||||
targetArray[atIndex + 0] = c.red / 255;
|
|
||||||
targetArray[atIndex + 1] = c.green / 255;
|
|
||||||
targetArray[atIndex + 2] = c.blue / 255;
|
|
||||||
targetArray[atIndex + 3] = c.alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertColorsIntoArray(stops, targetArray, atIndex) {
|
|
||||||
let i = 0;
|
|
||||||
if ('length' in stops) {
|
|
||||||
while (i < stops.length) {
|
|
||||||
insertColorIntoArray(stops[i], targetArray, atIndex + i * 4);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const offset in stops) {
|
|
||||||
insertColorIntoArray(stops[offset], targetArray, atIndex + i * 4);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return atIndex + i * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertOffsetsIntoArray(stops, targetArray, atIndex, multi, reverse) {
|
|
||||||
let offsetNumber;
|
|
||||||
let i = 0;
|
|
||||||
if ('length' in stops) {
|
|
||||||
while (i < stops.length) {
|
|
||||||
offsetNumber = (i / (stops.length - 1)) * multi;
|
|
||||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const offsetString in stops) {
|
|
||||||
offsetNumber = +offsetString * multi;
|
|
||||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return atIndex + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertColorStopsIntoArray(stops, targetArray, atIndex) {
|
|
||||||
const lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
|
||||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 1, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertDoubleColorStopsIntoArray(stops, targetArray, atIndex) {
|
|
||||||
let lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
|
||||||
lastIndex = insertColorsIntoArray(stops, targetArray, lastIndex);
|
|
||||||
lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false);
|
|
||||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyBoundingBoxToBrushData(brushData, props) {
|
|
||||||
const type = brushData[0];
|
|
||||||
const width = +props.width;
|
|
||||||
const height = +props.height;
|
|
||||||
if (type === LINEAR_GRADIENT) {
|
|
||||||
brushData[1] *= width;
|
|
||||||
brushData[2] *= height;
|
|
||||||
brushData[3] *= width;
|
|
||||||
brushData[4] *= height;
|
|
||||||
} else if (type === RADIAL_GRADIENT) {
|
|
||||||
brushData[1] *= width;
|
|
||||||
brushData[2] *= height;
|
|
||||||
brushData[3] *= width;
|
|
||||||
brushData[4] *= height;
|
|
||||||
brushData[5] *= width;
|
|
||||||
brushData[6] *= height;
|
|
||||||
} else if (type === PATTERN) {
|
|
||||||
// todo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractBrush(colorOrBrush, props) {
|
|
||||||
if (colorOrBrush == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (colorOrBrush._brush) {
|
|
||||||
if (colorOrBrush._bb) {
|
|
||||||
// The legacy API for Gradients allow for the bounding box to be used
|
|
||||||
// as a convenience for specifying gradient positions. This should be
|
|
||||||
// deprecated. It's not properly implemented in canvas mode. ReactART
|
|
||||||
// doesn't handle update to the bounding box correctly. That's why we
|
|
||||||
// mutate this so that if it's reused, we reuse the same resolved box.
|
|
||||||
applyBoundingBoxToBrushData(colorOrBrush._brush, props);
|
|
||||||
colorOrBrush._bb = false;
|
|
||||||
}
|
|
||||||
return colorOrBrush._brush;
|
|
||||||
}
|
|
||||||
const c = new Color(colorOrBrush);
|
|
||||||
return [SOLID_COLOR, c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractColor(color) {
|
|
||||||
if (color == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const c = new Color(color);
|
|
||||||
return [c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractStrokeCap(strokeCap) {
|
|
||||||
switch (strokeCap) {
|
|
||||||
case 'butt':
|
|
||||||
return 0;
|
|
||||||
case 'square':
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
return 1; // round
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractStrokeJoin(strokeJoin) {
|
|
||||||
switch (strokeJoin) {
|
|
||||||
case 'miter':
|
|
||||||
return 0;
|
|
||||||
case 'bevel':
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
return 1; // round
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shape
|
|
||||||
|
|
||||||
// Note: ART has a notion of width and height on Shape but AFAIK it's a noop in
|
|
||||||
// ReactART.
|
|
||||||
|
|
||||||
export type ShapeProps = {|
|
|
||||||
fill?: mixed,
|
|
||||||
stroke?: mixed,
|
|
||||||
strokeCap?: mixed,
|
|
||||||
strokeDash?: mixed,
|
|
||||||
strokeJoin?: mixed,
|
|
||||||
strokeWidth?: mixed,
|
|
||||||
x?: number,
|
|
||||||
y?: number,
|
|
||||||
opacity?: mixed,
|
|
||||||
|};
|
|
||||||
|
|
||||||
class Shape extends React.Component<ShapeProps> {
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const path = props.d || childrenAsString(props.children);
|
|
||||||
const d = (path instanceof Path ? path : new Path(path)).toJSON();
|
|
||||||
return (
|
|
||||||
<NativeShape
|
|
||||||
fill={extractBrush(props.fill, props)}
|
|
||||||
opacity={extractOpacity(props)}
|
|
||||||
stroke={extractColor(props.stroke)}
|
|
||||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
|
||||||
strokeDash={props.strokeDash || null}
|
|
||||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
|
||||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
|
||||||
transform={extractTransform(props)}
|
|
||||||
d={d}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text
|
|
||||||
|
|
||||||
const cachedFontObjectsFromString = {};
|
|
||||||
|
|
||||||
const fontFamilyPrefix = /^[\s"']*/;
|
|
||||||
const fontFamilySuffix = /[\s"']*$/;
|
|
||||||
|
|
||||||
function extractSingleFontFamily(fontFamilyString) {
|
|
||||||
// ART on the web allows for multiple font-families to be specified.
|
|
||||||
// For compatibility, we extract the first font-family, hoping
|
|
||||||
// we'll get a match.
|
|
||||||
return fontFamilyString
|
|
||||||
.split(',')[0]
|
|
||||||
.replace(fontFamilyPrefix, '')
|
|
||||||
.replace(fontFamilySuffix, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFontString(font) {
|
|
||||||
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
|
|
||||||
return cachedFontObjectsFromString[font];
|
|
||||||
}
|
|
||||||
const regexp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm\%]*(?:\s*\/.*?)?\s+)?\s*\"?([^\"]*)/i;
|
|
||||||
const match = regexp.exec(font);
|
|
||||||
if (!match) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const fontFamily = extractSingleFontFamily(match[3]);
|
|
||||||
const fontSize = +match[2] || 12;
|
|
||||||
const isBold = /bold/.exec(match[1]);
|
|
||||||
const isItalic = /italic/.exec(match[1]);
|
|
||||||
cachedFontObjectsFromString[font] = {
|
|
||||||
fontFamily: fontFamily,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: isBold ? 'bold' : 'normal',
|
|
||||||
fontStyle: isItalic ? 'italic' : 'normal',
|
|
||||||
};
|
|
||||||
return cachedFontObjectsFromString[font];
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractFont(font) {
|
|
||||||
if (font == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof font === 'string') {
|
|
||||||
return parseFontString(font);
|
|
||||||
}
|
|
||||||
const fontFamily = extractSingleFontFamily(font.fontFamily);
|
|
||||||
const fontSize = +font.fontSize || 12;
|
|
||||||
const fontWeight =
|
|
||||||
font.fontWeight != null ? font.fontWeight.toString() : '400';
|
|
||||||
return {
|
|
||||||
// Normalize
|
|
||||||
fontFamily: fontFamily,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
fontStyle: font.fontStyle,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newLine = /\n/g;
|
|
||||||
function extractFontAndLines(font, text) {
|
|
||||||
return {font: extractFont(font), lines: text.split(newLine)};
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractAlignment(alignment) {
|
|
||||||
switch (alignment) {
|
|
||||||
case 'right':
|
|
||||||
return 1;
|
|
||||||
case 'center':
|
|
||||||
return 2;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Text extends React.Component {
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const path = props.path;
|
|
||||||
const textPath = path
|
|
||||||
? (path instanceof Path ? path : new Path(path)).toJSON()
|
|
||||||
: null;
|
|
||||||
const textFrame = extractFontAndLines(
|
|
||||||
props.font,
|
|
||||||
childrenAsString(props.children),
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<NativeText
|
|
||||||
fill={extractBrush(props.fill, props)}
|
|
||||||
opacity={extractOpacity(props)}
|
|
||||||
stroke={extractColor(props.stroke)}
|
|
||||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
|
||||||
strokeDash={props.strokeDash || null}
|
|
||||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
|
||||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
|
||||||
transform={extractTransform(props)}
|
|
||||||
alignment={extractAlignment(props.alignment)}
|
|
||||||
frame={textFrame}
|
|
||||||
path={textPath}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declarative fill type objects - API design not finalized
|
|
||||||
|
|
||||||
function LinearGradient(stops, x1, y1, x2, y2) {
|
|
||||||
const type = LINEAR_GRADIENT;
|
|
||||||
|
|
||||||
if (arguments.length < 5) {
|
|
||||||
const angle = ((x1 == null ? 270 : x1) * Math.PI) / 180;
|
|
||||||
|
|
||||||
let x = Math.cos(angle);
|
|
||||||
let y = -Math.sin(angle);
|
|
||||||
const l = (Math.abs(x) + Math.abs(y)) / 2;
|
|
||||||
|
|
||||||
x *= l;
|
|
||||||
y *= l;
|
|
||||||
|
|
||||||
x1 = 0.5 - x;
|
|
||||||
x2 = 0.5 + x;
|
|
||||||
y1 = 0.5 - y;
|
|
||||||
y2 = 0.5 + y;
|
|
||||||
this._bb = true;
|
|
||||||
} else {
|
|
||||||
this._bb = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const brushData = [type, +x1, +y1, +x2, +y2];
|
|
||||||
insertColorStopsIntoArray(stops, brushData, 5);
|
|
||||||
this._brush = brushData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function RadialGradient(stops, fx, fy, rx, ry, cx, cy) {
|
|
||||||
if (ry == null) {
|
|
||||||
ry = rx;
|
|
||||||
}
|
|
||||||
if (cx == null) {
|
|
||||||
cx = fx;
|
|
||||||
}
|
|
||||||
if (cy == null) {
|
|
||||||
cy = fy;
|
|
||||||
}
|
|
||||||
if (fx == null) {
|
|
||||||
// As a convenience we allow the whole radial gradient to cover the
|
|
||||||
// bounding box. We should consider dropping this API.
|
|
||||||
fx = fy = rx = ry = cx = cy = 0.5;
|
|
||||||
this._bb = true;
|
|
||||||
} else {
|
|
||||||
this._bb = false;
|
|
||||||
}
|
|
||||||
// The ART API expects the radial gradient to be repeated at the edges.
|
|
||||||
// To simulate this we render the gradient twice as large and add double
|
|
||||||
// color stops. Ideally this API would become more restrictive so that this
|
|
||||||
// extra work isn't needed.
|
|
||||||
const brushData = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy];
|
|
||||||
insertDoubleColorStopsIntoArray(stops, brushData, 7);
|
|
||||||
this._brush = brushData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Pattern(url, width, height, left, top) {
|
|
||||||
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReactART = {
|
|
||||||
LinearGradient: LinearGradient,
|
|
||||||
RadialGradient: RadialGradient,
|
|
||||||
Pattern: Pattern,
|
|
||||||
Transform: Transform,
|
|
||||||
Path: Path,
|
|
||||||
Surface: Surface,
|
|
||||||
Group: Group,
|
|
||||||
ClippingRectangle: ClippingRectangle,
|
|
||||||
Shape: Shape,
|
|
||||||
Text: Text,
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = ReactART;
|
|
|
@ -11,8 +11,7 @@
|
||||||
|
|
||||||
#import <RCTTest/RCTTestRunner.h>
|
#import <RCTTest/RCTTestRunner.h>
|
||||||
|
|
||||||
@interface RNTesterSnapshotTests : XCTestCase
|
@interface RNTesterSnapshotTests : XCTestCase {
|
||||||
{
|
|
||||||
RCTTestRunner *_runner;
|
RCTTestRunner *_runner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,15 +28,14 @@
|
||||||
_runner.recordMode = NO;
|
_runner.recordMode = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define RCT_TEST(name) \
|
#define RCT_TEST(name) \
|
||||||
- (void)test##name \
|
-(void)test##name \
|
||||||
{ \
|
{ \
|
||||||
[_runner runTest:_cmd module:@#name]; \
|
[_runner runTest:_cmd module:@ #name]; \
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_TEST(ViewExample)
|
RCT_TEST(ViewExample)
|
||||||
RCT_TEST(LayoutExample)
|
RCT_TEST(LayoutExample)
|
||||||
RCT_TEST(ARTExample)
|
|
||||||
RCT_TEST(ScrollViewExample)
|
RCT_TEST(ScrollViewExample)
|
||||||
RCT_TEST(TextExample)
|
RCT_TEST(TextExample)
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const {ART, Platform, View} = require('react-native');
|
|
||||||
|
|
||||||
const {Surface, Path, Group, Shape} = ART;
|
|
||||||
|
|
||||||
const scale = Platform.isTV ? 4 : 1;
|
|
||||||
|
|
||||||
type Props = $ReadOnly<{||}>;
|
|
||||||
class ARTExample extends React.Component<Props> {
|
|
||||||
render() {
|
|
||||||
const pathRect = new Path()
|
|
||||||
.moveTo(scale * 0, scale * 0)
|
|
||||||
.lineTo(scale * 0, scale * 110)
|
|
||||||
.lineTo(scale * 110, scale * 110)
|
|
||||||
.lineTo(scale * 110, scale * 0)
|
|
||||||
.close();
|
|
||||||
|
|
||||||
const pathCircle0 = new Path()
|
|
||||||
.moveTo(scale * 30, scale * 5)
|
|
||||||
.arc(scale * 0, scale * 50, scale * 25)
|
|
||||||
.arc(scale * 0, -scale * 50, scale * 25)
|
|
||||||
.close();
|
|
||||||
|
|
||||||
const pathCircle1 = new Path()
|
|
||||||
.moveTo(scale * 30, scale * 55)
|
|
||||||
.arc(scale * 0, scale * 50, scale * 25)
|
|
||||||
.arc(scale * 0, -scale * 50, scale * 25)
|
|
||||||
.close();
|
|
||||||
|
|
||||||
const pathCircle2 = new Path()
|
|
||||||
.moveTo(scale * 55, scale * 30)
|
|
||||||
.arc(scale * 50, scale * 0, scale * 25)
|
|
||||||
.arc(-scale * 50, scale * 0, scale * 25)
|
|
||||||
.close();
|
|
||||||
|
|
||||||
const pathCircle3 = new Path()
|
|
||||||
.moveTo(scale * 55, scale * 80)
|
|
||||||
.arc(scale * 50, scale * 0, scale * 25)
|
|
||||||
.arc(-scale * 50, scale * 0, scale * 25)
|
|
||||||
.close();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Surface width={scale * 200} height={scale * 200}>
|
|
||||||
<Group>
|
|
||||||
<Shape
|
|
||||||
d={pathRect}
|
|
||||||
stroke="#000080"
|
|
||||||
fill="#000080"
|
|
||||||
strokeWidth={scale}
|
|
||||||
/>
|
|
||||||
<Shape
|
|
||||||
d={pathCircle0}
|
|
||||||
stroke="#FF0000"
|
|
||||||
fill="#FF0000"
|
|
||||||
strokeWidth={scale}
|
|
||||||
/>
|
|
||||||
<Shape
|
|
||||||
d={pathCircle1}
|
|
||||||
stroke="#00FF00"
|
|
||||||
fill="#00FF00"
|
|
||||||
strokeWidth={scale}
|
|
||||||
/>
|
|
||||||
<Shape
|
|
||||||
d={pathCircle2}
|
|
||||||
stroke="#00FFFF"
|
|
||||||
fill="#00FFFF"
|
|
||||||
strokeWidth={scale}
|
|
||||||
/>
|
|
||||||
<Shape
|
|
||||||
d={pathCircle3}
|
|
||||||
stroke="#FFFFFF"
|
|
||||||
fill="#FFFFFF"
|
|
||||||
strokeWidth={scale}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
</Surface>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.title = '<ART>';
|
|
||||||
exports.displayName = 'ARTExample';
|
|
||||||
exports.description = 'ART input for numeric values';
|
|
||||||
exports.examples = [
|
|
||||||
{
|
|
||||||
title: 'ART Example',
|
|
||||||
render(): React.Element<any> {
|
|
||||||
return <ARTExample />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
|
@ -18,11 +18,6 @@ const ComponentExamples: Array<RNTesterExample> = [
|
||||||
module: require('../examples/ActivityIndicator/ActivityIndicatorExample'),
|
module: require('../examples/ActivityIndicator/ActivityIndicatorExample'),
|
||||||
supportsTVOS: true,
|
supportsTVOS: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'ARTExample',
|
|
||||||
module: require('../examples/ART/ARTExample'),
|
|
||||||
supportsTVOS: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'ButtonExample',
|
key: 'ButtonExample',
|
||||||
module: require('../examples/Button/ButtonExample'),
|
module: require('../examples/Button/ButtonExample'),
|
||||||
|
|
23
index.js
23
index.js
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
|
import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
|
||||||
import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/ActivityIndicator';
|
import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/ActivityIndicator';
|
||||||
import typeof ReactNativeART from './Libraries/ART/ReactNativeART';
|
|
||||||
import typeof Button from './Libraries/Components/Button';
|
import typeof Button from './Libraries/Components/Button';
|
||||||
import typeof CheckBox from './Libraries/Components/CheckBox/CheckBox';
|
import typeof CheckBox from './Libraries/Components/CheckBox/CheckBox';
|
||||||
import typeof DatePickerIOS from './Libraries/Components/DatePicker/DatePickerIOS';
|
import typeof DatePickerIOS from './Libraries/Components/DatePicker/DatePickerIOS';
|
||||||
|
@ -112,15 +111,6 @@ module.exports = {
|
||||||
get ActivityIndicator(): ActivityIndicator {
|
get ActivityIndicator(): ActivityIndicator {
|
||||||
return require('./Libraries/Components/ActivityIndicator/ActivityIndicator');
|
return require('./Libraries/Components/ActivityIndicator/ActivityIndicator');
|
||||||
},
|
},
|
||||||
get ART(): ReactNativeART {
|
|
||||||
warnOnce(
|
|
||||||
'art-moved',
|
|
||||||
'React Native ART has been extracted from react-native core and will be removed in a future release. ' +
|
|
||||||
"It can now be installed and imported from '@react-native-community/art' instead of 'react-native'. " +
|
|
||||||
'See https://github.com/react-native-community/art',
|
|
||||||
);
|
|
||||||
return require('./Libraries/ART/ReactNativeART');
|
|
||||||
},
|
|
||||||
get Button(): Button {
|
get Button(): Button {
|
||||||
return require('./Libraries/Components/Button');
|
return require('./Libraries/Components/Button');
|
||||||
},
|
},
|
||||||
|
@ -467,6 +457,19 @@ module.exports = {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
// $FlowFixMe This is intentional: Flow will error when attempting to access ART.
|
||||||
|
Object.defineProperty(module.exports, 'ART', {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'ART has been removed from React Native. ' +
|
||||||
|
"It can now be installed and imported from '@react-native-community/art' instead of 'react-native'. " +
|
||||||
|
'See https://github.com/react-native-community/react-native-art',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// $FlowFixMe This is intentional: Flow will error when attempting to access ListView.
|
// $FlowFixMe This is intentional: Flow will error when attempting to access ListView.
|
||||||
Object.defineProperty(module.exports, 'ListView', {
|
Object.defineProperty(module.exports, 'ListView', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
|
@ -90,7 +90,6 @@
|
||||||
"@react-native-community/cli-platform-android": "^3.0.0-alpha.1",
|
"@react-native-community/cli-platform-android": "^3.0.0-alpha.1",
|
||||||
"@react-native-community/cli-platform-ios": "^3.0.0-alpha.1",
|
"@react-native-community/cli-platform-ios": "^3.0.0-alpha.1",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"art": "^0.10.0",
|
|
||||||
"base64-js": "^1.1.2",
|
"base64-js": "^1.1.2",
|
||||||
"connect": "^3.6.5",
|
"connect": "^3.6.5",
|
||||||
"create-react-class": "^15.6.3",
|
"create-react-class": "^15.6.3",
|
||||||
|
|
|
@ -1496,11 +1496,6 @@ arrify@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||||
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
|
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
|
||||||
|
|
||||||
art@^0.10.0:
|
|
||||||
version "0.10.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/art/-/art-0.10.3.tgz#b01d84a968ccce6208df55a733838c96caeeaea2"
|
|
||||||
integrity sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ==
|
|
||||||
|
|
||||||
asap@~2.0.3:
|
asap@~2.0.3:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче