Merge pull request #708 from usmonster/fix-firefox-gradients

Linear gradients now parse color names
This commit is contained in:
Niklas von Hertzen 2015-11-09 19:14:46 +02:00
commit 6d168f46be
7 changed files with 141 additions and 30 deletions

View File

@ -69,7 +69,7 @@ Color.prototype.hex6 = function(value) {
}; };
var _rgb = /^rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3})\)$/; var _rgb = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
Color.prototype.rgb = function(value) { Color.prototype.rgb = function(value) {
var match = null; var match = null;
@ -81,7 +81,7 @@ Color.prototype.rgb = function(value) {
return match !== null; return match !== null;
}; };
var _rgba = /^rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.?\d*)\)$/; var _rgba = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d?\.?\d+)\s*\)$/;
Color.prototype.rgba = function(value) { Color.prototype.rgba = function(value) {
var match = null; var match = null;
@ -101,12 +101,13 @@ Color.prototype.toString = function() {
}; };
Color.prototype.namedColor = function(value) { Color.prototype.namedColor = function(value) {
var color = colors[value.toLowerCase()]; value = value.toLowerCase();
var color = colors[value];
if (color) { if (color) {
this.r = color[0]; this.r = color[0];
this.g = color[1]; this.g = color[1];
this.b = color[2]; this.b = color[2];
} else if (value.toLowerCase() === "transparent") { } else if (value === "transparent") {
this.r = this.g = this.b = this.a = 0; this.r = this.g = this.b = this.a = 0;
return true; return true;
} }

View File

@ -9,9 +9,13 @@ function GradientContainer(imageData) {
this.promise = Promise.resolve(true); this.promise = Promise.resolve(true);
} }
GradientContainer.prototype.TYPES = { GradientContainer.TYPES = {
LINEAR: 1, LINEAR: 1,
RADIAL: 2 RADIAL: 2
}; };
// TODO: support hsl[a], negative %/length values
// TODO: support <angle> (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )
GradientContainer.REGEXP_COLORSTOP = /^\s*(rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[0-9\.]+)?\s*\)|[a-z]{3,20}|#[a-f0-9]{3,6})(?:\s+(\d{1,3}(?:\.\d+)?)(%|px)?)?(?:\s|$)/i;
module.exports = GradientContainer; module.exports = GradientContainer;

View File

@ -1,16 +1,15 @@
var GradientContainer = require('./gradientcontainer'); var GradientContainer = require('./gradientcontainer');
var Color = require('./color'); var Color = require('./color');
var COLOR_STOP_REGEXP = /^\s*(.*)\s*(\d{1,3})?(%|px)?$/;
function LinearGradientContainer(imageData) { function LinearGradientContainer(imageData) {
GradientContainer.apply(this, arguments); GradientContainer.apply(this, arguments);
this.type = this.TYPES.LINEAR; this.type = GradientContainer.TYPES.LINEAR;
var hasDirection = imageData.args[0].match(this.stepRegExp) === null; var hasDirection = LinearGradientContainer.REGEXP_DIRECTION.test( imageData.args[0] ) ||
!GradientContainer.REGEXP_COLORSTOP.test( imageData.args[0] );
if (hasDirection) { if (hasDirection) {
imageData.args[0].split(" ").reverse().forEach(function(position) { imageData.args[0].split(/\s+/).reverse().forEach(function(position, index) {
switch(position) { switch(position) {
case "left": case "left":
this.x0 = 0; this.x0 = 0;
@ -36,6 +35,24 @@ function LinearGradientContainer(imageData) {
this.x1 = x0; this.x1 = x0;
this.y1 = y0; this.y1 = y0;
break; break;
case "center":
break; // centered by default
// Firefox internally converts position keywords to percentages:
// http://www.w3.org/TR/2010/WD-CSS2-20101207/colors.html#propdef-background-position
default: // percentage or absolute length
// TODO: support absolute start point positions (e.g., use bounds to convert px to a ratio)
var ratio = parseFloat(position, 10) * 1e-2;
if (isNaN(ratio)) { // invalid or unhandled value
break;
}
if (index === 0) {
this.y0 = ratio;
this.y1 = 1 - this.y0;
} else {
this.x0 = ratio;
this.x1 = 1 - this.x0;
}
break;
} }
}, this); }, this);
} else { } else {
@ -43,15 +60,16 @@ function LinearGradientContainer(imageData) {
this.y1 = 1; this.y1 = 1;
} }
this.colorStops = imageData.args.slice(hasDirection ? 1 : 0) this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
.map(function(colorStop) { return colorStop.match(COLOR_STOP_REGEXP);}) var colorStopMatch = colorStop.match(GradientContainer.REGEXP_COLORSTOP);
.filter(function(colorStopMatch) { return !!colorStopMatch;}) var value = +colorStopMatch[2];
.map(function(colorStopMatch) { var unit = value === 0 ? "%" : colorStopMatch[3]; // treat "0" as "0%"
return { return {
color: new Color(colorStopMatch[1]), color: new Color(colorStopMatch[1]),
stop: colorStopMatch[3] === "%" ? colorStopMatch[2] / 100 : null // TODO: support absolute stop positions (e.g., compute gradient line length & convert px to ratio)
}; stop: unit === "%" ? value / 100 : null
}); };
});
if (this.colorStops[0].stop === null) { if (this.colorStops[0].stop === null) {
this.colorStops[0].stop = 0; this.colorStops[0].stop = 0;
@ -61,6 +79,7 @@ function LinearGradientContainer(imageData) {
this.colorStops[this.colorStops.length - 1].stop = 1; this.colorStops[this.colorStops.length - 1].stop = 1;
} }
// calculates and fills-in explicit stop positions when omitted from rule
this.colorStops.forEach(function(colorStop, index) { this.colorStops.forEach(function(colorStop, index) {
if (colorStop.stop === null) { if (colorStop.stop === null) {
this.colorStops.slice(index).some(function(find, count) { this.colorStops.slice(index).some(function(find, count) {
@ -77,6 +96,7 @@ function LinearGradientContainer(imageData) {
LinearGradientContainer.prototype = Object.create(GradientContainer.prototype); LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);
LinearGradientContainer.prototype.stepRegExp = /((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/; // TODO: support <angle> (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )
LinearGradientContainer.REGEXP_DIRECTION = /^\s*(?:to|left|right|top|bottom|center|\d{1,3}(?:\.\d+)?%?)(?:\s|$)/i;
module.exports = LinearGradientContainer; module.exports = LinearGradientContainer;

View File

@ -2,7 +2,7 @@ var GradientContainer = require('./gradientcontainer');
function WebkitGradientContainer(imageData) { function WebkitGradientContainer(imageData) {
GradientContainer.apply(this, arguments); GradientContainer.apply(this, arguments);
this.type = (imageData.args[0] === "linear") ? this.TYPES.LINEAR : this.TYPES.RADIAL; this.type = imageData.args[0] === "linear" ? GradientContainer.TYPES.LINEAR : GradientContainer.TYPES.RADIAL;
} }
WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype); WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);

View File

@ -112,10 +112,13 @@
.linearGradient8 { .linearGradient8 {
background: linear-gradient(to top left, #fff 0%, #00263c 100%); background: linear-gradient(to top left, #fff 0%, #00263c 100%);
} }
.linearGradient9 { .linearGradient9 {
background: linear-gradient(to top left, white 0%, black 100%); background: linear-gradient(to top left, white 0%, black 100%);
} }
.linearGradient10 {
background: linear-gradient(to left top, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
}
</style> </style>
</head> </head>
@ -130,6 +133,7 @@
<div class="linearGradient7">&nbsp;</div> <div class="linearGradient7">&nbsp;</div>
<div class="linearGradient8">&nbsp;</div> <div class="linearGradient8">&nbsp;</div>
<div class="linearGradient9">&nbsp;</div> <div class="linearGradient9">&nbsp;</div>
<div class="linearGradient10">&nbsp;</div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -9,6 +9,15 @@ describe("Gradients", function() {
" rgb(0, 255, 0)" " rgb(0, 255, 0)"
] ]
}, },
{
method: "linear-gradient",
args: [
"left",
" red",
" rgb(255, 255, 0)",
" rgb(0, 255, 0)"
]
},
{ {
method: 'linear-gradient', method: 'linear-gradient',
args: [ args: [
@ -23,6 +32,20 @@ describe("Gradients", function() {
" rgb(38, 85, 139) 100%" " rgb(38, 85, 139) 100%"
] ]
}, },
{
method: 'linear-gradient',
args: [
"left",
" rgb(206, 219, 233) 0%",
" rgb(170, 197, 222) 17px",
" rgb(97, 153, 199) 50%",
" rgb(58, 132, 195) 51px",
" rgb(65, 154, 214) 59%",
" rgb(75, 184, 240) 71px",
" rgb(58, 139, 194) 84%",
" rgb(38, 85, 139) 100px"
]
},
{ {
method: "gradient", method: "gradient",
args: [ args: [
@ -35,6 +58,20 @@ describe("Gradients", function() {
" to(rgb(191, 110, 78))" " to(rgb(191, 110, 78))"
] ]
}, },
{
method: "gradient",
args: [
"linear",
" 50% 0%",
" 50% 100%",
" from(rgb(255, 0, 0))",
" color-stop(0.314159, green)",
" color-stop(0.51, rgb(0, 0, 255))",
// temporary workaround for Blink/WebKit bug: crbug.com/453414
//" to(rgba(0, 0, 0, 0.5))"
" to(rgba(0, 0, 0, 0))"
]
},
{ {
method: 'linear-gradient', method: 'linear-gradient',
args: [ args: [

View File

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -67,7 +68,8 @@
<div style="background-position: left bottom;"></div> <div style="background-position: left bottom;"></div>
</div> </div>
<style> <div id="backgroundGradients">
<style scoped>
.linearGradientSimple { .linearGradientSimple {
/* FF 3.6+ */ /* FF 3.6+ */
background: -moz-linear-gradient(left, #ff0000, #ffff00, #00ff00); background: -moz-linear-gradient(left, #ff0000, #ffff00, #00ff00);
@ -82,6 +84,20 @@
/* W3C */ /* W3C */
background: linear-gradient(left, #ff0000, #ffff00, #00ff00); background: linear-gradient(left, #ff0000, #ffff00, #00ff00);
} }
.linearGradientSimple2 {
/* FF 3.6+ */
background: -moz-linear-gradient(left, red, #ff0, #0f0);
/* Chrome,Safari4+ */
background: -webkit-gradient(linear, left center, right center, color-stop(red), color-stop(#ff0), color-stop(#0f0));
/* Chrome 10+, Safari 5.1+ */
background: -webkit-linear-gradient(left, red, #ff0, #0f0);
/* Opera 11.10+ */
background: -o-linear-gradient(left, red, #ff0, #0f0);
/* IE 10+ */
background: -ms-linear-gradient(left, red, #ff0, #0f0);
/* W3C */
background: linear-gradient(left, red, #ff0, #0f0);
}
.linearGradientWithStops { .linearGradientWithStops {
/* FF 3.6+ */ /* FF 3.6+ */
background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%); background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
@ -96,7 +112,23 @@
/* W3C */ /* W3C */
background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%); background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
} }
.linearGradient3 { .linearGradientWithPixelLengthStops {
width: 100px;
height: 100px;
/* FF 3.6+ */
background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
/* Chrome, Safari 4+ */
background: -webkit-gradient(linear, left center, right center, color-stop(0%, #cedbe9), color-stop(17px, #aac5de), color-stop(50%, #6199c7), color-stop(51%, #3a84c3), color-stop(59%, #419ad6), color-stop(71px, #4bb8f0), color-stop(84%, #3a8bc2), color-stop(100px, #26558b));
/* Chrome 10+, Safari 5.1+ */
background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
/* Opera 11.10+ */
background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
/* IE 10+ */
background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
/* W3C */
background: linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
}
.linearGradient5 {
/* FF 3.6+ */ /* FF 3.6+ */
background: -moz-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%); background: -moz-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
/* Chrome, Safari 4+ */ /* Chrome, Safari 4+ */
@ -108,8 +140,19 @@
/* W3C */ /* W3C */
background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%); background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
} }
.linearGradient6 {
.linearGradient4 { /* FF 3.6+ */
background: -moz-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
/* Chrome, Safari 4+ */
background: -webkit-gradient(linear, center top, center bottom, color-stop(0, #F00), color-stop(31.4159%, green), color-stop(51%, #0000fF), color-stop(100%, rgba(0, 0, 0, 0.0)));
/* Opera 11.10+ */
background: -o-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
/* IE 10+ */
background: -ms-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
/* W3C */
background: linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
}
.linearGradient7 {
background: -webkit-linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%); background: -webkit-linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
background: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%); background: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
} }
@ -157,12 +200,14 @@
background: -ms-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%); background: -ms-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
background: radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%); background: radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
} }
</style> </style>
<div id="backgroundGradients">
<div class="linearGradientSimple"></div> <div class="linearGradientSimple"></div>
<div class="linearGradientSimple2"></div>
<div class="linearGradientWithStops"></div> <div class="linearGradientWithStops"></div>
<div class="linearGradient3"></div> <div class="linearGradientWithPixelLengthStops"></div>
<div class="linearGradient4"></div> <div class="linearGradient5"></div>
<div class="linearGradient6"></div>
<div class="linearGradient7"></div>
<div class="radialGradient"></div> <div class="radialGradient"></div>
<div class="radialGradient2"></div> <div class="radialGradient2"></div>
<div class="radialGradient3"></div> <div class="radialGradient3"></div>