|  | // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  | /** | 
|  | * @unrestricted | 
|  | */ | 
|  | export class CSSShadowModel { | 
|  | /** | 
|  | * @param {boolean} isBoxShadow | 
|  | */ | 
|  | constructor(isBoxShadow) { | 
|  | this._isBoxShadow = isBoxShadow; | 
|  | this._inset = false; | 
|  | this._offsetX = InlineEditor.CSSLength.zero(); | 
|  | this._offsetY = InlineEditor.CSSLength.zero(); | 
|  | this._blurRadius = InlineEditor.CSSLength.zero(); | 
|  | this._spreadRadius = InlineEditor.CSSLength.zero(); | 
|  | this._color = /** @type {!Common.Color} */ (Common.Color.parse('black')); | 
|  | this._format = [_Part.OffsetX, _Part.OffsetY]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} text | 
|  | * @return {!Array<!CSSShadowModel>} | 
|  | */ | 
|  | static parseTextShadow(text) { | 
|  | return CSSShadowModel._parseShadow(text, false); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} text | 
|  | * @return {!Array<!CSSShadowModel>} | 
|  | */ | 
|  | static parseBoxShadow(text) { | 
|  | return CSSShadowModel._parseShadow(text, true); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} text | 
|  | * @param {boolean} isBoxShadow | 
|  | * @return {!Array<!CSSShadowModel>} | 
|  | */ | 
|  | static _parseShadow(text, isBoxShadow) { | 
|  | const shadowTexts = []; | 
|  | // Split by commas that aren't inside of color values to get the individual shadow values. | 
|  | const splits = TextUtils.TextUtils.splitStringByRegexes(text, [Common.Color.Regex, /,/g]); | 
|  | let currentIndex = 0; | 
|  | for (let i = 0; i < splits.length; i++) { | 
|  | if (splits[i].regexIndex === 1) { | 
|  | const comma = splits[i]; | 
|  | shadowTexts.push(text.substring(currentIndex, comma.position)); | 
|  | currentIndex = comma.position + 1; | 
|  | } | 
|  | } | 
|  | shadowTexts.push(text.substring(currentIndex, text.length)); | 
|  |  | 
|  | const shadows = []; | 
|  | for (let i = 0; i < shadowTexts.length; i++) { | 
|  | const shadow = new CSSShadowModel(isBoxShadow); | 
|  | shadow._format = []; | 
|  | let nextPartAllowed = true; | 
|  | const regexes = [/inset/gi, Common.Color.Regex, InlineEditor.CSSLength.Regex]; | 
|  | const results = TextUtils.TextUtils.splitStringByRegexes(shadowTexts[i], regexes); | 
|  | for (let j = 0; j < results.length; j++) { | 
|  | const result = results[j]; | 
|  | if (result.regexIndex === -1) { | 
|  | // Don't allow anything other than inset, color, length values, and whitespace. | 
|  | if (/\S/.test(result.value)) { | 
|  | return []; | 
|  | } | 
|  | // All parts must be separated by whitespace. | 
|  | nextPartAllowed = true; | 
|  | } else { | 
|  | if (!nextPartAllowed) { | 
|  | return []; | 
|  | } | 
|  | nextPartAllowed = false; | 
|  |  | 
|  | if (result.regexIndex === 0) { | 
|  | shadow._inset = true; | 
|  | shadow._format.push(_Part.Inset); | 
|  | } else if (result.regexIndex === 1) { | 
|  | const color = Common.Color.parse(result.value); | 
|  | if (!color) { | 
|  | return []; | 
|  | } | 
|  | shadow._color = color; | 
|  | shadow._format.push(_Part.Color); | 
|  | } else if (result.regexIndex === 2) { | 
|  | const length = InlineEditor.CSSLength.parse(result.value); | 
|  | if (!length) { | 
|  | return []; | 
|  | } | 
|  | const previousPart = shadow._format.length > 0 ? shadow._format[shadow._format.length - 1] : ''; | 
|  | if (previousPart === _Part.OffsetX) { | 
|  | shadow._offsetY = length; | 
|  | shadow._format.push(_Part.OffsetY); | 
|  | } else if (previousPart === _Part.OffsetY) { | 
|  | shadow._blurRadius = length; | 
|  | shadow._format.push(_Part.BlurRadius); | 
|  | } else if (previousPart === _Part.BlurRadius) { | 
|  | shadow._spreadRadius = length; | 
|  | shadow._format.push(_Part.SpreadRadius); | 
|  | } else { | 
|  | shadow._offsetX = length; | 
|  | shadow._format.push(_Part.OffsetX); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (invalidCount(shadow, _Part.OffsetX, 1, 1) || invalidCount(shadow, _Part.OffsetY, 1, 1) || | 
|  | invalidCount(shadow, _Part.Color, 0, 1) || invalidCount(shadow, _Part.BlurRadius, 0, 1) || | 
|  | invalidCount(shadow, _Part.Inset, 0, isBoxShadow ? 1 : 0) || | 
|  | invalidCount(shadow, _Part.SpreadRadius, 0, isBoxShadow ? 1 : 0)) { | 
|  | return []; | 
|  | } | 
|  | shadows.push(shadow); | 
|  | } | 
|  | return shadows; | 
|  |  | 
|  | /** | 
|  | * @param {!CSSShadowModel} shadow | 
|  | * @param {string} part | 
|  | * @param {number} min | 
|  | * @param {number} max | 
|  | * @return {boolean} | 
|  | */ | 
|  | function invalidCount(shadow, part, min, max) { | 
|  | let count = 0; | 
|  | for (let i = 0; i < shadow._format.length; i++) { | 
|  | if (shadow._format[i] === part) { | 
|  | count++; | 
|  | } | 
|  | } | 
|  | return count < min || count > max; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {boolean} inset | 
|  | */ | 
|  | setInset(inset) { | 
|  | this._inset = inset; | 
|  | if (this._format.indexOf(_Part.Inset) === -1) { | 
|  | this._format.unshift(_Part.Inset); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!InlineEditor.CSSLength} offsetX | 
|  | */ | 
|  | setOffsetX(offsetX) { | 
|  | this._offsetX = offsetX; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!InlineEditor.CSSLength} offsetY | 
|  | */ | 
|  | setOffsetY(offsetY) { | 
|  | this._offsetY = offsetY; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!InlineEditor.CSSLength} blurRadius | 
|  | */ | 
|  | setBlurRadius(blurRadius) { | 
|  | this._blurRadius = blurRadius; | 
|  | if (this._format.indexOf(_Part.BlurRadius) === -1) { | 
|  | const yIndex = this._format.indexOf(_Part.OffsetY); | 
|  | this._format.splice(yIndex + 1, 0, _Part.BlurRadius); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!InlineEditor.CSSLength} spreadRadius | 
|  | */ | 
|  | setSpreadRadius(spreadRadius) { | 
|  | this._spreadRadius = spreadRadius; | 
|  | if (this._format.indexOf(_Part.SpreadRadius) === -1) { | 
|  | this.setBlurRadius(this._blurRadius); | 
|  | const blurIndex = this._format.indexOf(_Part.BlurRadius); | 
|  | this._format.splice(blurIndex + 1, 0, _Part.SpreadRadius); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Color} color | 
|  | */ | 
|  | setColor(color) { | 
|  | this._color = color; | 
|  | if (this._format.indexOf(_Part.Color) === -1) { | 
|  | this._format.push(_Part.Color); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isBoxShadow() { | 
|  | return this._isBoxShadow; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | inset() { | 
|  | return this._inset; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!InlineEditor.CSSLength} | 
|  | */ | 
|  | offsetX() { | 
|  | return this._offsetX; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!InlineEditor.CSSLength} | 
|  | */ | 
|  | offsetY() { | 
|  | return this._offsetY; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!InlineEditor.CSSLength} | 
|  | */ | 
|  | blurRadius() { | 
|  | return this._blurRadius; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!InlineEditor.CSSLength} | 
|  | */ | 
|  | spreadRadius() { | 
|  | return this._spreadRadius; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Common.Color} | 
|  | */ | 
|  | color() { | 
|  | return this._color; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {string} | 
|  | */ | 
|  | asCSSText() { | 
|  | const parts = []; | 
|  | for (let i = 0; i < this._format.length; i++) { | 
|  | const part = this._format[i]; | 
|  | if (part === _Part.Inset && this._inset) { | 
|  | parts.push('inset'); | 
|  | } else if (part === _Part.OffsetX) { | 
|  | parts.push(this._offsetX.asCSSText()); | 
|  | } else if (part === _Part.OffsetY) { | 
|  | parts.push(this._offsetY.asCSSText()); | 
|  | } else if (part === _Part.BlurRadius) { | 
|  | parts.push(this._blurRadius.asCSSText()); | 
|  | } else if (part === _Part.SpreadRadius) { | 
|  | parts.push(this._spreadRadius.asCSSText()); | 
|  | } else if (part === _Part.Color) { | 
|  | parts.push(this._color.asString(this._color.format())); | 
|  | } | 
|  | } | 
|  | return parts.join(' '); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @enum {string} | 
|  | */ | 
|  | export const _Part = { | 
|  | Inset: 'I', | 
|  | OffsetX: 'X', | 
|  | OffsetY: 'Y', | 
|  | BlurRadius: 'B', | 
|  | SpreadRadius: 'S', | 
|  | Color: 'C' | 
|  | }; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * @unrestricted | 
|  | */ | 
|  | export class CSSLength { | 
|  | /** | 
|  | * @param {number} amount | 
|  | * @param {string} unit | 
|  | */ | 
|  | constructor(amount, unit) { | 
|  | this.amount = amount; | 
|  | this.unit = unit; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} text | 
|  | * @return {?InlineEditor.CSSLength} | 
|  | */ | 
|  | static parse(text) { | 
|  | const lengthRegex = new RegExp('^(?:' + InlineEditor.CSSLength.Regex.source + ')$', 'i'); | 
|  | const match = text.match(lengthRegex); | 
|  | if (!match) { | 
|  | return null; | 
|  | } | 
|  | if (match.length > 2 && match[2]) { | 
|  | return new InlineEditor.CSSLength(parseFloat(match[1]), match[2]); | 
|  | } | 
|  | return InlineEditor.CSSLength.zero(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!InlineEditor.CSSLength} | 
|  | */ | 
|  | static zero() { | 
|  | return new InlineEditor.CSSLength(0, ''); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {string} | 
|  | */ | 
|  | asCSSText() { | 
|  | return this.amount + this.unit; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @type {!RegExp} */ | 
|  | CSSLength.Regex = (function() { | 
|  | const number = '([+-]?(?:[0-9]*[.])?[0-9]+(?:[eE][+-]?[0-9]+)?)'; | 
|  | const unit = '(ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmax|vmin|vw)'; | 
|  | const zero = '[+-]?(?:0*[.])?0+(?:[eE][+-]?[0-9]+)?'; | 
|  | return new RegExp(number + unit + '|' + zero, 'gi'); | 
|  | })(); | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | self.InlineEditor = self.InlineEditor || {}; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | InlineEditor = InlineEditor || {}; | 
|  |  | 
|  | /** @constructor */ | 
|  | InlineEditor.CSSShadowModel = CSSShadowModel; | 
|  |  | 
|  | /** @enum {string} */ | 
|  | InlineEditor.CSSShadowModel._Part = _Part; | 
|  |  | 
|  | /** @constructor */ | 
|  | InlineEditor.CSSLength = CSSLength; |