blob: 7a2d93269c7573faf8b31ad9ff91f74f2420d5cf [file] [log] [blame]
/*
* 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.
*/
// FIXME: Convert to class?
WebInspector.Gradient = {
Types: {
Linear: "linear-gradient",
Radial: "radial-gradient"
},
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 {
console.error("Couldn't parse angle \"" + typeString + "\"");
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++;
}
var gradient;
if (type === WebInspector.Gradient.Types.Linear)
gradient = WebInspector.LinearGradient.linearGradientWithComponents(components);
else
gradient = WebInspector.RadialGradient.radialGradientWithComponents(components);
if (gradient)
gradient.repeats = typeString.indexOf("repeating") === 0;
return gradient;
},
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) {
console.error("Couldn't parse any stops");
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;
},
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(", ");
}
};
WebInspector.LinearGradient = class LinearGradient
{
constructor(angle, stops)
{
this.type = WebInspector.Gradient.Types.Linear;
this.angle = angle;
this.stops = stops;
}
// Static
static linearGradientWithComponents(components)
{
var angle = 180;
if (components[0].length === 1 && components[0][0].substr(-3) === "deg") {
angle = (parseFloat(components[0][0]) % 360 + 360) % 360;
components.shift();
} else if (components[0][0] === "to") {
components[0].shift();
switch (components[0].sort().join(" ")) {
case "top":
angle = 0;
break;
case "right top":
angle = 45;
break;
case "right":
angle = 90;
break;
case "bottom right":
angle = 135;
break;
case "bottom":
angle = 180;
break;
case "bottom left":
angle = 225;
break;
case "left":
angle = 270;
break;
case "left top":
angle = 315;
break;
default:
console.error("Couldn't parse angle \"to " + components[0].join(" ") + "\"");
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
copy()
{
return new WebInspector.LinearGradient(this.angle, this.stops.concat());
}
toString()
{
var str = "";
if (this.angle === 0)
str += "to top";
else if (this.angle === 45)
str += "to top right";
else if (this.angle === 90)
str += "to right";
else if (this.angle === 135)
str += "to bottom right";
else if (this.angle === 225)
str += "to bottom left";
else if (this.angle === 270)
str += "to left";
else if (this.angle === 315)
str += "to top left";
else if (this.angle !== 180)
str += this.angle + "deg";
if (str !== "")
str += ", ";
str += WebInspector.Gradient.stringFromStops(this.stops);
return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")";
}
};
WebInspector.RadialGradient = class RadialGradient
{
constructor(sizing, stops)
{
this.type = WebInspector.Gradient.Types.Radial;
this.sizing = sizing;
this.stops = stops;
}
// Static
static radialGradientWithComponents(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 += WebInspector.Gradient.stringFromStops(this.stops);
return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")";
}
};