diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js index da9671316064c0..342f0f82d41cdd 100644 --- a/Libraries/Animated/src/Interpolation.js +++ b/Libraries/Animated/src/Interpolation.js @@ -9,9 +9,10 @@ * @providesModule Interpolation * @flow */ +/* eslint no-bitwise: 0 */ 'use strict'; -var tinycolor = require('tinycolor'); +var normalizeColor = require('normalizeColor'); // TODO(#7644673): fix this hack once github jest actually checks invariants var invariant = function(condition, message) { @@ -164,16 +165,18 @@ function interpolate( return result; } -function colorToRgba( - input: string -): string { - var color = tinycolor(input); - if (color.isValid()) { - var {r, g, b, a} = color.toRgb(); - return `rgba(${r}, ${g}, ${b}, ${a === undefined ? 1 : a})`; - } else { +function colorToRgba(input: string): string { + var int32Color = normalizeColor(input); + if (int32Color === null) { return input; } + + var a = ((int32Color & 0xff000000) >>> 24) / 255; + var r = (int32Color & 0x00ff0000) >>> 16; + var g = (int32Color & 0x0000ff00) >>> 8; + var b = int32Color & 0x000000ff; + + return `rgba(${r}, ${g}, ${b}, ${a})`; } var stringShapeRegex = /[0-9\.-]+/g; diff --git a/Libraries/Animated/src/__tests__/Interpolation-test.js b/Libraries/Animated/src/__tests__/Interpolation-test.js index d6625f25074c05..afea659ed956f8 100644 --- a/Libraries/Animated/src/__tests__/Interpolation-test.js +++ b/Libraries/Animated/src/__tests__/Interpolation-test.js @@ -11,7 +11,7 @@ jest .dontMock('Interpolation') .dontMock('Easing') - .dontMock('tinycolor'); + .dontMock('normalizeColor'); var Interpolation = require('Interpolation'); var Easing = require('Easing'); @@ -216,12 +216,12 @@ describe('Interpolation', () => { it('should work with output ranges as string', () => { var interpolation = Interpolation.create({ inputRange: [0, 1], - outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'], + outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'], }); expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)'); - expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.25)'); - expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.5)'); + expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.2)'); + expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.4)'); }); it('should work with output ranges as short hex string', () => { @@ -249,11 +249,11 @@ describe('Interpolation', () => { it('should work with output ranges with mixed hex and rgba strings', () => { var interpolation = Interpolation.create({ inputRange: [0, 1], - outputRange: ['rgba(100, 120, 140, .5)', '#87FC70'], + outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'], }); - expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.5)'); - expect(interpolation(0.5)).toBe('rgba(117.5, 186, 126, 0.75)'); + expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.4)'); + expect(interpolation(0.5)).toBe('rgba(117.5, 186, 126, 0.7)'); expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)'); }); diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index 84789e6cf61dfe..e8261a17d31577 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -111,7 +111,7 @@ var TypeToProcessorMap = { CGColor: processColor, CGColorArray: processColor, UIColor: processColor, - UIColorArray: processColor, + UIColorArray: colors => colors && colors.map(processColor), CGImage: resolveAssetSource, UIImage: resolveAssetSource, RCTImageSource: resolveAssetSource, diff --git a/Libraries/StyleSheet/ColorPropType.js b/Libraries/StyleSheet/ColorPropType.js index 753b65c2d32026..d13e19afe2c625 100644 --- a/Libraries/StyleSheet/ColorPropType.js +++ b/Libraries/StyleSheet/ColorPropType.js @@ -9,26 +9,38 @@ * @providesModule ColorPropType */ 'use strict'; + var ReactPropTypes = require('ReactPropTypes'); -var tinycolor = require('tinycolor'); -var colorValidator = function (props, propName) { +var normalizeColor = require('normalizeColor'); + +var ColorPropType = function(props, propName) { var selectedColor = props[propName]; - if (selectedColor === null || selectedColor === undefined || selectedColor.toString().trim() === '') { - return new Error( - `Invalid argument supplied to ${propName}.Expected a string like #123ADF or 'red'.` - ); + if (selectedColor === undefined) { + return; } - if (tinycolor(selectedColor.toString().trim()).isValid()) { - return null; + if (typeof selectedColor === 'number') { + // Developers should not use a number, but we are using the prop type + // both for user provided colors and for transformed ones. This isn't ideal + // and should be fixed but will do for now... + return; } - return new Error( - `Invalid argument supplied to ${propName}.Expected a string like #123ADF or 'red'.` - ); + if (normalizeColor(selectedColor) === null) { + return new Error( +`Invalid color supplied to ${propName}: ${selectedColor}. Valid color formats are + - #f0f (#rgb) + - #f0fc (#rgba) + - #ff00ff (#rrggbb) + - #ff00ff00 (#rrggbbaa) + - rgb(255, 255, 255) + - rgba(255, 255, 255, 1.0) + - hsl(360, 100%, 100%) + - hsla(360, 100%, 100%, 1.0) + - transparent + - red`); + } }; -var ColorPropType = ReactPropTypes.oneOfType([colorValidator, ReactPropTypes.number]); - module.exports = ColorPropType; diff --git a/Libraries/StyleSheet/__tests__/normalizeColor-test.js b/Libraries/StyleSheet/__tests__/normalizeColor-test.js new file mode 100644 index 00000000000000..6f333fba00a501 --- /dev/null +++ b/Libraries/StyleSheet/__tests__/normalizeColor-test.js @@ -0,0 +1,112 @@ +/** + * 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.dontMock('normalizeColor'); + +var normalizeColor = require('normalizeColor'); + +describe('normalizeColor', function() { + it('should accept only spec compliant colors', function() { + expect(normalizeColor('#abc')).not.toBe(null); + expect(normalizeColor('#abcd')).not.toBe(null); + expect(normalizeColor('#abcdef')).not.toBe(null); + expect(normalizeColor('#abcdef01')).not.toBe(null); + expect(normalizeColor('rgb(1,2,3)')).not.toBe(null); + expect(normalizeColor('rgb(1, 2, 3)')).not.toBe(null); + expect(normalizeColor('rgb( 1 , 2 , 3 )')).not.toBe(null); + expect(normalizeColor('rgb(-1, -2, -3)')).not.toBe(null); + expect(normalizeColor('rgba(0, 0, 0, 1)')).not.toBe(null); + }); + + it('should refuse non spec compliant colors', function() { + expect(normalizeColor('#00gg00')).toBe(null); + expect(normalizeColor('rgb(1, 2, 3,)')).toBe(null); + expect(normalizeColor('rgb(1, 2, 3')).toBe(null); + + // Used to be accepted by normalizeColor + expect(normalizeColor('abc')).toBe(null); + expect(normalizeColor(' #abc ')).toBe(null); + expect(normalizeColor('##abc')).toBe(null); + expect(normalizeColor('rgb 255 0 0')).toBe(null); + expect(normalizeColor('RGBA(0, 1, 2)')).toBe(null); + expect(normalizeColor('rgb (0, 1, 2)')).toBe(null); + expect(normalizeColor('hsv(0, 1, 2)')).toBe(null); + expect(normalizeColor({r: 10, g: 10, b: 10})).toBe(null); + expect(normalizeColor('hsl(1%, 2, 3)')).toBe(null); + expect(normalizeColor('rgb(1.0, 2.0, 3.0)')).toBe(null); + expect(normalizeColor('rgb(1%, 2%, 3%)')).toBe(null); + }); + + it('should handle hex6 properly', function() { + expect(normalizeColor('#000000')).toBe(0xff000000); + expect(normalizeColor('#ffffff')).toBe(0xffffffff); + expect(normalizeColor('#ff00ff')).toBe(0xffff00ff); + expect(normalizeColor('#abcdef')).toBe(0xffabcdef); + expect(normalizeColor('#012345')).toBe(0xff012345); + }); + + it('should handle hex3 properly', function() { + expect(normalizeColor('#000')).toBe(0xff000000); + expect(normalizeColor('#fff')).toBe(0xffffffff); + expect(normalizeColor('#f0f')).toBe(0xffff00ff); + }); + + it('should handle hex8 properly', function() { + expect(normalizeColor('#00000000')).toBe(0x00000000); + expect(normalizeColor('#ffffffff')).toBe(0xffffffff); + expect(normalizeColor('#ffff00ff')).toBe(0xffffff00); + expect(normalizeColor('#abcdef01')).toBe(0x01abcdef); + expect(normalizeColor('#01234567')).toBe(0x67012345); + }); + + it('should handle rgb properly', function() { + expect(normalizeColor('rgb(0, 0, 0)')).toBe(0xff000000); + expect(normalizeColor('rgb(-1, -2, -3)')).toBe(0xff000000); + expect(normalizeColor('rgb(0, 0, 255)')).toBe(0xff0000ff); + expect(normalizeColor('rgb(100, 15, 69)')).toBe(0xff640f45); + expect(normalizeColor('rgb(255, 255, 255)')).toBe(0xffffffff); + expect(normalizeColor('rgb(256, 256, 256)')).toBe(0xffffffff); + }); + + it('should handle rgba properly', function() { + expect(normalizeColor('rgba(0, 0, 0, 0.0)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, 0)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, -0.5)')).toBe(0x00000000); + expect(normalizeColor('rgba(0, 0, 0, 1.0)')).toBe(0xff000000); + expect(normalizeColor('rgba(0, 0, 0, 1)')).toBe(0xff000000); + expect(normalizeColor('rgba(0, 0, 0, 1.5)')).toBe(0xff000000); + expect(normalizeColor('rgba(100, 15, 69, 0.5)')).toBe(0x80640f45); + }); + + it('should handle hsl properly', function() { + expect(normalizeColor('hsl(0, 0%, 0%)')).toBe(0xff000000); + expect(normalizeColor('hsl(360, 100%, 100%)')).toBe(0xffffffff); + expect(normalizeColor('hsl(180, 50%, 50%)')).toBe(0xff40bfbf); + expect(normalizeColor('hsl(540, 50%, 50%)')).toBe(0xff40bfbf); + expect(normalizeColor('hsl(70, 25%, 75%)')).toBe(0xffcacfaf); + expect(normalizeColor('hsl(70, 100%, 75%)')).toBe(0xffeaff80); + expect(normalizeColor('hsl(70, 110%, 75%)')).toBe(0xffeaff80); + expect(normalizeColor('hsl(70, 0%, 75%)')).toBe(0xffbfbfbf); + expect(normalizeColor('hsl(70, -10%, 75%)')).toBe(0xffbfbfbf); + }); + + it('should handle hsla properly', function() { + expect(normalizeColor('hsla(0, 0%, 0%, 0)')).toBe(0x00000000); + expect(normalizeColor('hsla(360, 100%, 100%, 1)')).toBe(0xffffffff); + expect(normalizeColor('hsla(360, 100%, 100%, 0)')).toBe(0x00ffffff); + expect(normalizeColor('hsla(180, 50%, 50%, 0.2)')).toBe(0x3340bfbf); + }); + + it('should handle named colors properly', function() { + expect(normalizeColor('red')).toBe(0xffff0000); + expect(normalizeColor('transparent')).toBe(0x00000000); + expect(normalizeColor('peachpuff')).toBe(0xffffdab9); + }); +}); diff --git a/Libraries/StyleSheet/__tests__/processColor-test.js b/Libraries/StyleSheet/__tests__/processColor-test.js index 9416353b79b612..79682bbacc518a 100644 --- a/Libraries/StyleSheet/__tests__/processColor-test.js +++ b/Libraries/StyleSheet/__tests__/processColor-test.js @@ -49,12 +49,6 @@ describe('processColor', () => { 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', () => { @@ -65,12 +59,6 @@ describe('processColor', () => { 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', () => { @@ -81,12 +69,6 @@ describe('processColor', () => { 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', () => { @@ -97,12 +79,6 @@ describe('processColor', () => { expect(colorFromString).toEqual(expectedInt); }); - it('should convert hsla x, y%, z%, a', () => { - var colorFromString = processColor('hsla 318, 69%, 55%, 0.25'); - var expectedInt = 0x40DB3DAC; - expect(colorFromString).toEqual(expectedInt); - }); - }); describe('hex strings', () => { diff --git a/Libraries/StyleSheet/normalizeColor.js b/Libraries/StyleSheet/normalizeColor.js new file mode 100755 index 00000000000000..417fc5d3d0d151 --- /dev/null +++ b/Libraries/StyleSheet/normalizeColor.js @@ -0,0 +1,347 @@ +/** + * 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 normalizeColor + * @flow + */ +/* eslint no-bitwise: 0 */ +'use strict'; + +function normalizeColor(color: string): ?number { + var match; + + // Ordered based on occurrences on Facebook codebase + if ((match = matchers.hex6.exec(color))) { + return 0xff000000 + parseInt(match[1], 16); + } + + if (names.hasOwnProperty(color)) { + return names[color]; + } + + if ((match = matchers.rgb.exec(color))) { + return ( + 0xff000000 + // a + parse255(match[1]) * (1 << 16) + // r + parse255(match[2]) * (1 << 8) + // g + parse255(match[3]) // b + ); + } + + if ((match = matchers.rgba.exec(color))) { + return ( + parse1(match[4]) * (1 << 24) + // a + parse255(match[1]) * (1 << 16) + // r + parse255(match[2]) * (1 << 8) + // g + parse255(match[3]) // b + ); + } + + if ((match = matchers.hex3.exec(color))) { + return ( + parseInt( + 'ff' + // a + match[1] + match[1] + // r + match[2] + match[2] + // g + match[3] + match[3], // b + 16 + ) + ); + } + + // https://drafts.csswg.org/css-color-4/#hex-notation + if ((match = matchers.hex8.exec(color))) { + var number = parseInt(match[1], 16); + // Convert 0xrrggbbaa into 0xaarrggbb + return (number << 24 | number >>> 8) >>> 0; + } + + if ((match = matchers.hex4.exec(color))) { + return ( + parseInt( + match[4] + match[4] + // a + match[1] + match[1] + // r + match[2] + match[2] + // g + match[3] + match[3], // b + 16 + ) + ); + } + + if ((match = matchers.hsl.exec(color))) { + return ( + 0xff000000 + // a + hslToRgb( + parse360(match[1]), // h + parsePercentage(match[2]), // s + parsePercentage(match[3]) // l + ) + ); + } + + if ((match = matchers.hsla.exec(color))) { + return ( + parse1(match[4]) * (1 << 24) + // a + hslToRgb( + parse360(match[1]), // h + parsePercentage(match[2]), // s + parsePercentage(match[3]) // l + ) + ); + } + + return null; +} + +function hue2rgb(p: number, q: number, t: number): number { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} + +function hslToRgb(h: number, s: number, l: number): number { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + var r = hue2rgb(p, q, h + 1 / 3); + var g = hue2rgb(p, q, h); + var b = hue2rgb(p, q, h - 1 / 3); + + return ( + Math.round(r * 255) * (1 << 16) + + Math.round(g * 255) * (1 << 8) + + Math.round(b * 255) + ); +} + +var INTEGER = '[-+]?\\d+'; +var NUMBER = '[-+]?\\d*\\.?\\d+'; +var PERCENTAGE = NUMBER + '%'; + +function call(...args) { + return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'; +} + +var matchers = { + rgb: new RegExp('rgb' + call(INTEGER, INTEGER, INTEGER)), + rgba: new RegExp('rgba' + call(INTEGER, INTEGER, INTEGER, NUMBER)), + hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)), + hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)), + hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^#([0-9a-fA-F]{6})$/, + hex8: /^#([0-9a-fA-F]{8})$/, +}; + +function parse255(str: string): number { + var int = parseInt(str, 10); + if (int < 0) { + return 0; + } + if (int > 255) { + return 255; + } + return int; +} + +function parse360(str: string): number { + var int = parseFloat(str); + return (((int % 360) + 360) % 360) / 360; +} + +function parse1(str: string): number { + var num = parseFloat(str); + if (num < 0) { + return 0; + } + if (num > 1) { + return 255; + } + return Math.round(num * 255); +} + +function parsePercentage(str: string): number { + // parseFloat conveniently ignores the final % + var int = parseFloat(str, 10); + if (int < 0) { + return 0; + } + if (int > 100) { + return 1; + } + return int / 100; +} + +var names = { + transparent: 0x00000000, + + // http://www.w3.org/TR/css3-color/#svg-color + aliceblue: 0xfff0f8ff, + antiquewhite: 0xfffaebd7, + aqua: 0xff00ffff, + aquamarine: 0xff7fffd4, + azure: 0xfff0ffff, + beige: 0xfff5f5dc, + bisque: 0xffffe4c4, + black: 0xff000000, + blanchedalmond: 0xffffebcd, + blue: 0xff0000ff, + blueviolet: 0xff8a2be2, + brown: 0xffa52a2a, + burlywood: 0xffdeb887, + burntsienna: 0xffea7e5d, + cadetblue: 0xff5f9ea0, + chartreuse: 0xff7fff00, + chocolate: 0xffd2691e, + coral: 0xffff7f50, + cornflowerblue: 0xff6495ed, + cornsilk: 0xfffff8dc, + crimson: 0xffdc143c, + cyan: 0xff00ffff, + darkblue: 0xff00008b, + darkcyan: 0xff008b8b, + darkgoldenrod: 0xffb8860b, + darkgray: 0xffa9a9a9, + darkgreen: 0xff006400, + darkgrey: 0xffa9a9a9, + darkkhaki: 0xffbdb76b, + darkmagenta: 0xff8b008b, + darkolivegreen: 0xff556b2f, + darkorange: 0xffff8c00, + darkorchid: 0xff9932cc, + darkred: 0xff8b0000, + darksalmon: 0xffe9967a, + darkseagreen: 0xff8fbc8f, + darkslateblue: 0xff483d8b, + darkslategray: 0xff2f4f4f, + darkslategrey: 0xff2f4f4f, + darkturquoise: 0xff00ced1, + darkviolet: 0xff9400d3, + deeppink: 0xffff1493, + deepskyblue: 0xff00bfff, + dimgray: 0xff696969, + dimgrey: 0xff696969, + dodgerblue: 0xff1e90ff, + firebrick: 0xffb22222, + floralwhite: 0xfffffaf0, + forestgreen: 0xff228b22, + fuchsia: 0xffff00ff, + gainsboro: 0xffdcdcdc, + ghostwhite: 0xfff8f8ff, + gold: 0xffffd700, + goldenrod: 0xffdaa520, + gray: 0xff808080, + green: 0xff008000, + greenyellow: 0xffadff2f, + grey: 0xff808080, + honeydew: 0xfff0fff0, + hotpink: 0xffff69b4, + indianred: 0xffcd5c5c, + indigo: 0xff4b0082, + ivory: 0xfffffff0, + khaki: 0xfff0e68c, + lavender: 0xffe6e6fa, + lavenderblush: 0xfffff0f5, + lawngreen: 0xff7cfc00, + lemonchiffon: 0xfffffacd, + lightblue: 0xffadd8e6, + lightcoral: 0xfff08080, + lightcyan: 0xffe0ffff, + lightgoldenrodyellow: 0xfffafad2, + lightgray: 0xffd3d3d3, + lightgreen: 0xff90ee90, + lightgrey: 0xffd3d3d3, + lightpink: 0xffffb6c1, + lightsalmon: 0xffffa07a, + lightseagreen: 0xff20b2aa, + lightskyblue: 0xff87cefa, + lightslategray: 0xff778899, + lightslategrey: 0xff778899, + lightsteelblue: 0xffb0c4de, + lightyellow: 0xffffffe0, + lime: 0xff00ff00, + limegreen: 0xff32cd32, + linen: 0xfffaf0e6, + magenta: 0xffff00ff, + maroon: 0xff800000, + mediumaquamarine: 0xff66cdaa, + mediumblue: 0xff0000cd, + mediumorchid: 0xffba55d3, + mediumpurple: 0xff9370db, + mediumseagreen: 0xff3cb371, + mediumslateblue: 0xff7b68ee, + mediumspringgreen: 0xff00fa9a, + mediumturquoise: 0xff48d1cc, + mediumvioletred: 0xffc71585, + midnightblue: 0xff191970, + mintcream: 0xfff5fffa, + mistyrose: 0xffffe4e1, + moccasin: 0xffffe4b5, + navajowhite: 0xffffdead, + navy: 0xff000080, + oldlace: 0xfffdf5e6, + olive: 0xff808000, + olivedrab: 0xff6b8e23, + orange: 0xffffa500, + orangered: 0xffff4500, + orchid: 0xffda70d6, + palegoldenrod: 0xffeee8aa, + palegreen: 0xff98fb98, + paleturquoise: 0xffafeeee, + palevioletred: 0xffdb7093, + papayawhip: 0xffffefd5, + peachpuff: 0xffffdab9, + peru: 0xffcd853f, + pink: 0xffffc0cb, + plum: 0xffdda0dd, + powderblue: 0xffb0e0e6, + purple: 0xff800080, + rebeccapurple: 0xff663399, + red: 0xffff0000, + rosybrown: 0xffbc8f8f, + royalblue: 0xff4169e1, + saddlebrown: 0xff8b4513, + salmon: 0xfffa8072, + sandybrown: 0xfff4a460, + seagreen: 0xff2e8b57, + seashell: 0xfffff5ee, + sienna: 0xffa0522d, + silver: 0xffc0c0c0, + skyblue: 0xff87ceeb, + slateblue: 0xff6a5acd, + slategray: 0xff708090, + slategrey: 0xff708090, + snow: 0xfffffafa, + springgreen: 0xff00ff7f, + steelblue: 0xff4682b4, + tan: 0xffd2b48c, + teal: 0xff008080, + thistle: 0xffd8bfd8, + tomato: 0xffff6347, + turquoise: 0xff40e0d0, + violet: 0xffee82ee, + wheat: 0xfff5deb3, + white: 0xffffffff, + whitesmoke: 0xfff5f5f5, + yellow: 0xffffff00, + yellowgreen: 0xff9acd32, +}; + +module.exports = normalizeColor; diff --git a/Libraries/StyleSheet/processColor.js b/Libraries/StyleSheet/processColor.js index 09ea0369733c7e..5fe52c234d7df7 100644 --- a/Libraries/StyleSheet/processColor.js +++ b/Libraries/StyleSheet/processColor.js @@ -10,33 +10,29 @@ */ 'use strict'; -var tinycolor = require('tinycolor'); var Platform = require('Platform'); +var normalizeColor = require('normalizeColor'); + /* eslint no-bitwise: 0 */ function processColor(color) { - if (!color || typeof color === 'number') { + if (color === undefined) { return color; - } else if (color instanceof Array) { - return color.map(processColor); - } else { - var color = tinycolor(color); - if (color.isValid()) { - var rgb = color.toRgb(); - // All bitwise operations happen on 32-bit numbers, so we shift the 1 first - // then multiply it with the actual value. - var colorInt = Math.round(rgb.a * 255) * (1 << 24) + rgb.r * (1 << 16) + rgb.g * (1 << 8) + rgb.b; - if (Platform.OS === 'android') { - // Android use 32 bit *signed* integer to represent the color - // We utilize the fact that bitwise operations in JS also operates on - // signed 32 bit integers, so that we can use those to convert from - // *unsigned* to *signed* 32bit int that way. - colorInt = colorInt | 0x0; - } - return colorInt; - } - return 0; } + + var int32Color = normalizeColor(color); + if (int32Color === null) { + return undefined; + } + + if (Platform.OS === 'android') { + // Android use 32 bit *signed* integer to represent the color + // We utilize the fact that bitwise operations in JS also operates on + // signed 32 bit integers, so that we can use those to convert from + // *unsigned* to *signed* 32bit int that way. + int32Color = int32Color | 0x0; + } + return int32Color; } module.exports = processColor; diff --git a/Libraries/vendor/tinycolor/tinycolor.js b/Libraries/vendor/tinycolor/tinycolor.js deleted file mode 100755 index 7035bc166fba65..00000000000000 --- a/Libraries/vendor/tinycolor/tinycolor.js +++ /dev/null @@ -1,503 +0,0 @@ -/** - * @providesModule tinycolor - * @nolint - */ -'use strict'; - -// TinyColor v1.2.1 -// https://github.com/bgrins/TinyColor -// Brian Grinstead, MIT License - -var trimLeft = /^[\s,#]+/, - trimRight = /\s+$/, - tinyCounter = 0, - mathRound = Math.round, - mathMin = Math.min, - mathMax = Math.max; - -function tinycolor (color, opts) { - // If we are called as a function, call using new instead - if (!(this instanceof tinycolor)) { - return new tinycolor(color, opts); - } - - color = (color) ? color : ''; - opts = opts || { }; - - var rgb = inputToRGB(color); - this._r = rgb.r, - this._g = rgb.g, - this._b = rgb.b, - this._a = rgb.a, - - this._ok = rgb.ok; -} - -tinycolor.prototype = { - toRgb: function() { - return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; - }, - isValid: function() { - return this._ok; - }, -}; - -// Given a string or object, convert that input to RGB -// Possible string inputs: -// -// "red" -// "#f00" or "f00" -// "#f00f" or "f00f" -// "#ff0000" or "ff0000" -// "#ff0000ff" or "ff0000ff" -// "rgb 255 0 0" or "rgb (255, 0, 0)" -// "rgb 1.0 0 0" or "rgb (1, 0, 0)" -// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" -// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" -// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" -// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" -// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" -// -function inputToRGB(color) { - var rgb = { r: 0, g: 0, b: 0 }; - var a = 1; - var ok = false; - var format = false; - - if (typeof color == "string") { - color = stringInputToObject(color); - } - - if (typeof color == "object") { - if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { - rgb = rgbToRgb(color.r, color.g, color.b); - ok = true; - } - else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { - color.s = convertToPercentage(color.s); - color.v = convertToPercentage(color.v); - rgb = hsvToRgb(color.h, color.s, color.v); - ok = true; - } - else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { - color.s = convertToPercentage(color.s); - color.l = convertToPercentage(color.l); - rgb = hslToRgb(color.h, color.s, color.l); - ok = true; - } - - if (color.hasOwnProperty("a")) { - a = color.a; - } - } - - a = boundAlpha(a); - - return { - ok: ok, - r: mathMin(255, mathMax(rgb.r, 0)), - g: mathMin(255, mathMax(rgb.g, 0)), - b: mathMin(255, mathMax(rgb.b, 0)), - a: a - }; -} - - -// Conversion Functions -// -------------------- - -// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: -// - -// `rgbToRgb` -// Handle bounds / percentage checking to conform to CSS color spec -// -// *Assumes:* r, g, b in [0, 255] or [0, 1] -// *Returns:* { r, g, b } in [0, 255] -function rgbToRgb(r, g, b){ - return { - r: bound01(r, 255) * 255, - g: bound01(g, 255) * 255, - b: bound01(b, 255) * 255 - }; -} - -// `hslToRgb` -// Converts an HSL color value to RGB. -// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] -// *Returns:* { r, g, b } in the set [0, 255] -function hslToRgb(h, s, l) { - var r, g, b; - - h = bound01(h, 360); - s = bound01(s, 100); - l = bound01(l, 100); - - function hue2rgb(p, q, t) { - if(t < 0) t += 1; - if(t > 1) t -= 1; - if(t < 1/6) return p + (q - p) * 6 * t; - if(t < 1/2) return q; - if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - } - - if(s === 0) { - r = g = b = l; // achromatic - } - else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return { r: r * 255, g: g * 255, b: b * 255 }; -} - -// `hsvToRgb` -// Converts an HSV color value to RGB. -// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] -// *Returns:* { r, g, b } in the set [0, 255] - function hsvToRgb(h, s, v) { - h = bound01(h, 360) * 6; - s = bound01(s, 100); - v = bound01(v, 100); - - var i = math.floor(h), - f = h - i, - p = v * (1 - s), - q = v * (1 - f * s), - t = v * (1 - (1 - f) * s), - mod = i % 6, - r = [v, q, p, p, t, v][mod], - g = [t, v, v, q, p, p][mod], - b = [p, p, t, v, v, q][mod]; - - return { r: r * 255, g: g * 255, b: b * 255 }; -} - -// Big List of Colors -// ------------------ -// -var names = tinycolor.names = { - aliceblue: "f0f8ff", - antiquewhite: "faebd7", - aqua: "0ff", - aquamarine: "7fffd4", - azure: "f0ffff", - beige: "f5f5dc", - bisque: "ffe4c4", - black: "000", - blanchedalmond: "ffebcd", - blue: "00f", - blueviolet: "8a2be2", - brown: "a52a2a", - burlywood: "deb887", - burntsienna: "ea7e5d", - cadetblue: "5f9ea0", - chartreuse: "7fff00", - chocolate: "d2691e", - coral: "ff7f50", - cornflowerblue: "6495ed", - cornsilk: "fff8dc", - crimson: "dc143c", - cyan: "0ff", - darkblue: "00008b", - darkcyan: "008b8b", - darkgoldenrod: "b8860b", - darkgray: "a9a9a9", - darkgreen: "006400", - darkgrey: "a9a9a9", - 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: "f0f", - gainsboro: "dcdcdc", - ghostwhite: "f8f8ff", - gold: "ffd700", - goldenrod: "daa520", - gray: "808080", - green: "008000", - greenyellow: "adff2f", - grey: "808080", - 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", - lightgreen: "90ee90", - lightgrey: "d3d3d3", - lightpink: "ffb6c1", - lightsalmon: "ffa07a", - lightseagreen: "20b2aa", - lightskyblue: "87cefa", - lightslategray: "789", - lightslategrey: "789", - lightsteelblue: "b0c4de", - lightyellow: "ffffe0", - lime: "0f0", - limegreen: "32cd32", - linen: "faf0e6", - magenta: "f0f", - 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: "f00", - 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: "fff", - whitesmoke: "f5f5f5", - yellow: "ff0", - yellowgreen: "9acd32" -}; - -// Return a valid alpha value [0,1] with all invalid values being set to 1 -function boundAlpha(a) { - a = parseFloat(a); - - if (isNaN(a) || a < 0 || a > 1) { - a = 1; - } - - return a; -} - -// Take input from [0, n] and return it as [0, 1] -function bound01(n, max) { - if (isOnePointZero(n)) { n = "100%"; } - - var processPercent = isPercentage(n); - n = mathMin(max, mathMax(0, parseFloat(n))); - - // Automatically convert percentage into number - if (processPercent) { - n = parseInt(n * max, 10) / 100; - } - - // Handle floating point rounding errors - if ((Math.abs(n - max) < 0.000001)) { - return 1; - } - - // Convert into [0, 1] range if it isn't already - return (n % max) / parseFloat(max); -} - -// Parse a base-16 hex value into a base-10 integer -function parseIntFromHex(val) { - return parseInt(val, 16); -} - -// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 -// -function isOnePointZero(n) { - return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; -} - -// Check to see if string passed in is a percentage -function isPercentage(n) { - return typeof n === "string" && n.indexOf('%') != -1; -} - -// Replace a decimal with it's percentage value -function convertToPercentage(n) { - if (n <= 1) { - n = (n * 100) + "%"; - } - - return n; -} - -// Converts a hex value to a decimal -function convertHexToDecimal(h) { - return (parseIntFromHex(h) / 255); -} - -var matchers = (function() { - // - var CSS_INTEGER = "[-\\+]?\\d+%?"; - - // - var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; - - // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. - var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; - - // Actual matching. - // Parentheses and commas are optional, but not required. - // Whitespace can take the place of commas or opening paren - var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; - var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; - - return { - rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), - rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), - hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), - hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), - hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), - hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), - hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, - hex4: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, - hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, - hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, - }; -})(); - -// `stringInputToObject` -// Permissive string parsing. Take in a number of formats, and output an object -// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` -function stringInputToObject(color) { - color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); - var named = false; - if (names[color]) { - color = names[color]; - named = true; - } - else if (color == 'transparent') { - return { r: 0, g: 0, b: 0, a: 0, format: "name" }; - } - - // Try to match string input using regular expressions. - // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] - // Just return an object and let the conversion functions handle that. - // This way the result will be the same whether the tinycolor is initialized with string or object. - var match; - if ((match = matchers.rgb.exec(color))) { - return { r: match[1], g: match[2], b: match[3] }; - } - if ((match = matchers.rgba.exec(color))) { - return { r: match[1], g: match[2], b: match[3], a: match[4] }; - } - if ((match = matchers.hsl.exec(color))) { - return { h: match[1], s: match[2], l: match[3] }; - } - if ((match = matchers.hsla.exec(color))) { - return { h: match[1], s: match[2], l: match[3], a: match[4] }; - } - if ((match = matchers.hsv.exec(color))) { - return { h: match[1], s: match[2], v: match[3] }; - } - if ((match = matchers.hsva.exec(color))) { - return { h: match[1], s: match[2], v: match[3], a: match[4] }; - } - if ((match = matchers.hex8.exec(color))) { - return { - r: parseIntFromHex(match[1]), - g: parseIntFromHex(match[2]), - b: parseIntFromHex(match[3]), - a: convertHexToDecimal(match[4]), - format: named ? "name" : "hex" - }; - } - if ((match = matchers.hex6.exec(color))) { - return { - r: parseIntFromHex(match[1]), - g: parseIntFromHex(match[2]), - b: parseIntFromHex(match[3]), - format: named ? "name" : "hex" - }; - } - if ((match = matchers.hex4.exec(color))) { - return { - r: parseIntFromHex(match[1] + '' + match[1]), - g: parseIntFromHex(match[2] + '' + match[2]), - b: parseIntFromHex(match[3] + '' + match[3]), - a: convertHexToDecimal(match[4] + '' + match[4]), - format: named ? "name" : "hex" - }; - } - if ((match = matchers.hex3.exec(color))) { - return { - r: parseIntFromHex(match[1] + '' + match[1]), - g: parseIntFromHex(match[2] + '' + match[2]), - b: parseIntFromHex(match[3] + '' + match[3]), - format: named ? "name" : "hex" - }; - } - - return false; -} - -module.exports = tinycolor;