gradient-parser/lib/parser.js

347 lines
7.7 KiB
JavaScript
Raw Normal View History

// 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.parse = (function() {
2014-09-05 00:02:54 +00:00
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,
2021-07-20 12:35:29 +00:00
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
2014-09-07 13:50:31 +00:00
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-05 00:02:54 +00:00
startCall: /^\(/,
endCall: /^\)/,
2014-09-06 13:53:38 +00:00
comma: /^,/,
hexColor: /^\#([0-9a-fA-F]+)/,
2014-09-06 15:24:00 +00:00
literalColor: /^([a-zA-Z]+)/,
rgbColor: /^rgb/i,
rgbaColor: /^rgba/i,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
2014-09-05 00:02:54 +00:00
};
2014-09-06 16:02:18 +00:00
var input = '';
2014-09-05 16:56:36 +00:00
function error(msg) {
2014-09-06 16:02:18 +00:00
var err = new Error(input + ': ' + msg);
2014-09-05 08:28:40 +00:00
err.source = input;
throw err;
}
2014-09-05 16:56:36 +00:00
function getAST() {
2014-09-06 10:33:19 +00:00
var ast = matchListDefinitions();
2014-09-05 08:28:40 +00:00
2014-09-05 16:56:36 +00:00
if (input.length > 0) {
2014-09-06 10:33:19 +00:00
error('Invalid input not EOF');
2014-09-05 08:28:40 +00:00
}
return ast;
2014-09-06 10:33:19 +00:00
}
2014-09-06 10:33:19 +00:00
function matchListDefinitions() {
2014-09-06 16:03:51 +00:00
return matchListing(matchDefinition);
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 10:33:19 +00:00
function matchDefinition() {
return matchGradient(
'linear-gradient',
tokens.linearGradient,
2014-09-06 18:20:25 +00:00
matchLinearOrientation) ||
2014-09-06 18:38:10 +00:00
2014-09-06 18:20:25 +00:00
matchGradient(
'repeating-linear-gradient',
tokens.repeatingLinearGradient,
2014-09-06 18:38:10 +00:00
matchLinearOrientation) ||
matchGradient(
'radial-gradient',
tokens.radialGradient,
2014-09-07 13:26:45 +00:00
matchListRadialOrientations) ||
2014-09-06 18:38:10 +00:00
matchGradient(
'repeating-radial-gradient',
tokens.repeatingRadialGradient,
2014-09-07 13:26:45 +00:00
matchListRadialOrientations);
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 13:25:10 +00:00
function matchGradient(gradientType, pattern, orientationMatcher) {
2014-09-06 15:24:00 +00:00
return matchCall(pattern, function(captures) {
2014-09-06 16:20:11 +00:00
var orientation = orientationMatcher();
2014-09-06 15:24:00 +00:00
if (orientation) {
if (!scan(tokens.comma)) {
error('Missing comma before color stops');
}
}
return {
type: gradientType,
orientation: orientation,
2014-09-06 16:20:11 +00:00
colorStops: matchListing(matchColorStop)
2014-09-06 15:24:00 +00:00
};
});
}
function matchCall(pattern, callback) {
2014-09-06 16:20:11 +00:00
var captures = scan(pattern);
2014-09-05 00:02:54 +00:00
if (captures) {
2014-09-06 10:33:19 +00:00
if (!scan(tokens.startCall)) {
error('Missing (');
}
2019-12-25 13:05:34 +00:00
var result = callback(captures);
2014-09-06 10:33:19 +00:00
if (!scan(tokens.endCall)) {
error('Missing )');
}
2014-09-05 00:02:54 +00:00
2014-09-06 15:24:00 +00:00
return result;
2014-09-05 00:02:54 +00:00
}
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 18:20:25 +00:00
function matchLinearOrientation() {
2014-09-06 13:40:32 +00:00
return matchSideOrCorner() ||
matchAngle();
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 10:33:19 +00:00
function matchSideOrCorner() {
2014-09-06 16:13:10 +00:00
return match('directional', tokens.sideOrCorner, 1);
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 13:40:32 +00:00
function matchAngle() {
2014-09-06 16:13:10 +00:00
return match('angular', tokens.angleValue, 1);
2014-09-06 13:40:32 +00:00
}
2014-09-07 13:26:45 +00:00
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 {
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 13:26:45 +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) {
2014-09-07 15:23:16 +00:00
ellipse.style = matchDistance() || matchExtentKeyword();
2014-09-07 13:26:45 +00:00
}
return ellipse;
}
function matchExtentKeyword() {
2014-09-07 13:50:31 +00:00
return match('extent-keyword', tokens.extentKeywords, 1);
2014-09-07 13:26:45 +00:00
}
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()
};
}
2014-09-06 15:24:00 +00:00
function matchListing(matcher) {
var captures = matcher(),
result = [];
2014-09-05 00:02:54 +00:00
2014-09-06 15:24:00 +00:00
if (captures) {
result.push(captures);
2014-09-05 16:56:36 +00:00
while (scan(tokens.comma)) {
2014-09-06 15:24:00 +00:00
captures = matcher();
if (captures) {
result.push(captures);
2014-09-05 00:02:54 +00:00
} else {
2014-09-06 10:33:19 +00:00
error('One extra comma');
}
}
2014-09-05 00:02:54 +00:00
}
2014-09-06 15:24:00 +00:00
return result;
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-06 10:33:19 +00:00
function matchColorStop() {
2014-09-06 11:28:43 +00:00
var color = matchColor();
if (!color) {
error('Expected color definition');
}
2014-09-07 13:26:45 +00:00
color.length = matchDistance();
2014-09-06 11:28:43 +00:00
return color;
}
function matchColor() {
2014-09-06 15:24:00 +00:00
return matchHexColor() ||
matchRGBAColor() ||
matchRGBColor() ||
matchLiteralColor();
2014-09-06 10:33:19 +00:00
}
function matchLiteralColor() {
2014-09-06 16:13:10 +00:00
return match('literal', tokens.literalColor, 0);
2014-09-06 10:33:19 +00:00
}
2014-09-06 13:53:38 +00:00
function matchHexColor() {
2014-09-06 16:13:10 +00:00
return match('hex', tokens.hexColor, 1);
2014-09-06 13:53:38 +00:00
}
2014-09-06 15:24:00 +00:00
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];
}
2014-09-07 13:26:45 +00:00
function matchDistance() {
return match('%', tokens.percentageValue, 1) ||
matchPositionKeyword() ||
matchLength();
}
function matchPositionKeyword() {
2014-09-07 15:23:16 +00:00
return match('position-keyword', tokens.positionKeywords, 1);
2014-09-07 13:26:45 +00:00
}
2014-09-06 11:28:43 +00:00
function matchLength() {
2014-09-06 16:13:10 +00:00
return match('px', tokens.pixelValue, 1) ||
match('em', tokens.emValue, 1);
2014-09-06 11:28:43 +00:00
}
2014-09-06 16:13:10 +00:00
function match(type, pattern, captureIndex) {
2014-09-06 13:25:10 +00:00
var captures = scan(pattern);
2014-09-06 11:28:43 +00:00
if (captures) {
return {
2014-09-06 16:13:10 +00:00
type: type,
value: captures[captureIndex]
2014-09-06 11:28:43 +00:00
};
}
}
2014-09-05 16:56:36 +00:00
function scan(regexp) {
2014-09-05 00:02:54 +00:00
var captures,
blankCaptures;
2014-09-05 16:56:36 +00:00
blankCaptures = /^[\n\r\t\s]+/.exec(input);
2014-09-05 00:02:54 +00:00
if (blankCaptures) {
2014-09-05 16:56:36 +00:00
consume(blankCaptures[0].length);
2014-09-05 00:02:54 +00:00
}
2014-09-05 16:56:36 +00:00
captures = regexp.exec(input);
2014-09-05 00:02:54 +00:00
if (captures) {
2014-09-05 16:56:36 +00:00
consume(captures[0].length);
2014-09-05 00:02:54 +00:00
}
return captures;
2014-09-05 16:56:36 +00:00
}
2014-09-05 00:02:54 +00:00
2014-09-05 16:56:36 +00:00
function consume(size) {
input = input.substr(size);
}
2014-09-05 00:02:54 +00:00
2014-09-05 16:56:36 +00:00
return function(code) {
input = code.toString();
return getAST();
};
2014-09-05 00:02:54 +00:00
})();