diff --git a/src/base_brush.class.js b/src/base_brush.class.js
index 0e99296122f..1825f116b09 100644
--- a/src/base_brush.class.js
+++ b/src/base_brush.class.js
@@ -61,5 +61,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
ctx.shadowColor = this.shadowColor || this.color;
ctx.shadowOffsetX = this.shadowOffsetX;
ctx.shadowOffsetY = this.shadowOffsetY;
+ },
+
+ /**
+ * Remove brush shadow styles
+ */
+ removeShadowStyles: function() {
+ var ctx = this.canvas.contextTop;
+
+ ctx.shadowColor = '';
+ ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
}
});
diff --git a/src/circle.class.js b/src/circle.class.js
index 7e91e73238d..fc84eb412eb 100644
--- a/src/circle.class.js
+++ b/src/circle.class.js
@@ -86,11 +86,9 @@
ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false);
ctx.closePath();
+
this._renderFill(ctx);
- this._removeShadow(ctx);
- if (this.stroke) {
- ctx.stroke();
- }
+ this._renderStroke(ctx);
},
/**
diff --git a/src/circle_brush.class.js b/src/circle_brush.class.js
index 9bdfb4143a4..af4c3076310 100644
--- a/src/circle_brush.class.js
+++ b/src/circle_brush.class.js
@@ -69,6 +69,8 @@ fabric.CircleBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabri
this.canvas.add(circle);
}
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
this.canvas.renderOnAddition = originalRenderOnAddition;
this.canvas.renderAll();
},
diff --git a/src/ellipse.class.js b/src/ellipse.class.js
index 0ddc6a18704..2ee60b17034 100644
--- a/src/ellipse.class.js
+++ b/src/ellipse.class.js
@@ -116,11 +116,9 @@
}
ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0);
ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.rx, 0, piBy2, false);
- if (this.stroke) {
- ctx.stroke();
- }
- this._removeShadow(ctx);
+
this._renderFill(ctx);
+ this._renderStroke(ctx);
ctx.restore();
},
diff --git a/src/image.class.js b/src/image.class.js
index 1501f5a1996..5ab31837272 100644
--- a/src/image.class.js
+++ b/src/image.class.js
@@ -107,12 +107,11 @@
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
this._render(ctx);
- this.clipTo && ctx.restore();
- this._removeShadow(ctx);
-
- if (this.stroke) {
- this._stroke(ctx);
+ if (this.shadow && !this.shadow.affectStroke) {
+ this._removeShadow(ctx);
}
+ this._renderStroke(ctx);
+ this.clipTo && ctx.restore();
if (this.active && !noTransform) {
this.drawBorders(ctx);
@@ -121,18 +120,40 @@
ctx.restore();
},
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
_stroke: function(ctx) {
ctx.save();
ctx.lineWidth = this.strokeWidth;
ctx.strokeStyle = this.stroke;
- ctx.strokeRect(
- -this.width / 2,
- -this.height / 2,
- this.width,
- this.height);
+ ctx.beginPath();
+ ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);
+ ctx.beginPath();
ctx.restore();
},
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderDashedStroke: function(ctx) {
+ var x = -this.width/2,
+ y = -this.height/2,
+ w = this.width,
+ h = this.height;
+
+ ctx.lineWidth = this.strokeWidth;
+ ctx.strokeStyle = this.stroke;
+ ctx.beginPath();
+ fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray);
+ ctx.closePath();
+ },
+
/**
* Returns object representation of an instance
* @param {Array} propertiesToInclude
@@ -150,16 +171,37 @@
* @return {String} svg representation of an instance
*/
toSVG: function() {
- return ''+
- ' element with actual transformation, then offsetting object to the top/left
- // so that object's center aligns with container's left/top
- 'transform="translate('+ (-this.width/2) + ' ' + (-this.height/2) + ')" ' +
- 'width="' + this.width + '" ' +
- 'height="' + this.height + '"' + '>' +
- '';
+ var markup = [];
+
+ markup.push(
+ '',
+ ''
+ );
+
+ if (this.stroke || this.strokeDashArray) {
+ var origFill = this.fill;
+ this.fill = null;
+ markup.push(
+ ''
+ );
+ this.fill = origFill;
+ }
+
+ markup.push('');
+
+ return markup.join('');
},
/**
diff --git a/src/line.class.js b/src/line.class.js
index 4149e552f58..6ae6da4497b 100644
--- a/src/line.class.js
+++ b/src/line.class.js
@@ -89,9 +89,11 @@
ctx.translate(this.left, this.top);
}
- // move from center (of virtual box) to its left/top corner
- ctx.moveTo(this.width === 1 ? 0 : (-this.width / 2), this.height === 1 ? 0 : (-this.height / 2));
- ctx.lineTo(this.width === 1 ? 0 : (this.width / 2), this.height === 1 ? 0 : (this.height / 2));
+ if (!this.strokeDashArray || this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {
+ // move from center (of virtual box) to its left/top corner
+ ctx.moveTo(this.width === 1 ? 0 : (-this.width / 2), this.height === 1 ? 0 : (-this.height / 2));
+ ctx.lineTo(this.width === 1 ? 0 : (this.width / 2), this.height === 1 ? 0 : (this.height / 2));
+ }
ctx.lineWidth = this.strokeWidth;
@@ -100,10 +102,23 @@
// (by copying fillStyle to strokeStyle, since line is stroked, not filled)
var origStrokeStyle = ctx.strokeStyle;
ctx.strokeStyle = this.stroke || ctx.fillStyle;
- ctx.stroke();
+ this._renderStroke(ctx);
ctx.strokeStyle = origStrokeStyle;
},
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderDashedStroke: function(ctx) {
+ var x = this.width === 1 ? 0 : -this.width / 2,
+ y = this.height === 1 ? 0 : -this.height / 2;
+
+ ctx.beginPath();
+ fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray);
+ ctx.closePath();
+ },
+
/**
* Returns complexity of an instance
* @return {Number} complexity
diff --git a/src/object.class.js b/src/object.class.js
index a81192f8f16..3cc8ea8df47 100644
--- a/src/object.class.js
+++ b/src/object.class.js
@@ -428,7 +428,7 @@
return [
"stroke: ", (this.stroke ? this.stroke : 'none'), "; ",
"stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ",
- "stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : "; "),
+ "stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : ''), "; ",
"fill: ", (this.fill ? (this.fill && this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) : 'none'), "; ",
"opacity: ", (typeof this.opacity !== 'undefined' ? this.opacity : '1'), ";",
(this.visible ? '' : " visibility: hidden;")
@@ -637,6 +637,7 @@
/**
* @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
*/
_setShadow: function(ctx) {
if (!this.shadow) return;
@@ -649,6 +650,7 @@
/**
* @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
*/
_removeShadow: function(ctx) {
ctx.shadowColor = '';
@@ -657,6 +659,7 @@
/**
* @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderFill: function(ctx) {
if (!this.fill) return;
@@ -671,6 +674,37 @@
if (this.fill.toLive) {
ctx.restore();
}
+ if (this.shadow && !this.shadow.affectStroke) {
+ this._removeShadow(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderStroke: function(ctx) {
+ if (!this.stroke && !this.strokeDashArray) return;
+
+ if (this.strokeDashArray) {
+ // Spec requires the concatenation of two copies the dash list when the number of elements is odd
+ if (1 & this.strokeDashArray.length) {
+ this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
+ }
+
+ if (fabric.StaticCanvas.supports('setLineDash')) {
+ ctx.setLineDash(this.strokeDashArray);
+ this._stroke && this._stroke(ctx);
+ }
+ else {
+ this._renderDashedStroke && this._renderDashedStroke(ctx);
+ }
+ ctx.stroke();
+ }
+ else {
+ this._stroke ? this._stroke(ctx) : ctx.stroke();
+ }
+ this._removeShadow(ctx);
},
/**
diff --git a/src/path.class.js b/src/path.class.js
index bcc86f34eed..d326f2c5580 100644
--- a/src/path.class.js
+++ b/src/path.class.js
@@ -561,17 +561,14 @@
this._render(ctx);
this._renderFill(ctx);
- this.clipTo && ctx.restore();
- if (this.shadow && !this.shadow.affectStroke) {
- this._removeShadow(ctx);
- }
-
if (this.stroke) {
ctx.strokeStyle = this.stroke;
ctx.lineWidth = this.strokeWidth;
ctx.lineCap = ctx.lineJoin = 'round';
- ctx.stroke();
+ this._renderStroke(ctx);
}
+ this.clipTo && ctx.restore();
+
this._removeShadow(ctx);
if (!noTransform && this.active) {
diff --git a/src/pencil_brush.class.js b/src/pencil_brush.class.js
index df644a63681..bcd36814f7d 100644
--- a/src/pencil_brush.class.js
+++ b/src/pencil_brush.class.js
@@ -251,7 +251,8 @@
this.canvas.add(path);
path.setCoords();
- this.canvas.contextTop && this.canvas.clearContext(this.canvas.contextTop);
+ this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
this.canvas.renderAll();
// fire event 'path' created
diff --git a/src/polygon.class.js b/src/polygon.class.js
index 08031f77c6b..a78f7e8f173 100644
--- a/src/polygon.class.js
+++ b/src/polygon.class.js
@@ -123,13 +123,28 @@
ctx.lineTo(point.x, point.y);
}
this._renderFill(ctx);
- this._removeShadow(ctx);
- if (this.stroke) {
+ if (this.stroke || this.strokeDashArray) {
ctx.closePath();
- ctx.stroke();
+ this._renderStroke(ctx);
}
},
+ /**
+ * @private
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ _renderDashedStroke: function(ctx) {
+ var p1, p2;
+
+ ctx.beginPath();
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ p1 = this.points[i];
+ p2 = this.points[i+1] || this.points[0];
+ fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
+ }
+ ctx.closePath();
+ },
+
/**
* Returns complexity of an instance
* @return {Number} complexity of this instance
diff --git a/src/polyline.class.js b/src/polyline.class.js
index 63b6e6b09bd..f3c6e4f2314 100644
--- a/src/polyline.class.js
+++ b/src/polyline.class.js
@@ -27,7 +27,7 @@
* Constructor
* @param {Array} points array of points
* @param {Object} [options] Options object
- * @param {Boolean} Whether points offsetting should be skipped
+ * @param {Boolean} skipOffset Whether points offsetting should be skipped
* @return {fabric.Polyline} thisArg
*/
initialize: function(points, options, skipOffset) {
@@ -39,6 +39,7 @@
/**
* @private
+ * @param {Boolean} skipOffset Whether points offsetting should be skipped
*/
_calcDimensions: function(skipOffset) {
return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset);
@@ -95,10 +96,23 @@
point = this.points[i];
ctx.lineTo(point.x, point.y);
}
+
this._renderFill(ctx);
- this._removeShadow(ctx);
- if (this.stroke) {
- ctx.stroke();
+ this._renderStroke(ctx);
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderDashedStroke: function(ctx) {
+ var p1, p2;
+
+ ctx.beginPath();
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ p1 = this.points[i];
+ p2 = this.points[i+1] || p1;
+ fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
}
},
diff --git a/src/rect.class.js b/src/rect.class.js
index e24d2d9148a..c03549fb219 100644
--- a/src/rect.class.js
+++ b/src/rect.class.js
@@ -119,74 +119,25 @@
ctx.closePath();
this._renderFill(ctx);
- this._removeShadow(ctx);
-
- if (this.strokeDashArray) {
- this._renderDashedStroke(ctx);
- }
- else if (this.stroke) {
- ctx.stroke();
- }
+ this._renderStroke(ctx);
},
/**
* @private
+ * @param ctx {CanvasRenderingContext2D} context to render on
*/
_renderDashedStroke: function(ctx) {
+ var x = -this.width/2,
+ y = -this.height/2,
+ w = this.width,
+ h = this.height;
- if (1 & this.strokeDashArray.length /* if odd number of items */) {
- /* duplicate items */
- this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
- }
-
- var i = 0,
- x = -this.width/2, y = -this.height/2,
- _this = this,
- padding = this.padding,
- dashedArrayLength = this.strokeDashArray.length;
-
- ctx.save();
ctx.beginPath();
-
- /** @ignore */
- function renderSide(xMultiplier, yMultiplier) {
-
- var lineLength = 0,
- lengthDiff = 0,
- sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2;
-
- while (lineLength < sideLength) {
-
- var lengthOfSubPath = _this.strokeDashArray[i++];
- lineLength += lengthOfSubPath;
-
- if (lineLength > sideLength) {
- lengthDiff = lineLength - sideLength;
- }
-
- // track coords
- if (xMultiplier) {
- x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0);
- }
- else {
- y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0);
- }
-
- ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y);
- if (i >= dashedArrayLength) {
- i = 0;
- }
- }
- }
-
- renderSide(1, 0);
- renderSide(0, 1);
- renderSide(-1, 0);
- renderSide(0, -1);
-
- ctx.stroke();
+ fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray);
ctx.closePath();
- ctx.restore();
},
/**
diff --git a/src/spray_brush.class.js b/src/spray_brush.class.js
index a1652b5ccdc..8907bc1b3d7 100644
--- a/src/spray_brush.class.js
+++ b/src/spray_brush.class.js
@@ -96,8 +96,9 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric
}
}
- this.canvas.renderOnAddition = originalRenderOnAddition;
this.canvas.clearContext(this.canvas.contextTop);
+ this.removeShadowStyles();
+ this.canvas.renderOnAddition = originalRenderOnAddition;
this.canvas.renderAll();
},
diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js
index a7a4bcb5329..88781a9c931 100644
--- a/src/static_canvas.class.js
+++ b/src/static_canvas.class.js
@@ -1160,7 +1160,7 @@
* (either those of HTMLCanvasElement itself, or rendering context)
*
* @param methodName {String} Method to check support for;
- * Could be one of "getImageData", "toDataURL" or "toDataURLWithQuality"
+ * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
* @return {Boolean | null} `true` if method is supported (or at least exists),
* `null` if canvas element or context can not be initialized
*/
@@ -1181,6 +1181,9 @@
case 'getImageData':
return typeof ctx.getImageData !== 'undefined';
+ case 'setLineDash':
+ return typeof ctx.setLineDash !== 'undefined';
+
case 'toDataURL':
return typeof el.toDataURL !== 'undefined';
diff --git a/src/triangle.class.js b/src/triangle.class.js
index ec1c3e71a0f..41028624950 100644
--- a/src/triangle.class.js
+++ b/src/triangle.class.js
@@ -52,10 +52,22 @@
ctx.closePath();
this._renderFill(ctx);
+ this._renderStroke(ctx);
+ },
- if (this.stroke) {
- ctx.stroke();
- }
+ /**
+ * @private
+ * @param ctx {CanvasRenderingContext2D} Context to render on
+ */
+ _renderDashedStroke: function(ctx) {
+ var widthBy2 = this.width / 2,
+ heightBy2 = this.height / 2;
+
+ ctx.beginPath();
+ fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
+ fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
+ ctx.closePath();
},
/**