diff --git a/src/circle.class.js b/src/circle.class.js
index 6de5fcc2..23cd8c8c 100644
--- a/src/circle.class.js
+++ b/src/circle.class.js
@@ -59,12 +59,25 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- return ('');
+ var markup = [];
+
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, false));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, false));
+ }
+
+ markup.push(
+ ''
+ );
+
+ return markup.join('');
},
/**
diff --git a/src/color.class.js b/src/color.class.js
index a1ed5b4a..4ec4bb7f 100644
--- a/src/color.class.js
+++ b/src/color.class.js
@@ -37,7 +37,14 @@
* @method _tryParsingColor
*/
_tryParsingColor: function(color) {
- var source = Color.sourceFromHex(color);
+ var source;
+
+ if (color in Color.colorNameMap) {
+ color = Color.colorNameMap[color];
+ }
+
+ source = Color.sourceFromHex(color);
+
if (!source) {
source = Color.sourceFromRgb(color);
}
@@ -197,6 +204,30 @@
*/
fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
+ /**
+ * Map of the 16 basic color names with HEX code
+ * @static
+ * @field
+ */
+ fabric.Color.colorNameMap = {
+ 'aqua': '#00FFFF',
+ 'black': '#000000',
+ 'blue': '#0000FF',
+ 'fuchsia': '#FF00FF',
+ 'gray': '#808080',
+ 'green': '#008000',
+ 'lime': '#00FF00',
+ 'maroon': '#800000',
+ 'navy': '#000080',
+ 'olive': '#808000',
+ 'purple': '#800080',
+ 'red': '#FF0000',
+ 'silver': '#C0C0C0',
+ 'teal': '#008080',
+ 'white': '#FFFFFF',
+ 'yellow': '#FFFF00',
+ };
+
/**
* Returns new color object, when given a color in RGB format
* @method fromRgb
diff --git a/src/ellipse.class.js b/src/ellipse.class.js
index 8d1eda6f..ad1e3229 100644
--- a/src/ellipse.class.js
+++ b/src/ellipse.class.js
@@ -62,14 +62,25 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- return [
+ var markup = [];
+
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, false));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, false));
+ }
+
+ markup.push(
''
- ].join('');
+ 'rx="', this.get('rx'),
+ '" ry="', this.get('ry'),
+ '" style="', this.getSvgStyles(),
+ '" transform="', this.getSvgTransform(),
+ '"/>'
+ );
+
+ return markup.join('');
},
/**
diff --git a/src/gradient.class.js b/src/gradient.class.js
index 9a4bf77f..489c3743 100644
--- a/src/gradient.class.js
+++ b/src/gradient.class.js
@@ -1,7 +1,12 @@
(function() {
- function getColorStopFromStyle(el) {
- var style = el.getAttribute('style');
+ 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*/);
@@ -17,10 +22,29 @@
value = split[1].trim();
if (key === 'stop-color') {
- return value;
+ color = value;
+ }
+ else if (key === 'stop-opacity') {
+ opacity = value;
}
}
}
+
+ if (!color) {
+ color = el.getAttribute('stop-color');
+ }
+ 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: opacity
+ }
}
/**
@@ -33,19 +57,43 @@
/**
* Constructor
* @method initialize
- * @param [options] Options object with x1, y1, x2, y2 and colorStops
+ * @param [options] Options object with type, coords, gradientUnits and colorStops
* @return {fabric.Gradient} thisArg
*/
initialize: function(options) {
-
options || (options = { });
- this.x1 = options.x1 || 0;
- this.y1 = options.y1 || 0;
- this.x2 = options.x2 || 0;
- this.y2 = options.y2 || 0;
+ var coords = { };
- this.colorStops = options.colorStops;
+ 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
+ * @method add
+ * @param {Object} colorStop Object with offset, color and opacity
+ * @return {fabric.Gradient} thisArg
+ */
+ addColorStop: function(colorStop) {
+ this.colorStops.push(colorStop);
+ return this;
},
/**
@@ -55,10 +103,9 @@
*/
toObject: function() {
return {
- x1: this.x1,
- x2: this.x2,
- y1: this.y1,
- y2: this.y2,
+ type: this.type,
+ coords: this.coords,
+ gradientUnits: this.gradientUnits,
colorStops: this.colorStops
};
},
@@ -70,15 +117,97 @@
* @return {CanvasGradient}
*/
toLive: function(ctx) {
- var gradient = ctx.createLinearGradient(
- this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2);
+ var gradient;
- for (var position in this.colorStops) {
- var colorValue = this.colorStops[position];
- gradient.addColorStop(parseFloat(position), colorValue);
+ if (!this.type) return;
+
+ if (this.type === 'linear') {
+ gradient = ctx.createLinearGradient(
+ this.coords.x1, this.coords.y1, this.coords.x2 || ctx.canvas.width, 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; i < this.colorStops.length; i++) {
+ var color = this.colorStops[i].color,
+ opacity = this.colorStops[i].opacity,
+ offset = this.colorStops[i].offset;
+
+ if (opacity) {
+ color = new fabric.Color(color).setAlpha(opacity).toRgba();
+ }
+ gradient.addColorStop(parseFloat(offset), color);
}
return gradient;
+ },
+
+ /**
+ * Returns SVG representation of an gradient
+ * @method toSVG
+ * @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);
+
+ // 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') {
+ var markup = [
+ ''
+ ];
+ }
+ else if (this.type === 'radial') {
+ var markup = [
+ ''
+ ];
+ }
+
+ for (var i = 0; i < this.colorStops.length; i++) {
+ markup.push(
+ ''
+ );
+ }
+
+ markup.push((this.type === 'linear' ? '' : ''));
+
+ return markup.join('');
}
});
@@ -90,52 +219,78 @@
* @static
* @memberof fabric.Gradient
* @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'),
- offset,
- colorStops = { },
- coords = {
- x1: el.getAttribute('x1') || 0,
- y1: el.getAttribute('y1') || 0,
- x2: el.getAttribute('x2') || '100%',
- y2: el.getAttribute('y2') || 0
- };
+ type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
+ gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
+ colorStops = [],
+ coords = { };
+
+ if (type === 'linear') {
+ coords = {
+ x1: el.getAttribute('x1') || 0,
+ y1: el.getAttribute('y1') || 0,
+ x2: el.getAttribute('x2') || '100%',
+ y2: el.getAttribute('y2') || 0
+ };
+ }
+ else if (type === 'radial') {
+ coords = {
+ 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%',
+ };
+ }
for (var i = colorStopEls.length; i--; ) {
- el = colorStopEls[i];
- offset = el.getAttribute('offset');
-
- // convert percents to absolute values
- offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
- colorStops[offset] = getColorStopFromStyle(el) || el.getAttribute('stop-color');
+ colorStops.push(getColorStop(colorStopEls[i]));
}
_convertPercentUnitsToValues(instance, coords);
return new fabric.Gradient({
- x1: coords.x1,
- y1: coords.y1,
- x2: coords.x2,
- y2: coords.y2,
+ type: type,
+ coords: coords,
+ gradientUnits: gradientUnits,
colorStops: colorStops
});
},
@@ -144,8 +299,8 @@
* Returns {@link fabric.Gradient} instance from its object representation
* @method forObject
* @static
- * @param obj
- * @param [options]
+ * @param {Object} obj
+ * @param {Object} [options]
* @memberof fabric.Gradient
*/
forObject: function(obj, options) {
@@ -159,7 +314,7 @@
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') {
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
options[prop] = fabric.util.toFixed(object.width * percents / 100, 2);
}
else if (prop === 'y1' || prop === 'y2') {
@@ -176,6 +331,25 @@
}
}
+ function _convertValuesToPercentUnits(object, options) {
+ for (var prop in options) {
+ // normalize rendering point (should be from center rather than top/left corner of the shape)
+ 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);
+ }
+ // 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) + '%';
+ }
+ }
+ }
+
/**
* Parses an SVG document, returning all of the gradient declarations found in it
* @static
diff --git a/src/line.class.js b/src/line.class.js
index c0062716..cd2aea3d 100644
--- a/src/line.class.js
+++ b/src/line.class.js
@@ -135,15 +135,23 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- return [
+ var markup = [];
+
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, true));
+ }
+
+ markup.push(
''
- ].join('');
+ 'x1="', this.get('x1'),
+ '" y1="', this.get('y1'),
+ '" x2="', this.get('x2'),
+ '" y2="', this.get('y2'),
+ '" style="', this.getSvgStyles(),
+ '"/>'
+ );
+
+ return markup.join('');
}
});
diff --git a/src/object.class.js b/src/object.class.js
index 3d0394d6..8b17c953 100644
--- a/src/object.class.js
+++ b/src/object.class.js
@@ -434,10 +434,10 @@
*/
getSvgStyles: function() {
return [
- "stroke: ", (this.stroke ? this.stroke : 'none'), "; ",
+ "stroke: ", (this.stroke ? (this.stroke && this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) : 'none'), "; ",
"stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ",
"stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : "; "),
- "fill: ", (this.fill ? this.fill : 'none'), "; ",
+ "fill: ", (this.fill ? (this.fill && this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) : 'none'), "; ",
"opacity: ", (this.opacity ? this.opacity : '1'), ";"
].join("");
},
@@ -855,11 +855,13 @@
},
/**
- * Sets gradient fill of an object
- * @method setGradientFill
+ * Sets gradient (fill or stroke) of an object
+ * @method setGradient
+ * @param {String} property Property name 'stroke' or 'fill'
+ * @param {Object} [options] Options object
*/
- setGradientFill: function(options) {
- this.set('fill', fabric.Gradient.forObject(this, options));
+ setGradient: function(property, options) {
+ this.set(property, fabric.Gradient.forObject(this, options));
},
/**
@@ -1065,4 +1067,10 @@
*/
fabric.Object.NUM_FRACTION_DIGITS = 2;
+ /**
+ * @static
+ * @type Number
+ */
+ fabric.Object.__uid = 0;
+
})(typeof exports !== 'undefined' ? exports : this);
diff --git a/src/path.class.js b/src/path.class.js
index 149f395d..67857b47 100644
--- a/src/path.class.js
+++ b/src/path.class.js
@@ -626,20 +626,33 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- var chunks = [];
+ var chunks = [],
+ markup = [];
+
for (var i = 0, len = this.path.length; i < len; i++) {
chunks.push(this.path[i].join(' '));
}
var path = chunks.join(' ');
- return [
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, true));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, true));
+ }
+
+ markup.push(
'',
'',
+ 'd="', path,
+ '" style="', this.getSvgStyles(),
+ '" transform="translate(', (-this.width / 2), ' ', (-this.height/2), ')',
+ '" stroke-linecap="round" ',
+ '/>',
''
- ].join('');
+ );
+
+ return markup.join('');
},
/**
diff --git a/src/polygon.class.js b/src/polygon.class.js
index a55ee784..1f136f13 100644
--- a/src/polygon.class.js
+++ b/src/polygon.class.js
@@ -90,18 +90,29 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- var points = [];
+ var points = [],
+ markup = [];
+
for (var i = 0, len = this.points.length; i < len; i++) {
points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
}
- return [
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, false));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, false));
+ }
+
+ markup.push(
''
- ].join('');
+ 'points="', points.join(''),
+ '" style="', this.getSvgStyles(),
+ '" transform="', this.getSvgTransform(),
+ '"/>'
+ );
+
+ return markup.join('');
},
/**
diff --git a/src/polyline.class.js b/src/polyline.class.js
index 9f04c541..dadfc27d 100644
--- a/src/polyline.class.js
+++ b/src/polyline.class.js
@@ -63,18 +63,29 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- var points = [];
+ var points = [],
+ markup = [];
+
for (var i = 0, len = this.points.length; i < len; i++) {
points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
}
- return [
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, false));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, false));
+ }
+
+ markup.push(
''
- ].join('');
+ 'points="', points.join(''),
+ '" style="', this.getSvgStyles(),
+ '" transform="', this.getSvgTransform(),
+ '"/>'
+ );
+
+ return markup.join('');
},
/**
diff --git a/src/rect.class.js b/src/rect.class.js
index 0a7d6bde..4219dd12 100644
--- a/src/rect.class.js
+++ b/src/rect.class.js
@@ -237,13 +237,26 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- return '';
+ var markup = [];
+
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, false));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, false));
+ }
+
+ markup.push(
+ ''
+ );
+
+ return markup.join('');
}
});
diff --git a/src/triangle.class.js b/src/triangle.class.js
index d9545346..6757adf4 100644
--- a/src/triangle.class.js
+++ b/src/triangle.class.js
@@ -76,8 +76,8 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
-
- var widthBy2 = this.width / 2,
+ var markup = [],
+ widthBy2 = this.width / 2,
heightBy2 = this.height / 2;
var points = [
@@ -86,11 +86,22 @@
widthBy2 + " " + heightBy2
].join(",");
- return '';
+ if (this.fill && this.fill.toLive) {
+ markup.push(this.fill.toSVG(this, true));
+ }
+ if (this.stroke && this.stroke.toLive) {
+ markup.push(this.stroke.toSVG(this, true));
+ }
+
+ markup.push(
+ ''
+ );
+
+ return markup.join('');
}
});