// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617/**18* @fileoverview Utilities related to color and color conversion.19* Some of this code is borrowed and modified from goog.color and20* goog.color.alpha.21*/2223goog.provide('bot.color');2425goog.require('goog.array');26goog.require('goog.color.names');272829/**30* Returns a property, with a standardized color if it contains a31* convertible color.32* @param {string} propertyName Name of the CSS property in camelCase.33* @param {string} propertyValue The value of the CSS property.34* @return {string} The value, in a standardized format35* if it is a color property.36*/37bot.color.standardizeColor = function (propertyName, propertyValue) {38if (!goog.array.contains(bot.color.COLOR_PROPERTIES_, propertyName)) {39return propertyValue;40}41var rgba =42bot.color.maybeParseRgbaColor_(propertyValue) ||43bot.color.maybeParseRgbColor_(propertyValue) ||44bot.color.maybeConvertHexOrColorName_(propertyValue);45return rgba ? 'rgba(' + rgba.join(', ') + ')' : propertyValue;46};474849/**50* Used to determine whether a css property contains a color and51* should therefore be standardized to rgba.52* These are extracted from the W3C CSS spec:53*54* http://www.w3.org/TR/CSS/#properties55*56* @const57* @private {!Array.<string>}58*/59bot.color.COLOR_PROPERTIES_ = [60'backgroundColor',61'borderTopColor',62'borderRightColor',63'borderBottomColor',64'borderLeftColor',65'color',66'outlineColor'67];686970/**71* Regular expression for extracting the digits in a hex color triplet.72* @private {!RegExp}73* @const74*/75bot.color.HEX_TRIPLET_RE_ = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/;767778/**79* Converts a hex representation of a color to RGB.80* @param {string} hexOrColorName Color to convert.81* @return {?Array} array containing [r, g, b, 1] as ints in [0, 255] or null82* for invalid colors.83* @private84*/85bot.color.maybeConvertHexOrColorName_ = function (hexOrColorName) {86hexOrColorName = hexOrColorName.toLowerCase();87var hex = goog.color.names[hexOrColorName.toLowerCase()];88if (!hex) {89hex = hexOrColorName.charAt(0) == '#' ?90hexOrColorName : '#' + hexOrColorName;91if (hex.length == 4) { // of the form #RGB92hex = hex.replace(bot.color.HEX_TRIPLET_RE_, '#$1$1$2$2$3$3');93}9495if (!bot.color.VALID_HEX_COLOR_RE_.test(hex)) {96return null;97}98}99100var r = parseInt(hex.substr(1, 2), 16);101var g = parseInt(hex.substr(3, 2), 16);102var b = parseInt(hex.substr(5, 2), 16);103104return [r, g, b, 1];105};106107108/**109* Helper for isValidHexColor_.110* @private {!RegExp}111* @const112*/113bot.color.VALID_HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i;114115116/**117* Regular expression for matching and capturing RGBA style strings.118* @private {!RegExp}119* @const120*/121bot.color.RGBA_COLOR_RE_ =122/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i;123124125/**126* Attempts to parse a string as an rgba color. We expect strings of the127* format '(r, g, b, a)', or 'rgba(r, g, b, a)', where r, g, b are ints in128* [0, 255] and a is a float in [0, 1].129* @param {string} str String to check.130* @return {?Array.<number>} the integers [r, g, b, a] for valid colors or null131* for invalid colors.132* @private133*/134bot.color.maybeParseRgbaColor_ = function (str) {135// Each component is separate (rather than using a repeater) so we can136// capture the match. Also, we explicitly set each component to be either 0,137// or start with a non-zero, to prevent octal numbers from slipping through.138var regExpResultArray = str.match(bot.color.RGBA_COLOR_RE_);139if (regExpResultArray) {140var r = Number(regExpResultArray[1]);141var g = Number(regExpResultArray[2]);142var b = Number(regExpResultArray[3]);143var a = Number(regExpResultArray[4]);144if (r >= 0 && r <= 255 &&145g >= 0 && g <= 255 &&146b >= 0 && b <= 255 &&147a >= 0 && a <= 1) {148return [r, g, b, a];149}150}151return null;152};153154155/**156* Regular expression for matching and capturing RGB style strings.157* @private {!RegExp}158* @const159*/160bot.color.RGB_COLOR_RE_ =161/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;162163164/**165* Attempts to parse a string as an rgb color. We expect strings of the format166* '(r, g, b)', or 'rgb(r, g, b)', where each color component is an int in167* [0, 255].168* @param {string} str String to check.169* @return {?Array.<number>} the integers [r, g, b, 1] for valid colors or null170* for invalid colors.171* @private172*/173bot.color.maybeParseRgbColor_ = function (str) {174// Each component is separate (rather than using a repeater) so we can175// capture the match. Also, we explicitly set each component to be either 0,176// or start with a non-zero, to prevent octal numbers from slipping through.177var regExpResultArray = str.match(bot.color.RGB_COLOR_RE_);178if (regExpResultArray) {179var r = Number(regExpResultArray[1]);180var g = Number(regExpResultArray[2]);181var b = Number(regExpResultArray[3]);182if (r >= 0 && r <= 255 &&183g >= 0 && g <= 255 &&184b >= 0 && b <= 255) {185return [r, g, b, 1];186}187}188return null;189};190191192