Move color processing to JS

Reviewed By: @vjeux

Differential Revision: D2346353
This commit is contained in:
Alexsander Akers 2015-09-17 08:36:08 -07:00 committed by facebook-github-bot-7
parent 6078a4f865
commit 9a2d05d9b2
17 changed files with 292 additions and 424 deletions

View File

@ -26,12 +26,12 @@ var styles = StyleSheet.create({
},
border1: {
borderWidth: 10,
borderColor: '#a52a2a',
borderColor: 'brown',
},
borderRadius: {
borderWidth: 10,
borderRadius: 10,
borderColor: '#00ffff',
borderColor: 'cyan',
},
border2: {
borderWidth: 10,

View File

@ -11,22 +11,22 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
* @flow-weak
*/
'use strict';
var React = require('react-native');
var {
StyleSheet,
PanResponder,
StyleSheet,
View,
processColor,
} = React;
var CIRCLE_SIZE = 80;
var CIRCLE_COLOR = 'blue';
var CIRCLE_HIGHLIGHT_COLOR = 'green';
var PanResponderExample = React.createClass({
statics: {
@ -78,13 +78,13 @@ var PanResponderExample = React.createClass({
_highlight: function() {
this.circle && this.circle.setNativeProps({
backgroundColor: CIRCLE_HIGHLIGHT_COLOR
backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)
});
},
_unHighlight: function() {
this.circle && this.circle.setNativeProps({
backgroundColor: CIRCLE_COLOR
backgroundColor: processColor(CIRCLE_COLOR)
});
},

View File

@ -57,7 +57,6 @@
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; };
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */; };
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; };
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
/* End PBXBuildFile section */
@ -231,7 +230,6 @@
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = "<group>"; };
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = "<group>"; };
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageLoaderHelpers.h; sourceTree = "<group>"; };
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -385,7 +383,6 @@
138D6A151B53CD440074A87E /* RCTCacheTests.m */,
1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */,
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
@ -846,7 +843,6 @@
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */,
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,

View File

@ -1,79 +0,0 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>
#import "RCTConvert.h"
@interface RCTConvert_UIColorTests : XCTestCase
@end
@implementation RCTConvert_UIColorTests
#define XCTAssertEqualColors(color1, color2) do { \
CGFloat r1, g1, b1, a1; \
CGFloat r2, g2, b2, a2; \
XCTAssertTrue([(color1) getRed:&r1 green:&g1 blue:&b1 alpha:&a1] && \
[(color2) getRed:&r2 green:&g2 blue:&b2 alpha:&a2] && \
r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2, \
@"rgba(%d, %d, %d, %.3f) != rgba(%d, %d, %d, %.3f)", \
(int)(r1 * 255), (int)(g1 * 255), (int)(b1 * 255), a1, \
(int)(r2 * 255), (int)(g2 * 255), (int)(b2 * 255), a2 \
); \
} while (0)
- (void)testHex3
{
UIColor *color = [RCTConvert UIColor:@"#333"];
UIColor *expected = [UIColor colorWithWhite:0.2 alpha:1.0];
XCTAssertEqualColors(color, expected);
}
- (void)testHex6
{
UIColor *color = [RCTConvert UIColor:@"#666"];
UIColor *expected = [UIColor colorWithWhite:0.4 alpha:1.0];
XCTAssertEqualColors(color, expected);
}
- (void)testRGB
{
UIColor *color = [RCTConvert UIColor:@"rgb(51, 102, 153)"];
UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:1.0];
XCTAssertEqualColors(color, expected);
}
- (void)testRGBA
{
UIColor *color = [RCTConvert UIColor:@"rgba(51, 102, 153, 0.5)"];
UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.5];
XCTAssertEqualColors(color, expected);
}
- (void)testHSL
{
UIColor *color = [RCTConvert UIColor:@"hsl(30, 50%, 50%)"];
UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:1.0];
XCTAssertEqualColors(color, expected);
}
- (void)testHSLA
{
UIColor *color = [RCTConvert UIColor:@"hsla(30, 50%, 50%, 0.5)"];
UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:0.5];
XCTAssertEqualColors(color, expected);
}
@end

