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:
Christoph Nakazawa 2019-09-13 07:44:20 -07:00 коммит произвёл Facebook Github Bot
Родитель 56fb72b2c8
Коммит 9a0ebe0aba
8 изменённых файлов: 19 добавлений и 830 удалений

Просмотреть файл

@ -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>
@interface RNTesterSnapshotTests : XCTestCase
{
@interface RNTesterSnapshotTests : XCTestCase {
RCTTestRunner *_runner;
}
@ -29,15 +28,14 @@
_runner.recordMode = NO;
}
#define RCT_TEST(name) \
- (void)test##name \
{ \
[_runner runTest:_cmd module:@#name]; \
}
#define RCT_TEST(name) \
-(void)test##name \
{ \
[_runner runTest:_cmd module:@ #name]; \
}
RCT_TEST(ViewExample)
RCT_TEST(LayoutExample)
RCT_TEST(ARTExample)
RCT_TEST(ScrollViewExample)
RCT_TEST(TextExample)
#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'),
supportsTVOS: true,
},
{
key: 'ARTExample',
module: require('../examples/ART/ARTExample'),
supportsTVOS: true,
},
{
key: 'ButtonExample',
module: require('../examples/Button/ButtonExample'),

Просмотреть файл

@ -12,7 +12,6 @@
import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/ActivityIndicator';
import typeof ReactNativeART from './Libraries/ART/ReactNativeART';
import typeof Button from './Libraries/Components/Button';
import typeof CheckBox from './Libraries/Components/CheckBox/CheckBox';
import typeof DatePickerIOS from './Libraries/Components/DatePicker/DatePickerIOS';
@ -112,15 +111,6 @@ module.exports = {
get 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 {
return require('./Libraries/Components/Button');
},
@ -467,6 +457,19 @@ module.exports = {
};
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.
Object.defineProperty(module.exports, 'ListView', {
configurable: true,

Просмотреть файл

@ -90,7 +90,6 @@
"@react-native-community/cli-platform-android": "^3.0.0-alpha.1",
"@react-native-community/cli-platform-ios": "^3.0.0-alpha.1",
"abort-controller": "^3.0.0",
"art": "^0.10.0",
"base64-js": "^1.1.2",
"connect": "^3.6.5",
"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"
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:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"