(function() { /* _FROM_SVG_START_ */ function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset'), color, opacity; // convert percents to absolute values offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); if (style) { var keyValuePairs = style.split(/\s*;\s*/); if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } for (var i = keyValuePairs.length; i--; ) { var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), value = split[1].trim(); if (key === 'stop-color') { color = value; } else if (key === 'stop-opacity') { opacity = value; } } } if (!color) { color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; } if (!opacity) { opacity = el.getAttribute('stop-opacity'); } // convert rgba color to rgb color - alpha value has no affect in svg color = new fabric.Color(color).toRgb(); return { offset: offset, color: color, opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity) }; } function getLinearCoords(el) { return { x1: el.getAttribute('x1') || 0, y1: el.getAttribute('y1') || 0, x2: el.getAttribute('x2') || '100%', y2: el.getAttribute('y2') || 0 }; } function getRadialCoords(el) { return { x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', r1: 0, x2: el.getAttribute('cx') || '50%', y2: el.getAttribute('cy') || '50%', r2: el.getAttribute('r') || '50%' }; } /* _FROM_SVG_END_ */ /** * Gradient class * @class fabric.Gradient * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} * @see {@link fabric.Gradient#initialize} for constructor definition */ fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { /** * Constructor * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); var coords = { }; this.id = fabric.Object.__uid++; this.type = options.type || 'linear'; coords = { x1: options.coords.x1 || 0, y1: options.coords.y1 || 0, x2: options.coords.x2 || 0, y2: options.coords.y2 || 0 }; if (this.type === 'radial') { coords.r1 = options.coords.r1 || 0; coords.r2 = options.coords.r2 || 0; } this.coords = coords; this.gradientUnits = options.gradientUnits || 'objectBoundingBox'; this.colorStops = options.colorStops.slice(); }, /** * Adds another colorStop * @param {Object} colorStop Object with offset and color * @return {fabric.Gradient} thisArg */ addColorStop: function(colorStop) { for (var position in colorStop) { var color = new fabric.Color(colorStop[position]); this.colorStops.push({ offset: position, color: color.toRgb(), opacity: color.getAlpha() }); } return this; }, /** * Returns object representation of a gradient * @return {Object} */ toObject: function() { return { type: this.type, coords: this.coords, gradientUnits: this.gradientUnits, colorStops: this.colorStops }; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an gradient * @param {Object} object Object to create a gradient for * @param {Boolean} normalize Whether coords should be normalized * @return {String} SVG representation of an gradient (linear/radial) */ toSVG: function(object, normalize) { var coords = fabric.util.object.clone(this.coords), markup; // colorStops must be sorted ascending this.colorStops.sort(function(a, b) { return a.offset - b.offset; }); if (normalize && this.gradientUnits === 'userSpaceOnUse') { coords.x1 += object.width / 2; coords.y1 += object.height / 2; coords.x2 += object.width / 2; coords.y2 += object.height / 2; } else if (this.gradientUnits === 'objectBoundingBox') { _convertValuesToPercentUnits(object, coords); } if (this.type === 'linear') { markup = [ '' ]; } else if (this.type === 'radial') { markup = [ '' ]; } for (var i = 0; i < this.colorStops.length; i++) { markup.push( '' ); } markup.push((this.type === 'linear' ? '' : '')); return markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns an instance of CanvasGradient * @param {CanvasRenderingContext2D} ctx Context to render on * @return {CanvasGradient} */ toLive: function(ctx) { var gradient; if (!this.type) return; if (this.type === 'linear') { gradient = ctx.createLinearGradient( this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2); } else if (this.type === 'radial') { gradient = ctx.createRadialGradient( this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2); } for (var i = 0, len = this.colorStops.length; i < len; i++) { var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset; if (typeof opacity !== 'undefined') { color = new fabric.Color(color).setAlpha(opacity).toRgba(); } gradient.addColorStop(parseFloat(offset), color); } return gradient; } }); fabric.util.object.extend(fabric.Gradient, { /* _FROM_SVG_START_ */ /** * Returns {@link fabric.Gradient} instance from an SVG element * @static * @memberof fabric.Gradient * @param {SVGGradientElement} el SVG gradient element * @param {fabric.Object} instance * @return {fabric.Gradient} Gradient instance * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement */ fromElement: function(el, instance) { /** * @example: * * * * * * * OR * * * * * * * OR * * * * * * * * OR * * * * * * * */ var colorStopEls = el.getElementsByTagName('stop'), type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', colorStops = [], coords = { }; if (type === 'linear') { coords = getLinearCoords(el); } else if (type === 'radial') { coords = getRadialCoords(el); } for (var i = colorStopEls.length; i--; ) { colorStops.push(getColorStop(colorStopEls[i])); } _convertPercentUnitsToValues(instance, coords); return new fabric.Gradient({ type: type, coords: coords, gradientUnits: gradientUnits, colorStops: colorStops }); }, /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Gradient} instance from its object representation * @static * @memberof fabric.Gradient * @param {Object} obj * @param {Object} [options] Options object */ forObject: function(obj, options) { options || (options = { }); _convertPercentUnitsToValues(obj, options); return new fabric.Gradient(options); } }); /** * @private */ function _convertPercentUnitsToValues(object, options) { for (var prop in options) { if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { var percents = parseFloat(options[prop], 10); if (prop === 'x1' || prop === 'x2' || prop === 'r2') { options[prop] = fabric.util.toFixed(object.width * percents / 100, 2); } else if (prop === 'y1' || prop === 'y2') { options[prop] = fabric.util.toFixed(object.height * percents / 100, 2); } } normalize(options, prop, object); } } // normalize rendering point (should be from top/left corner rather than center of the shape) function normalize(options, prop, object) { if (prop === 'x1' || prop === 'x2') { options[prop] -= fabric.util.toFixed(object.width / 2, 2); } else if (prop === 'y1' || prop === 'y2') { options[prop] -= fabric.util.toFixed(object.height / 2, 2); } } /* _TO_SVG_START_ */ /** * @private */ function _convertValuesToPercentUnits(object, options) { for (var prop in options) { normalize(options, prop, object); // convert to percent units if (prop === 'x1' || prop === 'x2' || prop === 'r2') { options[prop] = fabric.util.toFixed(options[prop] / object.width * 100, 2) + '%'; } else if (prop === 'y1' || prop === 'y2') { options[prop] = fabric.util.toFixed(options[prop] / object.height * 100, 2) + '%'; } } } /* _TO_SVG_END_ */ })();