View File

@ -40,30 +40,7 @@ var notMultiline = {
onSubmitEditing: true,
};
var AndroidTextInputAttributes = {
autoCapitalize: true,
autoCorrect: true,
autoFocus: true,
textAlign: true,
textAlignVertical: true,
keyboardType: true,
mostRecentEventCount: true,
multiline: true,
numberOfLines: true,
password: true,
placeholder: true,
placeholderTextColor: true,
text: true,
testID: true,
underlineColorAndroid: true,
editable : true,
};
var viewConfigAndroid = {
uiViewClassName: 'AndroidTextInput',
validAttributes: AndroidTextInputAttributes,
};
var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);
var RCTTextView = requireNativeComponent('RCTTextView', null);
var RCTTextField = requireNativeComponent('RCTTextField', null);
@ -317,7 +294,7 @@ var TextInput = React.createClass({
mixins: [NativeMethodsMixin, TimerMixin],
viewConfig: ((Platform.OS === 'ios' ? RCTTextField.viewConfig :
(Platform.OS === 'android' ? viewConfigAndroid : {})) : Object),
(Platform.OS === 'android' ? AndroidTextInput.viewConfig : {})) : Object),
isFocused: function(): boolean {
return TextInputState.currentlyFocusedField() ===
@ -578,9 +555,4 @@ var styles = StyleSheet.create({
},
});
var AndroidTextInput = createReactNativeComponentClass({
validAttributes: AndroidTextInputAttributes,
uiViewClassName: 'AndroidTextInput',
});
module.exports = TextInput;

View File

