/* * Copyright (C) 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.Gradient = class Gradient { constructor(type, stops) { this.type = type; this.stops = stops; } // Static static fromString(cssString) { var type; var openingParenthesisIndex = cssString.indexOf("("); var typeString = cssString.substring(0, openingParenthesisIndex); if (typeString.indexOf(WebInspector.Gradient.Types.Linear) !== -1) type = WebInspector.Gradient.Types.Linear; else if (typeString.indexOf(WebInspector.Gradient.Types.Radial) !== -1) type = WebInspector.Gradient.Types.Radial; else return null; var components = []; var currentParams = []; var currentParam = ""; var openParentheses = 0; var ch = openingParenthesisIndex + 1; var c = null; while (c = cssString[ch]) { if (c === "(") openParentheses++; if (c === ")") openParentheses--; var isComma = c === ","; var isSpace = /\s/.test(c); if (openParentheses === 0) { if (isSpace) { if (currentParam !== "") currentParams.push(currentParam); currentParam = ""; } else if (isComma) { currentParams.push(currentParam); components.push(currentParams); currentParams = []; currentParam = ""; } } if (openParentheses === -1) { currentParams.push(currentParam); components.push(currentParams); break; } if (openParentheses > 0 || (!isComma && !isSpace)) currentParam += c; ch++; } if (openParentheses !== -1) return null; var gradient; if (type === WebInspector.Gradient.Types.Linear) gradient = WebInspector.LinearGradient.fromComponents(components); else gradient = WebInspector.RadialGradient.fromComponents(components); if (gradient) gradient.repeats = typeString.startsWith("repeating"); return gradient; } static stopsWithComponents(components) { // FIXME: handle lengths. var stops = components.map(function(component) { while (component.length) { var color = WebInspector.Color.fromString(component.shift()); if (!color) continue; var stop = {color}; if (component.length && component[0].substr(-1) === "%") stop.offset = parseFloat(component.shift()) / 100; return stop; } }); if (!stops.length) return null; for (var i = 0, count = stops.length; i < count; ++i) { var stop = stops[i]; // If one of the stops failed to parse, then this is not a valid // set of components for a gradient. So the whole thing is invalid. if (!stop) return null; if (!stop.offset) stop.offset = i / (count - 1); } return stops; } // Public stringFromStops(stops) { var count = stops.length - 1; return stops.map(function(stop, index) { var str = stop.color; if (stop.offset !== index / count) str += " " + Math.round(stop.offset * 10000) / 100 + "%"; return str; }).join(", "); } // Public copy() { // Implemented by subclasses. } toString() { // Implemented by subclasses. } }; WebInspector.Gradient.Types = { Linear: "linear-gradient", Radial: "radial-gradient" }; WebInspector.LinearGradient = class LinearGradient extends WebInspector.Gradient { constructor(angle, stops) { super(WebInspector.Gradient.Types.Linear, stops); this._angle = angle; } // Static static fromComponents(components) { let angle = {value: 180, units: WebInspector.LinearGradient.AngleUnits.DEG}; if (components[0].length === 1 && !WebInspector.Color.fromString(components[0][0])) { let match = components[0][0].match(/([-\d\.]+)(\w+)/); if (!match || !Object.values(WebInspector.LinearGradient.AngleUnits).includes(match[2])) return null; angle.value = parseFloat(match[1]); angle.units = match[2]; components.shift(); } else if (components[0][0] === "to") { components[0].shift(); switch (components[0].sort().join(" ")) { case "top": angle.value = 0; break; case "right top": angle.value = 45; break; case "right": angle.value = 90; break; case "bottom right": angle.value = 135; break; case "bottom": angle.value = 180; break; case "bottom left": angle.value = 225; break; case "left": angle.value = 270; break; case "left top": angle.value = 315; break; default: return null; } components.shift(); } else if (components[0].length !== 1 && !WebInspector.Color.fromString(components[0][0])) { // If the first component is not a color, then we're dealing with a // legacy linear gradient format that we don't support. return null; } var stops = WebInspector.Gradient.stopsWithComponents(components); if (!stops) return null; return new WebInspector.LinearGradient(angle, stops); } // Public set angleValue(value) { this._angle.value = value; } get angleValue() { return this._angle.value.maxDecimals(2); } set angleUnits(units) { if (units === this._angle.units) return; this._angle.value = this._angleValueForUnits(units); this._angle.units = units; } get angleUnits() { return this._angle.units; } copy() { return new WebInspector.LinearGradient(this._angle, this.stops.concat()); } toString() { let str = ""; let deg = this._angleValueForUnits(WebInspector.LinearGradient.AngleUnits.DEG); if (deg === 0) str += "to top"; else if (deg === 45) str += "to top right"; else if (deg === 90) str += "to right"; else if (deg === 135) str += "to bottom right"; else if (deg === 225) str += "to bottom left"; else if (deg === 270) str += "to left"; else if (deg === 315) str += "to top left"; else if (deg !== 180) str += this.angleValue + this.angleUnits; if (str !== "") str += ", "; str += this.stringFromStops(this.stops); return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")"; } // Private _angleValueForUnits(units) { if (units === this._angle.units) return this._angle.value; let deg = 0; switch (this._angle.units) { case WebInspector.LinearGradient.AngleUnits.DEG: deg = this._angle.value; break; case WebInspector.LinearGradient.AngleUnits.RAD: deg = this._angle.value * 180 / Math.PI; break; case WebInspector.LinearGradient.AngleUnits.GRAD: deg = this._angle.value / 400 * 360; break; case WebInspector.LinearGradient.AngleUnits.TURN: deg = this._angle.value * 360; break; default: WebInspector.reportInternalError(`Unknown angle units "${this._angle.units}"`); return 0; } let value = 0; switch (units) { case WebInspector.LinearGradient.AngleUnits.DEG: value = deg; break; case WebInspector.LinearGradient.AngleUnits.RAD: value = deg * Math.PI / 180; break; case WebInspector.LinearGradient.AngleUnits.GRAD: value = deg / 360 * 400; break; case WebInspector.LinearGradient.AngleUnits.TURN: value = deg / 360; break; } return value; } }; WebInspector.LinearGradient.AngleUnits = { DEG: "deg", RAD: "rad", GRAD: "grad", TURN: "turn", }; WebInspector.RadialGradient = class RadialGradient extends WebInspector.Gradient { constructor(sizing, stops) { super(WebInspector.Gradient.Types.Radial, stops); this.sizing = sizing; } // Static static fromComponents(components) { var sizing = !WebInspector.Color.fromString(components[0].join(" ")) ? components.shift().join(" ") : ""; var stops = WebInspector.Gradient.stopsWithComponents(components); if (!stops) return null; return new WebInspector.RadialGradient(sizing, stops); } // Public copy() { return new WebInspector.RadialGradient(this.sizing, this.stops.concat()); } toString() { var str = this.sizing; if (str !== "") str += ", "; str += this.stringFromStops(this.stops); return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")"; } };