| // 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; |