@ -17,7 +17,7 @@ var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var ReactPropTypes = require('ReactPropTypes');
var createReactNativeComponentClass = require('createReactNativeComponentClass');
var requireNativeComponent = require('requireNativeComponent');
/**
* React component that wraps the Android-only [`Toolbar` widget][0]. A Toolbar can display a logo,
@ -166,9 +166,6 @@ var toolbarAttributes = {
titleColor: true,
};
var NativeToolbar = createReactNativeComponentClass({
validAttributes: toolbarAttributes,
uiViewClassName: 'ToolbarAndroid',
});
var NativeToolbar = requireNativeComponent('ToolbarAndroid', null);
module.exports = ToolbarAndroid;

View File

@ -21,6 +21,7 @@ var createReactNativeComponentClass = require('createReactNativeComponentClass')
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var onlyChild = require('onlyChild');
var processColor = require('processColor');
var rippleBackgroundPropType = createStrictShapeTypeChecker({
type: React.PropTypes.oneOf(['RippleAndroid']),
@ -112,7 +113,7 @@ var TouchableNativeFeedback = React.createClass({
return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackgroundBorderless'};
},
Ripple: function(color, borderless) {
return {type: 'RippleAndroid', color: color, borderless: borderless};
return {type: 'RippleAndroid', color: processColor(color), borderless: borderless};
},
},

View File

@ -77,15 +77,34 @@ var NativeMethodsMixin = {
break;
}
}
var style = precomputeStyle(flattenStyle(nativeProps.style));
var validAttributes = this.viewConfig.validAttributes;
var hasProcessedProps = false;
var processedProps = {};
for (var key in nativeProps) {
var process = validAttributes[key] && validAttributes[key].process;
if (process) {
hasProcessedProps = true;
processedProps[key] = process(nativeProps[key]);
}
}
var style = precomputeStyle(
flattenStyle(processedProps.style || nativeProps.style),
this.viewConfig.validAttributes
);
var props = null;
if (hasOnlyStyle) {
props = style;
} else if (!style) {
props = nativeProps;
} else {
props = mergeFast(nativeProps, style);
props = nativeProps;
if (hasProcessedProps) {
props = mergeFast(props, processedProps);
}
if (style) {
props = mergeFast(props, style);
}
}
RCTUIManager.updateView(

View File

@ -18,6 +18,7 @@ var createReactNativeComponentClass = require('createReactNativeComponentClass')
var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var processColor = require('processColor');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
var warning = require('warning');
@ -57,8 +58,22 @@ function requireNativeComponent(
viewConfig.validAttributes = {};
viewConfig.propTypes = componentInterface && componentInterface.propTypes;
for (var key in nativeProps) {
var useAttribute = false;
var attribute = {};
var differ = TypeToDifferMap[nativeProps[key]];
viewConfig.validAttributes[key] = differ ? {diff: differ} : true;
if (differ) {
attribute.diff = differ;
useAttribute = true;
}
var processor = TypeToProcessorMap[nativeProps[key]];
if (processor) {
attribute.process = processor;
useAttribute = true;
}
viewConfig.validAttributes[key] = useAttribute ? attribute : true;
}
if (__DEV__) {
componentInterface && verifyPropTypes(
@ -80,4 +95,14 @@ var TypeToDifferMap = {
// (not yet implemented)
};
var TypeToProcessorMap = {
// iOS Types
CGColor: processColor,
CGColorArray: processColor,
UIColor: processColor,
UIColorArray: processColor,
// Android Types
Color: processColor,
};
module.exports = requireNativeComponent;

View File

@ -158,13 +158,23 @@ ReactNativeBaseComponent.Mixin = {
validAttributes
);
for (var key in updatePayload) {
var process = validAttributes[key] && validAttributes[key].process;
if (process) {
updatePayload[key] = process(updatePayload[key]);
}
}
// The style property is a deeply nested element which includes numbers
// to represent static objects. Most of the time, it doesn't change across
// renders, so it's faster to spend the time checking if it is different
// before actually doing the expensive flattening operation in order to
// compute the diff.
if (styleDiffer(nextProps.style, prevProps.style)) {
var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style));
var nextFlattenedStyle = precomputeStyle(
flattenStyle(nextProps.style),
this.viewConfig.validAttributes
);
updatePayload = diffRawProperties(
updatePayload,
this.previousFlattenedStyle,

View File

@ -18,6 +18,7 @@ var ViewStylePropTypes = require('ViewStylePropTypes');
var keyMirror = require('keyMirror');
var matricesDiffer = require('matricesDiffer');
var processColor = require('processColor');
var sizesDiffer = require('sizesDiffer');
var ReactNativeStyleAttributes = {
@ -32,4 +33,16 @@ ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer };
// Do not rely on this attribute.
ReactNativeStyleAttributes.decomposedMatrix = 'decomposedMatrix';
var colorAttributes = { process: processColor };
ReactNativeStyleAttributes.backgroundColor = colorAttributes;
ReactNativeStyleAttributes.borderBottomColor = colorAttributes;
ReactNativeStyleAttributes.borderColor = colorAttributes;
ReactNativeStyleAttributes.borderLeftColor = colorAttributes;
ReactNativeStyleAttributes.borderRightColor = colorAttributes;
ReactNativeStyleAttributes.borderTopColor = colorAttributes;
ReactNativeStyleAttributes.color = colorAttributes;
ReactNativeStyleAttributes.shadowColor = colorAttributes;
ReactNativeStyleAttributes.textDecorationColor = colorAttributes;
ReactNativeStyleAttributes.tintColor = colorAttributes;
module.exports = ReactNativeStyleAttributes;

View File

@ -0,0 +1,118 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.autoMockOff();
var processColor = require('processColor');
describe('processColor', () => {
describe('predefined color names', () => {
it('should convert red', () => {
var colorFromString = processColor('red');
var expectedInt = 0xFFFF0000;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert white', () => {
var colorFromString = processColor('white');
var expectedInt = 0xFFFFFFFF;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert black', () => {
var colorFromString = processColor('black');
var expectedInt = 0xFF000000;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert transparent', () => {
var colorFromString = processColor('transparent');
var expectedInt = 0x00000000;
expect(colorFromString).toEqual(expectedInt);
});
});
describe('RGB strings', () => {
it('should convert rgb(x, y, z)', () => {
var colorFromString = processColor('rgb(10, 20, 30)');
var expectedInt = 0xFF0A141E;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert rgb x, y, z', () => {
var colorFromString = processColor('rgb 10, 20, 30');
var expectedInt = 0xFF0A141E;
expect(colorFromString).toEqual(expectedInt);
});
});
describe('RGBA strings', () => {
it('should convert rgba(x, y, z, a)', () => {
var colorFromString = processColor('rgba(10, 20, 30, 0.4)');
var expectedInt = 0x660A141E;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert rgba x, y, z, a', () => {
var colorFromString = processColor('rgba 10, 20, 30, 0.4');
var expectedInt = 0x660A141E;
expect(colorFromString).toEqual(expectedInt);
});
});
describe('HSL strings', () => {
it('should convert hsl(x, y%, z%)', () => {
var colorFromString = processColor('hsl(318, 69%, 55%)');
var expectedInt = 0xFFDB3DAC;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert hsl x, y%, z%', () => {
var colorFromString = processColor('hsl 318, 69%, 55%');
var expectedInt = 0xFFDB3DAC;
expect(colorFromString).toEqual(expectedInt);
});
});
describe('HSL strings', () => {
it('should convert hsl(x, y%, z%)', () => {
var colorFromString = processColor('hsla(318, 69%, 55%, 0.25)');
var expectedInt = 0x40DB3DAC;
expect(colorFromString).toEqual(expectedInt);
});
it('should convert hsl x, y%, z%', () => {
var colorFromString = processColor('hsla 318, 69%, 55%, 0.25');
var expectedInt = 0x40DB3DAC;
expect(colorFromString).toEqual(expectedInt);
});
});
describe('hex strings', () => {
it('should convert #xxxxxx', () => {
var colorFromString = processColor('#1e83c9');
var expectedInt = 0xFF1E83C9;
expect(colorFromString).toEqual(expectedInt);
});
});
});

View File

@ -12,6 +12,7 @@
'use strict';
var MatrixMath = require('MatrixMath');
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
var Platform = require('Platform');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
@ -22,19 +23,57 @@ var stringifySafe = require('stringifySafe');
* This method provides a hook where flattened styles may be precomputed or
* otherwise prepared to become better input data for native code.
*/
function precomputeStyle(style: ?Object): ?Object {
if (!style || !style.transform) {
function precomputeStyle(style: ?Object, validAttributes: Object): ?Object {
if (!style) {
return style;
}
var hasPreprocessKeys = false;
for (var i = 0, keys = Object.keys(style); i < keys.length; i++) {
var key = keys[i];
if (_processor(key, validAttributes)) {
hasPreprocessKeys = true;
break;
}
}
if (!hasPreprocessKeys && !style.transform) {
return style;
}
var newStyle = {...style};
for (var i = 0, keys = Object.keys(style); i < keys.length; i++) {
var key = keys[i];
var process = _processor(key, validAttributes);
if (process) {
newStyle[key] = process(newStyle[key]);
}
}
if (style.transform) {
invariant(
!style.transformMatrix,
'transformMatrix and transform styles cannot be used on the same component'
);
var newStyle = _precomputeTransforms({...style});
newStyle = _precomputeTransforms(newStyle);
}
deepFreezeAndThrowOnMutationInDev(newStyle);
return newStyle;
}
function _processor(key: string, validAttributes: Object) {
var process = validAttributes[key] && validAttributes[key].process;
if (!process) {
process =
ReactNativeStyleAttributes[key] &&
ReactNativeStyleAttributes[key].process;
}
return process;
}
/**
* Generate a transform matrix based on the provided transforms, and use that
* within the style object instead.

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule processColor
*/
'use strict';
var tinycolor = require('tinycolor2');
function processColor(color) {
if (!color || typeof color === 'number') {
return color;
} else if (color instanceof Array) {
return color.map(processColor);
} else {
var hexString = tinycolor(color).toHex8();
return parseInt(hexString, 16);
}
}
module.exports = processColor;

View File

@ -78,6 +78,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
NativeModules: require('NativeModules'),
Platform: require('Platform'),
processColor: require('processColor'),
requireNativeComponent: require('requireNativeComponent'),
// Prop Types

View File

@ -379,292 +379,21 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
+ (UIColor *)UIColor:(id)json
{
// Check color cache
static RCTCache *colorCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colorCache = [RCTCache new];
colorCache.countLimit = 128;
});
UIColor *color = colorCache[json];
if (color) {
return color;
}
if ([json isKindOfClass:[NSString class]]) {
// Check named colors
static NSDictionary *namedColors = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
namedColors = @{
// CSS colors
@"aliceblue": @"#f0f8ff",
@"antiquewhite": @"#faebd7",
@"aqua": @"#00ffff",
@"aquamarine": @"#7fffd4",
@"azure": @"#f0ffff",
@"beige": @"#f5f5dc",
@"bisque": @"#ffe4c4",
@"black": @"#000000",
@"blanchedalmond": @"#ffebcd",
@"blue": @"#0000ff",
@"blueviolet": @"#8a2be2",
@"brown": @"#a52a2a",
@"burlywood": @"#deb887",
@"cadetblue": @"#5f9ea0",
@"chartreuse": @"#7fff00",
@"chocolate": @"#d2691e",
@"coral": @"#ff7f50",
@"cornflowerblue": @"#6495ed",
@"cornsilk": @"#fff8dc",
@"crimson": @"#dc143c",
@"cyan": @"#00ffff",
@"darkblue": @"#00008b",
@"darkcyan": @"#008b8b",
@"darkgoldenrod": @"#b8860b",
@"darkgray": @"#a9a9a9",
@"darkgrey": @"#a9a9a9",
@"darkgreen": @"#006400",
@"darkkhaki": @"#bdb76b",
@"darkmagenta": @"#8b008b",
@"darkolivegreen": @"#556b2f",
@"darkorange": @"#ff8c00",
@"darkorchid": @"#9932cc",
@"darkred": @"#8b0000",
@"darksalmon": @"#e9967a",
@"darkseagreen": @"#8fbc8f",
@"darkslateblue": @"#483d8b",
@"darkslategray": @"#2f4f4f",
@"darkslategrey": @"#2f4f4f",
@"darkturquoise": @"#00ced1",
@"darkviolet": @"#9400d3",
@"deeppink": @"#ff1493",
@"deepskyblue": @"#00bfff",
@"dimgray": @"#696969",
@"dimgrey": @"#696969",
@"dodgerblue": @"#1e90ff",
@"firebrick": @"#b22222",
@"floralwhite": @"#fffaf0",
@"forestgreen": @"#228b22",
@"fuchsia": @"#ff00ff",
@"gainsboro": @"#dcdcdc",
@"ghostwhite": @"#f8f8ff",
@"gold": @"#ffd700",
@"goldenrod": @"#daa520",
@"gray": @"#808080",
@"grey": @"#808080",
@"green": @"#008000",
@"greenyellow": @"#adff2f",
@"honeydew": @"#f0fff0",
@"hotpink": @"#ff69b4",
@"indianred": @"#cd5c5c",
@"indigo": @"#4b0082",
@"ivory": @"#fffff0",
@"khaki": @"#f0e68c",
@"lavender": @"#e6e6fa",
@"lavenderblush": @"#fff0f5",
@"lawngreen": @"#7cfc00",
@"lemonchiffon": @"#fffacd",
@"lightblue": @"#add8e6",
@"lightcoral": @"#f08080",
@"lightcyan": @"#e0ffff",
@"lightgoldenrodyellow": @"#fafad2",
@"lightgray": @"#d3d3d3",
@"lightgrey": @"#d3d3d3",
@"lightgreen": @"#90ee90",
@"lightpink": @"#ffb6c1",
@"lightsalmon": @"#ffa07a",
@"lightseagreen": @"#20b2aa",
@"lightskyblue": @"#87cefa",
@"lightslategray": @"#778899",
@"lightslategrey": @"#778899",
@"lightsteelblue": @"#b0c4de",
@"lightyellow": @"#ffffe0",
@"lime": @"#00ff00",
@"limegreen": @"#32cd32",
@"linen": @"#faf0e6",
@"magenta": @"#ff00ff",
@"maroon": @"#800000",
@"mediumaquamarine": @"#66cdaa",
@"mediumblue": @"#0000cd",
@"mediumorchid": @"#ba55d3",
@"mediumpurple": @"#9370db",
@"mediumseagreen": @"#3cb371",
@"mediumslateblue": @"#7b68ee",
@"mediumspringgreen": @"#00fa9a",
@"mediumturquoise": @"#48d1cc",
@"mediumvioletred": @"#c71585",
@"midnightblue": @"#191970",
@"mintcream": @"#f5fffa",
@"mistyrose": @"#ffe4e1",
@"moccasin": @"#ffe4b5",
@"navajowhite": @"#ffdead",
@"navy": @"#000080",
@"oldlace": @"#fdf5e6",
@"olive": @"#808000",
@"olivedrab": @"#6b8e23",
@"orange": @"#ffa500",
@"orangered": @"#ff4500",
@"orchid": @"#da70d6",
@"palegoldenrod": @"#eee8aa",
@"palegreen": @"#98fb98",
@"paleturquoise": @"#afeeee",
@"palevioletred": @"#db7093",
@"papayawhip": @"#ffefd5",
@"peachpuff": @"#ffdab9",
@"peru": @"#cd853f",
@"pink": @"#ffc0cb",
@"plum": @"#dda0dd",
@"powderblue": @"#b0e0e6",
@"purple": @"#800080",
@"rebeccapurple": @"#663399",
@"red": @"#ff0000",
@"rosybrown": @"#bc8f8f",
@"royalblue": @"#4169e1",
@"saddlebrown": @"#8b4513",
@"salmon": @"#fa8072",
@"sandybrown": @"#f4a460",
@"seagreen": @"#2e8b57",
@"seashell": @"#fff5ee",
@"sienna": @"#a0522d",
@"silver": @"#c0c0c0",
@"skyblue": @"#87ceeb",
@"slateblue": @"#6a5acd",
@"slategray": @"#708090",
@"slategrey": @"#708090",
@"snow": @"#fffafa",
@"springgreen": @"#00ff7f",
@"steelblue": @"#4682b4",
@"tan": @"#d2b48c",
@"teal": @"#008080",
@"thistle": @"#d8bfd8",
@"tomato": @"#ff6347",
@"turquoise": @"#40e0d0",
@"violet": @"#ee82ee",
@"wheat": @"#f5deb3",
@"white": @"#ffffff",
@"whitesmoke": @"#f5f5f5",
@"yellow": @"#ffff00",
@"yellowgreen": @"#9acd32",
// Nonstandard color extensions
@"transparent": @"rgba(0,0,0,0)",
};
});
NSString *colorString = namedColors[json];
if (!colorString) {
colorString = json;
}
// Parse color
enum {
MODE_RGB = 0,
MODE_HSB = 1,
};
struct {
union {
struct {
double r, g, b;
} rgb;
struct {
double h, s, b;
} hsb;
};
double a;
unsigned int mode: 1;
} components = {
.a = 1.0,
.mode = MODE_RGB,
};
if ([colorString hasPrefix:@"#"]) {
uint32_t redInt = 0, greenInt = 0, blueInt = 0;
if (colorString.length == 4) { // 3 digit hex
sscanf(colorString.UTF8String, "#%01x%01x%01x", &redInt, &greenInt, &blueInt);
// expand to 6 digit hex
components.rgb.r = redInt / 15.0;
components.rgb.g = greenInt / 15.0;
components.rgb.b = blueInt / 15.0;
} else if (colorString.length == 7) { // 6 digit hex
sscanf(colorString.UTF8String, "#%02x%02x%02x", &redInt, &greenInt, &blueInt);
components.rgb.r = redInt / 255.0;
components.rgb.g = greenInt / 255.0;
components.rgb.b = blueInt / 255.0;
if ([json isKindOfClass:[NSArray class]]) {
NSArray *components = [self NSNumberArray:json];
CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
return [UIColor colorWithRed:[self CGFloat:components[0]]
green:[self CGFloat:components[1]]
blue:[self CGFloat:components[2]]
alpha:alpha];
} else {
RCTLogError(@"Invalid hex color %@. Hex colors should be 3 or 6 digits long.", colorString);
components.a = -1;
NSUInteger argb = [self NSUInteger:json];
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
} else if (4 == sscanf(colorString.UTF8String, "rgba(%lf,%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b, &components.a) ||
3 == sscanf(colorString.UTF8String, "rgb(%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b)) {
components.rgb.r /= 255.0;
components.rgb.g /= 255.0;
components.rgb.b /= 255.0;
} else if (4 == sscanf(colorString.UTF8String, "hsla(%lf,%lf%%,%lf%%,%lf)", &components.hsb.h, &components.hsb.s, &components.hsb.b, &components.a) ||
3 == sscanf(colorString.UTF8String, "hsl(%lf,%lf%%,%lf%%)", &components.hsb.h, &components.hsb.s, &components.hsb.b)) {
components.hsb.h /= 360.0;
components.hsb.s /= 100.0;
components.hsb.b /= 100.0;
components.mode = MODE_HSB;
} else {
RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb or a valid CSS color name.", colorString);
components.a = -1;
}
if (components.a < 0) {
RCTLogError(@"Invalid color string '%@'", colorString);
} else {
if (components.mode == MODE_RGB) {
color = [UIColor colorWithRed:components.rgb.r green:components.rgb.g blue:components.rgb.b alpha:components.a];
} else {
color = [UIColor colorWithHue:components.hsb.h saturation:components.hsb.s brightness:components.hsb.b alpha:components.a];
}
}
} else if ([json isKindOfClass:[NSArray class]]) {
if ([json count] < 3 || [json count] > 4) {
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
} else {
// Color array
color = [UIColor colorWithRed:[self CGFloat:json[0]]
green:[self CGFloat:json[1]]
blue:[self CGFloat:json[2]]
alpha:[json count] > 3 ? [self CGFloat:json[3]] : 1];
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
// Color dictionary
if (json[@"r"]) {
color = [UIColor colorWithRed:[self CGFloat:json[@"r"]]
green:[self CGFloat:json[@"g"]]
blue:[self CGFloat:json[@"b"]]
alpha:[self CGFloat:json[@"a"] ?: @1]];
} else if (json[@"h"]) {
color = [UIColor colorWithHue:[self CGFloat:json[@"h"]]
saturation:[self CGFloat:json[@"s"]]
brightness:[self CGFloat:json[@"b"]]
alpha:[self CGFloat:json[@"a"] ?: @1]];
} else {
RCTLogError(@"Expected dictionary with keys {r,g,b} or {h,s,b}, got: %@", [json allKeys]);
}
} else if (json) {
RCTLogConvertError(json, @"a color");
}
// Default color
if (!color) {
color = [UIColor whiteColor];
}
// Cache and return
if (json) {
colorCache[json] = color;
}
return color;
}
+ (CGColorRef)CGColor:(id)json

View File

@ -70,6 +70,7 @@
"semver": "^5.0.1",
"source-map": "^0.4.4",
"stacktrace-parser": "^0.1.3",
"tinycolor2": "^1.1.2",
"uglify-js": "^2.4.24",
"underscore": "^1.8.3",
"wordwrap": "^1.0.0",