Linear gradients now parse color names

Also:
- Cleans up color stop and linear gradient regular expressions.
- Handles percentage-based linear gradient positions (fixes Firefox).

Fixes niklasvh/html2canvas#469.
This commit is contained in:
Usman Akeju
2015-01-20 17:29:42 +01:00
committed by usmonster
parent bebb353b3f
commit 318ca48157
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) {
var match = null;
@ -81,7 +81,7 @@ Color.prototype.rgb = function(value) {
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) {
var match = null;
@ -101,12 +101,13 @@ Color.prototype.toString = function() {
};
Color.prototype.namedColor = function(value) {
var color = colors[value.toLowerCase()];
value = value.toLowerCase();
var color = colors[value];
if (color) {
this.r = color[0];
this.g = color[1];
this.b = color[2];
} else if (value.toLowerCase() === "transparent") {
} else if (value === "transparent") {
this.r = this.g = this.b = this.a = 0;
return true;
}

View File

@ -9,9 +9,13 @@ function GradientContainer(imageData) {
this.promise = Promise.resolve(true);
}
GradientContainer.prototype.TYPES = {
GradientContainer.TYPES = {
LINEAR: 1,
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;

View File

@ -1,16 +1,15 @@
var GradientContainer = require('./gradientcontainer');
var Color = require('./color');
var COLOR_STOP_REGEXP = /^\s*(.*)\s*(\d{1,3})?(%|px)?$/;
function LinearGradientContainer(imageData) {
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) {
imageData.args[0].split(" ").reverse().forEach(function(position) {
imageData.args[0].split(/\s+/).reverse().forEach(function(position, index) {
switch(position) {
case "left":
this.x0 = 0;
@ -36,6 +35,24 @@ function LinearGradientContainer(imageData) {
this.x1 = x0;
this.y1 = y0;
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);
} else {
@ -43,15 +60,16 @@ function LinearGradientContainer(imageData) {
this.y1 = 1;
}
this.colorStops = imageData.args.slice(hasDirection ? 1 : 0)
.map(function(colorStop) { return colorStop.match(COLOR_STOP_REGEXP);})
.filter(function(colorStopMatch) { return !!colorStopMatch;})
.map(function(colorStopMatch) {
return {
color: new Color(colorStopMatch[1]),
stop: colorStopMatch[3] === "%" ? colorStopMatch[2] / 100 : null
};
});
this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
var colorStopMatch = colorStop.match(GradientContainer.REGEXP_COLORSTOP);
var value = +colorStopMatch[2];
var unit = value === 0 ? "%" : colorStopMatch[3]; // treat "0" as "0%"
return {
color: new Color(colorStopMatch[1]),
// 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) {
this.colorStops[0].stop = 0;
@ -61,6 +79,7 @@ function LinearGradientContainer(imageData) {
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) {
if (colorStop.stop === null) {
this.colorStops.slice(index).some(function(find, count) {
@ -77,6 +96,7 @@ function LinearGradientContainer(imageData) {
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;

View File

@ -2,7 +2,7 @@ var GradientContainer = require('./gradientcontainer');
function WebkitGradientContainer(imageData) {
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);