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(); }, /**