2014-09-07 19:10:04 +00:00
|
|
|
// Copyright (c) 2014 Rafael Caricio. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2014-09-19 08:07:31 +00:00
|
|
|
var GradientParser = (GradientParser || {});
|
|
|
|
|
|
|
|
GradientParser.stringify = (function() {
|
|
|
|
|
|
|
|
var visitor = {
|
|
|
|
|
|
|
|
'visit_linear-gradient': function(node) {
|
2014-09-20 09:37:41 +00:00
|
|
|
return visitor.visit_gradient(node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_repeating-linear-gradient': function(node) {
|
|
|
|
return visitor.visit_gradient(node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_radial-gradient': function(node) {
|
|
|
|
return visitor.visit_gradient(node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_repeating-radial-gradient': function(node) {
|
|
|
|
return visitor.visit_gradient(node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_gradient': function(node) {
|
2014-09-19 08:07:31 +00:00
|
|
|
var orientation = visitor.visit(node.orientation);
|
|
|
|
if (orientation) {
|
|
|
|
orientation += ', ';
|
|
|
|
}
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
return node.type + '(' + orientation + visitor.visit(node.colorStops) + ')';
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_shape': function(node) {
|
|
|
|
var result = node.value,
|
|
|
|
at = visitor.visit(node.at),
|
|
|
|
style = visitor.visit(node.style);
|
|
|
|
|
|
|
|
if (style) {
|
|
|
|
result += ' ' + style;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at) {
|
|
|
|
result += ' at ' + at;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_default-radial': function(node) {
|
|
|
|
var result = '',
|
|
|
|
at = visitor.visit(node.at);
|
|
|
|
|
|
|
|
if (at) {
|
|
|
|
result += at;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_extent-keyword': function(node) {
|
2016-01-10 12:25:58 +00:00
|
|
|
var result = node.value,
|
|
|
|
at = visitor.visit(node.at);
|
|
|
|
|
|
|
|
if (at) {
|
|
|
|
result += ' at ' + at;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2014-09-19 08:07:31 +00:00
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_position-keyword': function(node) {
|
2014-09-19 08:07:31 +00:00
|
|
|
return node.value;
|
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_position': function(node) {
|
|
|
|
return visitor.visit(node.value.x) + ' ' + visitor.visit(node.value.y);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_%': function(node) {
|
|
|
|
return node.value + '%';
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_em': function(node) {
|
|
|
|
return node.value + 'em';
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_px': function(node) {
|
|
|
|
return node.value + 'px';
|
2014-09-19 17:36:13 +00:00
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_literal': function(node) {
|
|
|
|
return visitor.visit_color(node.value, node);
|
2014-09-19 17:36:13 +00:00
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_hex': function(node) {
|
|
|
|
return visitor.visit_color('#' + node.value, node);
|
2014-09-19 17:36:13 +00:00
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_rgb': function(node) {
|
|
|
|
return visitor.visit_color('rgb(' + node.value.join(', ') + ')', node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_rgba': function(node) {
|
|
|
|
return visitor.visit_color('rgba(' + node.value.join(', ') + ')', node);
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_color': function(resultColor, node) {
|
|
|
|
var result = resultColor,
|
|
|
|
length = visitor.visit(node.length);
|
|
|
|
|
|
|
|
if (length) {
|
|
|
|
result += ' ' + length;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
'visit_angular': function(node) {
|
|
|
|
return node.value + 'deg';
|
2014-09-19 17:36:13 +00:00
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_directional': function(node) {
|
2014-09-19 17:36:13 +00:00
|
|
|
return 'to ' + node.value;
|
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit_array': function(elements) {
|
2014-09-19 08:07:31 +00:00
|
|
|
var result = '',
|
|
|
|
size = elements.length;
|
|
|
|
|
|
|
|
elements.forEach(function(element, i) {
|
|
|
|
result += visitor.visit(element);
|
|
|
|
if (i < size - 1) {
|
|
|
|
result += ', ';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
2014-09-20 09:37:41 +00:00
|
|
|
'visit': function(element) {
|
2014-09-19 08:07:31 +00:00
|
|
|
if (!element) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (element instanceof Array) {
|
|
|
|
return visitor.visit_array(element, result);
|
|
|
|
} else if (element.type) {
|
|
|
|
var nodeVisitor = visitor['visit_' + element.type];
|
|
|
|
if (nodeVisitor) {
|
|
|
|
return nodeVisitor(element);
|
|
|
|
} else {
|
|
|
|
throw Error('Missing visitor visit_' + element.type);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw Error('Invalid node.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
return function(root) {
|
|
|
|
return visitor.visit(root);
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Copyright (c) 2014 Rafael Caricio. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
var GradientParser = (GradientParser || {});
|
2014-09-07 19:10:04 +00:00
|
|
|
|
|
|
|
GradientParser.parse = (function() {
|
|
|
|
|
|
|
|
var tokens = {
|
2014-09-11 15:46:08 +00:00
|
|
|
linearGradient: /^(\-(webkit|o|ms|moz)\-)?(linear\-gradient)/i,
|
|
|
|
repeatingLinearGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-linear\-gradient)/i,
|
|
|
|
radialGradient: /^(\-(webkit|o|ms|moz)\-)?(radial\-gradient)/i,
|
|
|
|
repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
|
2014-09-07 19:10:04 +00:00
|
|
|
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
|
|
|
|
extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,
|
|
|
|
positionKeywords: /^(left|center|right|top|bottom)/i,
|
2014-09-11 16:02:58 +00:00
|
|
|
pixelValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))px/,
|
|
|
|
percentageValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))\%/,
|
|
|
|
emValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))em/,
|
|
|
|
angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
|
2014-09-07 19:10:04 +00:00
|
|
|
startCall: /^\(/,
|
|
|
|
endCall: /^\)/,
|
|
|
|
comma: /^,/,
|
|
|
|
hexColor: /^\#([0-9a-fA-F]+)/,
|
|
|
|
literalColor: /^([a-zA-Z]+)/,
|
|
|
|
rgbColor: /^rgb/i,
|
|
|
|
rgbaColor: /^rgba/i,
|
|
|
|
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
|
|
|
|
};
|
|
|
|
|
|
|
|
var input = '';
|
|
|
|
|
|
|
|
function error(msg) {
|
|
|
|
var err = new Error(input + ': ' + msg);
|
|
|
|
err.source = input;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAST() {
|
|
|
|
var ast = matchListDefinitions();
|
|
|
|
|
|
|
|
if (input.length > 0) {
|
|
|
|
error('Invalid input not EOF');
|
|
|
|
}
|
|
|
|
|
|
|
|
return ast;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchListDefinitions() {
|
|
|
|
return matchListing(matchDefinition);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchDefinition() {
|
|
|
|
return matchGradient(
|
|
|
|
'linear-gradient',
|
|
|
|
tokens.linearGradient,
|
|
|
|
matchLinearOrientation) ||
|
|
|
|
|
|
|
|
matchGradient(
|
|
|
|
'repeating-linear-gradient',
|
|
|
|
tokens.repeatingLinearGradient,
|
|
|
|
matchLinearOrientation) ||
|
|
|
|
|
|
|
|
matchGradient(
|
|
|
|
'radial-gradient',
|
|
|
|
tokens.radialGradient,
|
|
|
|
matchListRadialOrientations) ||
|
|
|
|
|
|
|
|
matchGradient(
|
|
|
|
'repeating-radial-gradient',
|
|
|
|
tokens.repeatingRadialGradient,
|
|
|
|
matchListRadialOrientations);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchGradient(gradientType, pattern, orientationMatcher) {
|
|
|
|
return matchCall(pattern, function(captures) {
|
|
|
|
|
|
|
|
var orientation = orientationMatcher();
|
|
|
|
if (orientation) {
|
|
|
|
if (!scan(tokens.comma)) {
|
|
|
|
error('Missing comma before color stops');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
type: gradientType,
|
|
|
|
orientation: orientation,
|
|
|
|
colorStops: matchListing(matchColorStop)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchCall(pattern, callback) {
|
|
|
|
var captures = scan(pattern);
|
|
|
|
|
|
|
|
if (captures) {
|
|
|
|
if (!scan(tokens.startCall)) {
|
|
|
|
error('Missing (');
|
|
|
|
}
|
|
|
|
|
2021-02-01 10:21:42 +00:00
|
|
|
var result = callback(captures);
|
2014-09-07 19:10:04 +00:00
|
|
|
|
|
|
|
if (!scan(tokens.endCall)) {
|
|
|
|
error('Missing )');
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchLinearOrientation() {
|
|
|
|
return matchSideOrCorner() ||
|
|
|
|
matchAngle();
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchSideOrCorner() {
|
|
|
|
return match('directional', tokens.sideOrCorner, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchAngle() {
|
|
|
|
return match('angular', tokens.angleValue, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchListRadialOrientations() {
|
|
|
|
var radialOrientations,
|
|
|
|
radialOrientation = matchRadialOrientation(),
|
|
|
|
lookaheadCache;
|
|
|
|
|
|
|
|
if (radialOrientation) {
|
|
|
|
radialOrientations = [];
|
|
|
|
radialOrientations.push(radialOrientation);
|
|
|
|
|
|
|
|
lookaheadCache = input;
|
|
|
|
if (scan(tokens.comma)) {
|
|
|
|
radialOrientation = matchRadialOrientation();
|
|
|
|
if (radialOrientation) {
|
|
|
|
radialOrientations.push(radialOrientation);
|
|
|
|
} else {
|
|
|
|
input = lookaheadCache;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return radialOrientations;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchRadialOrientation() {
|
|
|
|
var radialType = matchCircle() ||
|
|
|
|
matchEllipse();
|
|
|
|
|
|
|
|
if (radialType) {
|
|
|
|
radialType.at = matchAtPosition();
|
|
|
|
} else {
|
2016-01-10 12:25:58 +00:00
|
|
|
var extent = matchExtentKeyword();
|
|
|
|
if (extent) {
|
|
|
|
radialType = extent;
|
|
|
|
var positionAt = matchAtPosition();
|
|
|
|
if (positionAt) {
|
|
|
|
radialType.at = positionAt;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var defaultPosition = matchPositioning();
|
|
|
|
if (defaultPosition) {
|
|
|
|
radialType = {
|
|
|
|
type: 'default-radial',
|
|
|
|
at: defaultPosition
|
|
|
|
};
|
|
|
|
}
|
2014-09-07 19:10:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return radialType;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchCircle() {
|
|
|
|
var circle = match('shape', /^(circle)/i, 0);
|
|
|
|
|
|
|
|
if (circle) {
|
|
|
|
circle.style = matchLength() || matchExtentKeyword();
|
|
|
|
}
|
|
|
|
|
|
|
|
return circle;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchEllipse() {
|
|
|
|
var ellipse = match('shape', /^(ellipse)/i, 0);
|
|
|
|
|
|
|
|
if (ellipse) {
|
|
|
|
ellipse.style = matchDistance() || matchExtentKeyword();
|
|
|
|
}
|
|
|
|
|
|
|
|
return ellipse;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchExtentKeyword() {
|
|
|
|
return match('extent-keyword', tokens.extentKeywords, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchAtPosition() {
|
|
|
|
if (match('position', /^at/, 0)) {
|
|
|
|
var positioning = matchPositioning();
|
|
|
|
|
|
|
|
if (!positioning) {
|
|
|
|
error('Missing positioning value');
|
|
|
|
}
|
|
|
|
|
|
|
|
return positioning;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchPositioning() {
|
|
|
|
var location = matchCoordinates();
|
|
|
|
|
|
|
|
if (location.x || location.y) {
|
|
|
|
return {
|
|
|
|
type: 'position',
|
|
|
|
value: location
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchCoordinates() {
|
|
|
|
return {
|
|
|
|
x: matchDistance(),
|
|
|
|
y: matchDistance()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchListing(matcher) {
|
|
|
|
var captures = matcher(),
|
|
|
|
result = [];
|
|
|
|
|
|
|
|
if (captures) {
|
|
|
|
result.push(captures);
|
|
|
|
while (scan(tokens.comma)) {
|
|
|
|
captures = matcher();
|
|
|
|
if (captures) {
|
|
|
|
result.push(captures);
|
|
|
|
} else {
|
|
|
|
error('One extra comma');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchColorStop() {
|
|
|
|
var color = matchColor();
|
|
|
|
|
|
|
|
if (!color) {
|
|
|
|
error('Expected color definition');
|
|
|
|
}
|
|
|
|
|
|
|
|
color.length = matchDistance();
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchColor() {
|
|
|
|
return matchHexColor() ||
|
|
|
|
matchRGBAColor() ||
|
|
|
|
matchRGBColor() ||
|
|
|
|
matchLiteralColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchLiteralColor() {
|
|
|
|
return match('literal', tokens.literalColor, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchHexColor() {
|
|
|
|
return match('hex', tokens.hexColor, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchRGBColor() {
|
|
|
|
return matchCall(tokens.rgbColor, function() {
|
|
|
|
return {
|
|
|
|
type: 'rgb',
|
|
|
|
value: matchListing(matchNumber)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchRGBAColor() {
|
|
|
|
return matchCall(tokens.rgbaColor, function() {
|
|
|
|
return {
|
|
|
|
type: 'rgba',
|
|
|
|
value: matchListing(matchNumber)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchNumber() {
|
|
|
|
return scan(tokens.number)[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchDistance() {
|
|
|
|
return match('%', tokens.percentageValue, 1) ||
|
|
|
|
matchPositionKeyword() ||
|
|
|
|
matchLength();
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchPositionKeyword() {
|
|
|
|
return match('position-keyword', tokens.positionKeywords, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchLength() {
|
|
|
|
return match('px', tokens.pixelValue, 1) ||
|
|
|
|
match('em', tokens.emValue, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function match(type, pattern, captureIndex) {
|
|
|
|
var captures = scan(pattern);
|
|
|
|
if (captures) {
|
|
|
|
return {
|
|
|
|
type: type,
|
|
|
|
value: captures[captureIndex]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function scan(regexp) {
|
|
|
|
var captures,
|
|
|
|
blankCaptures;
|
|
|
|
|
|
|
|
blankCaptures = /^[\n\r\t\s]+/.exec(input);
|
|
|
|
if (blankCaptures) {
|
|
|
|
consume(blankCaptures[0].length);
|
|
|
|
}
|
|
|
|
|
|
|
|
captures = regexp.exec(input);
|
|
|
|
if (captures) {
|
|
|
|
consume(captures[0].length);
|
|
|
|
}
|
|
|
|
|
|
|
|
return captures;
|
|
|
|
}
|
|
|
|
|
|
|
|
function consume(size) {
|
|
|
|
input = input.substr(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
return function(code) {
|
|
|
|
input = code.toString();
|
|
|
|
return getAST();
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2014-09-19 08:07:31 +00:00
|
|
|
exports.parse = GradientParser.parse;
|
|
|
|
exports.stringify = GradientParser.stringify;
|