mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-11 01:40:59 +00:00
406 lines
12 KiB
JavaScript
406 lines
12 KiB
JavaScript
(function() {
|
|
|
|
/* _FROM_SVG_START_ */
|
|
function getColorStop(el) {
|
|
var style = el.getAttribute('style'),
|
|
offset = el.getAttribute('offset'),
|
|
color, colorAlpha, opacity;
|
|
|
|
// convert percents to absolute values
|
|
offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
|
|
offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
|
|
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');
|
|
}
|
|
|
|
color = new fabric.Color(color);
|
|
colorAlpha = color.getAlpha();
|
|
opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
|
|
opacity *= colorAlpha;
|
|
|
|
return {
|
|
offset: offset,
|
|
color: color.toRgb(),
|
|
opacity: 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 */ {
|
|
|
|
/**
|
|
* Horizontal offset for aligning gradients coming from SVG when outside pathgroups
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
offsetX: 0,
|
|
|
|
/**
|
|
* Vertical offset for aligning gradients coming from SVG when outside pathgroups
|
|
* @type Number
|
|
* @default 0
|
|
*/
|
|
offsetY: 0,
|
|
|
|
/**
|
|
* 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.colorStops = options.colorStops.slice();
|
|
if (options.gradientTransform) {
|
|
this.gradientTransform = options.gradientTransform;
|
|
}
|
|
this.offsetX = options.offsetX || this.offsetX;
|
|
this.offsetY = options.offsetY || this.offsetY;
|
|
},
|
|
|
|
/**
|
|
* 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,
|
|
colorStops: this.colorStops,
|
|
offsetX: this.offsetX,
|
|
offsetY: this.offsetY
|
|
};
|
|
},
|
|
|
|
/* _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) {
|
|
var coords = fabric.util.object.clone(this.coords),
|
|
markup, commonAttributes;
|
|
|
|
// colorStops must be sorted ascending
|
|
this.colorStops.sort(function(a, b) {
|
|
return a.offset - b.offset;
|
|
});
|
|
|
|
if (!(object.group && object.group.type === 'path-group')) {
|
|
for (var prop in coords) {
|
|
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
|
|
coords[prop] += this.offsetX - object.width / 2;
|
|
}
|
|
else if (prop === 'y1' || prop === 'y2') {
|
|
coords[prop] += this.offsetY - object.height / 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
commonAttributes = 'id="SVGID_' + this.id +
|
|
'" gradientUnits="userSpaceOnUse"';
|
|
if (this.gradientTransform) {
|
|
commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
|
|
}
|
|
if (this.type === 'linear') {
|
|
markup = [
|
|
//jscs:disable validateIndentation
|
|
'<linearGradient ',
|
|
commonAttributes,
|
|
' x1="', coords.x1,
|
|
'" y1="', coords.y1,
|
|
'" x2="', coords.x2,
|
|
'" y2="', coords.y2,
|
|
'">\n'
|
|
//jscs:enable validateIndentation
|
|
];
|
|
}
|
|
else if (this.type === 'radial') {
|
|
markup = [
|
|
//jscs:disable validateIndentation
|
|
'<radialGradient ',
|
|
commonAttributes,
|
|
' cx="', coords.x2,
|
|
'" cy="', coords.y2,
|
|
'" r="', coords.r2,
|
|
'" fx="', coords.x1,
|
|
'" fy="', coords.y1,
|
|
'">\n'
|
|
//jscs:enable validateIndentation
|
|
];
|
|
}
|
|
|
|
for (var i = 0; i < this.colorStops.length; i++) {
|
|
markup.push(
|
|
//jscs:disable validateIndentation
|
|
'<stop ',
|
|
'offset="', (this.colorStops[i].offset * 100) + '%',
|
|
'" style="stop-color:', this.colorStops[i].color,
|
|
(this.colorStops[i].opacity != null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
|
|
'"/>\n'
|
|
//jscs:enable validateIndentation
|
|
);
|
|
}
|
|
|
|
markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));
|
|
|
|
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:
|
|
*
|
|
* <linearGradient id="linearGrad1">
|
|
* <stop offset="0%" stop-color="white"/>
|
|
* <stop offset="100%" stop-color="black"/>
|
|
* </linearGradient>
|
|
*
|
|
* OR
|
|
*
|
|
* <linearGradient id="linearGrad2">
|
|
* <stop offset="0" style="stop-color:rgb(255,255,255)"/>
|
|
* <stop offset="1" style="stop-color:rgb(0,0,0)"/>
|
|
* </linearGradient>
|
|
*
|
|
* OR
|
|
*
|
|
* <radialGradient id="radialGrad1">
|
|
* <stop offset="0%" stop-color="white" stop-opacity="1" />
|
|
* <stop offset="50%" stop-color="black" stop-opacity="0.5" />
|
|
* <stop offset="100%" stop-color="white" stop-opacity="1" />
|
|
* </radialGradient>
|
|
*
|
|
* OR
|
|
*
|
|
* <radialGradient id="radialGrad2">
|
|
* <stop offset="0" stop-color="rgb(255,255,255)" />
|
|
* <stop offset="0.5" stop-color="rgb(0,0,0)" />
|
|
* <stop offset="1" stop-color="rgb(255,255,255)" />
|
|
* </radialGradient>
|
|
*
|
|
*/
|
|
|
|
var colorStopEls = el.getElementsByTagName('stop'),
|
|
type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
|
|
gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
|
|
gradientTransform = el.getAttribute('gradientTransform'),
|
|
colorStops = [],
|
|
coords = { }, ellipseMatrix;
|
|
|
|
if (type === 'linear') {
|
|
coords = getLinearCoords(el);
|
|
}
|
|
else if (type === 'radial') {
|
|
coords = getRadialCoords(el);
|
|
}
|
|
|
|
for (var i = colorStopEls.length; i--; ) {
|
|
colorStops.push(getColorStop(colorStopEls[i]));
|
|
}
|
|
|
|
ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
|
|
|
|
var gradient = new fabric.Gradient({
|
|
type: type,
|
|
coords: coords,
|
|
colorStops: colorStops,
|
|
offsetX: -instance.left,
|
|
offsetY: -instance.top
|
|
});
|
|
|
|
if (gradientTransform || ellipseMatrix !== '') {
|
|
gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
|
|
}
|
|
return gradient;
|
|
},
|
|
/* _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.coords, 'userSpaceOnUse');
|
|
return new fabric.Gradient(options);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
function _convertPercentUnitsToValues(object, options, gradientUnits) {
|
|
var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
|
|
for (var prop in options) {
|
|
propValue = parseFloat(options[prop], 10);
|
|
if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
|
|
multFactor = 0.01;
|
|
}
|
|
else {
|
|
multFactor = 1;
|
|
}
|
|
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
|
|
multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
|
|
addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
|
|
}
|
|
else if (prop === 'y1' || prop === 'y2') {
|
|
multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
|
|
addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
|
|
}
|
|
options[prop] = propValue * multFactor + addFactor;
|
|
}
|
|
if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) {
|
|
var scaleFactor = object.ry/object.rx;
|
|
ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
|
|
if (options.y1) {
|
|
options.y1 /= scaleFactor;
|
|
}
|
|
if (options.y2) {
|
|
options.y2 /= scaleFactor;
|
|
}
|
|
}
|
|
return ellipseMatrix;
|
|
}
|
|
})();
|