From 328f14f38861f4a3a04aa9e719f236c538648928 Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 31 Oct 2013 17:36:18 +0000 Subject: [PATCH 01/27] Add zoom --- src/canvas.class.js | 15 ++- src/mixins/canvas_events.mixin.js | 5 +- src/mixins/object_geometry.mixin.js | 50 ++++++--- src/mixins/object_interactivity.mixin.js | 126 ++++++++++++----------- src/shapes/group.class.js | 54 ++++++---- src/shapes/image.class.js | 24 +++++ src/shapes/object.class.js | 28 +++++ src/shapes/path.class.js | 16 +++ src/shapes/path_group.class.js | 16 +++ src/shapes/text.class.js | 14 +++ src/static_canvas.class.js | 97 +++++++++++++++++ src/util/misc.js | 41 ++++++++ 12 files changed, 381 insertions(+), 105 deletions(-) diff --git a/src/canvas.class.js b/src/canvas.class.js index caf1781f..a2c12606 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -263,7 +263,8 @@ _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), x = pointer.x, - y = pointer.y; + y = pointer.y, + lt; var isObjectInGroup = ( activeGroup && @@ -272,8 +273,10 @@ ); if (isObjectInGroup) { - x -= activeGroup.left; - y -= activeGroup.top; + lt = new fabric.Point(activeGroup.left, activeGroup.top); + lt = fabric.util.transformPoint(lt, this.viewportTransform, true); + x -= lt.x; + y -= lt.y; } return { x: x, y: y }; }, @@ -382,7 +385,10 @@ var action = 'drag', corner, - pointer = getPointer(e, target.canvas.upperCanvasEl); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -852,6 +858,7 @@ */ getPointer: function (e) { var pointer = getPointer(e, this.upperCanvasEl); + return { x: pointer.x - this._offset.left, y: pointer.y - this._offset.top diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index ea70a739..330d2877 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -402,7 +402,10 @@ } else { // object is being transformed (scaled/rotated/moved/etc.) - pointer = getPointer(e, this.upperCanvasEl); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); var x = pointer.x, y = pointer.y, diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 7111ada1..3deff055 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -326,40 +326,40 @@ var offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), - cosTh = Math.cos(theta); - - var coords = this.getCenterPoint(); + cosTh = Math.cos(theta), + coords = this.getCenterPoint(), + wh = new fabric.Point(this.currentWidth, this.currentHeight); var tl = { x: coords.x - offsetX, y: coords.y - offsetY }; var tr = { - x: tl.x + (this.currentWidth * cosTh), - y: tl.y + (this.currentWidth * sinTh) + x: tl.x + (wh.x * cosTh), + y: tl.y + (wh.x * sinTh) }; var br = { - x: tr.x - (this.currentHeight * sinTh), - y: tr.y + (this.currentHeight * cosTh) + x: tr.x - (wh.y * sinTh), + y: tr.y + (wh.y * cosTh) }; var bl = { - x: tl.x - (this.currentHeight * sinTh), - y: tl.y + (this.currentHeight * cosTh) + x: tl.x - (wh.y * sinTh), + y: tl.y + (wh.y * cosTh) }; var ml = { - x: tl.x - (this.currentHeight/2 * sinTh), - y: tl.y + (this.currentHeight/2 * cosTh) + x: tl.x - (wh.y/2 * sinTh), + y: tl.y + (wh.y/2 * cosTh) }; var mt = { - x: tl.x + (this.currentWidth/2 * cosTh), - y: tl.y + (this.currentWidth/2 * sinTh) + x: tl.x + (wh.x/2 * cosTh), + y: tl.y + (wh.x/2 * sinTh) }; var mr = { - x: tr.x - (this.currentHeight/2 * sinTh), - y: tr.y + (this.currentHeight/2 * cosTh) + x: tr.x - (wh.y/2 * sinTh), + y: tr.y + (wh.y/2 * cosTh) }; var mb = { - x: bl.x + (this.currentWidth/2 * cosTh), - y: bl.y + (this.currentWidth/2 * sinTh) + x: bl.x + (wh.x/2 * cosTh), + y: bl.y + (wh.x/2 * sinTh) }; var mtr = { x: mt.x, @@ -389,6 +389,22 @@ mtr: mtr }; + var tform; + if (typeof this.canvas == 'undefined') { + if (this.type == 'group') { + tform = this._objects[0].canvas.viewportTransform; + } + else { + tform = [1, 0, 0, 1, 0, 0]; + } + } + else { + tform = this.canvas.viewportTransform; + } + for (c in this.oCoords) { + this.oCoords[c] = fabric.util.transformPoint(this.oCoords[c], tform); + } + // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index 2b9bd95b..458e91b5 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -266,25 +266,31 @@ scaleY = 1 / this._constrainScale(this.scaleY); ctx.lineWidth = 1 / this.borderScaleFactor; - - ctx.scale(scaleX, scaleY); - - var w = this.getWidth(), - h = this.getHeight(); + + var wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), this.canvas.viewportTransform, true), + w = wh.x, + h = wh.y, + sx= sxy.x, + sy= sxy.y; + if (this.get('group')) { + w = w * this.get('group').scaleX; + h = h * this.get('group').scaleY; + } ctx.strokeRect( - ~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) - 0.5, // offset needed to make lines look sharper - ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) - 0.5, - ~~(w + padding2 + strokeWidth * this.scaleX) + 1, // double offset needed to make lines look sharper - ~~(h + padding2 + strokeWidth * this.scaleY) + 1 + ~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper + ~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5, + ~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper + ~~(h + padding2 + strokeWidth * sy) + 1 ); if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { var rotateHeight = ( this.flipY - ? h + (strokeWidth * this.scaleY) + (padding * 2) - : -h - (strokeWidth * this.scaleY) - (padding * 2) + ? h + (strokeWidth * sx) + (padding * 2) + : -h - (strokeWidth * sy) - (padding * 2) ) / 2; ctx.beginPath(); @@ -300,7 +306,7 @@ /** * Draws corners of an object's bounding box. - * Requires public properties: width, height, scaleX, scaleY + * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg @@ -312,99 +318,95 @@ var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - left = -(this.width / 2), - top = -(this.height / 2), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + width = wh.x, + height = wh.y, + left = -(width / 2), + top = -(height / 2), _left, _top, - sizeX = size / this.scaleX, - sizeY = size / this.scaleY, - paddingX = this.padding / this.scaleX, - paddingY = this.padding / this.scaleY, - scaleOffsetY = size2 / this.scaleY, - scaleOffsetX = size2 / this.scaleX, - scaleOffsetSizeX = (size2 - size) / this.scaleX, - scaleOffsetSizeY = (size2 - size) / this.scaleY, - height = this.height, - width = this.width, + padding = this.padding, + scaleOffset = size2, + scaleOffsetSize = size2 - size, methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', transparent = this.transparentCorners, isVML = typeof G_vmlCanvasManager !== 'undefined'; ctx.save(); - ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); + ctx.lineWidth = 1; ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = ctx.fillStyle = this.cornerColor; // top-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // top-right - _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width - scaleOffset + strokeWidth2 + padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); if (!this.get('lockUniScaling')) { // middle-top - _left = left + width/2 - scaleOffsetX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width/2 - scaleOffset; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-bottom - _left = left + width/2 - scaleOffsetX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width/2 - scaleOffset; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } // middle-top-rotate if (this.hasRotatingPoint) { - _left = left + width/2 - scaleOffsetX; + _left = left + width/2 - scaleOffset; _top = this.flipY ? - (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + (top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding) + : (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding); - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } ctx.restore(); diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index ab7c054a..89c0fd90 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -6,7 +6,8 @@ extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke; + invoke = fabric.util.array.invoke, + degreesToRadians = fabric.util.degreesToRadians; if (fabric.Group) { return; @@ -215,9 +216,18 @@ if (!this.visible) return; ctx.save(); - this.transform(ctx); + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } - var groupScaleFactor = Math.max(this.scaleX, this.scaleY); + var sxy = fabric.util.transformPoint( + new fabric.Point(this.scaleX, this.scaleY), + v, true), + groupScaleFactor = Math.max(sxy.x, sxy.y); this.clipTo && fabric.util.clipContext(this, ctx); @@ -231,22 +241,21 @@ // do not render if object is not visible if (!object.visible) continue; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; - object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; } this.clipTo && ctx.restore(); - if (!noTransform && this.active) { + if (this.active && !noTransform) { + var center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } ctx.restore(); - this.setCoords(); }, /** @@ -394,9 +403,10 @@ _calcBounds: function() { var aX = [], aY = [], - minX, minY, maxX, maxY, o, width, height, + minX, minY, maxX, maxY, o, width, height, minXY, maxXY, ivt, // TODO: cleanup i = 0, - len = this._objects.length; + len = this._objects.length, + canvas = this._objects[0].canvas; for (; i < len; ++i) { o = this._objects[i]; @@ -407,19 +417,21 @@ } } - minX = min(aX); - maxX = max(aX); - minY = min(aY); - maxY = max(aY); + minXY = new fabric.Point(min(aX), min(aY)); + maxXY = new fabric.Point(max(aX), max(aY)); + // TODO: cleanup + ivt = fabric.util.invertTransform(canvas.viewportTransform); + this.width = (maxXY.x - minXY.x) || 0; + this.height = (maxXY.y - minXY.y) || 0; - width = (maxX - minX) || 0; - height = (maxY - minY) || 0; + // TODO: cleanup + minXY = fabric.util.transformPoint(minXY, ivt); + maxXY = fabric.util.transformPoint(maxXY, ivt); + this.width = (maxXY.x - minXY.x) || 0; + this.height = (maxXY.y - minXY.y) || 0; - this.width = width; - this.height = height; - - this.left = (minX + width / 2) || 0; - this.top = (minY + height / 2) || 0; + this.left = (minXY.x + maxXY.x) / 2 || 0; + this.top = (minXY.y + maxXY.y) / 2 || 0; }, /* _TO_SVG_START_ */ diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index dc5348c4..e16af047 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -101,8 +101,17 @@ ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } var isInPathGroup = this.group && this.group.type !== 'group'; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); @@ -124,8 +133,23 @@ this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index dad164f2..764c72cb 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1012,11 +1012,24 @@ ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); } if (!noTransform) { + if (this.group) { + this.group.transform(ctx); + } this.transform(ctx); } @@ -1046,8 +1059,23 @@ this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index 81eeece8..98be5a88 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -431,6 +431,16 @@ ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -464,8 +474,14 @@ this._renderStroke(ctx); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/shapes/path_group.class.js b/src/shapes/path_group.class.js index ddc56add..18e528a3 100644 --- a/src/shapes/path_group.class.js +++ b/src/shapes/path_group.class.js @@ -68,6 +68,16 @@ ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -81,8 +91,14 @@ } this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 085f5b15..3a03872d 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -757,8 +757,22 @@ if (!this.visible) return; ctx.save(); + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 203c1d89..a7011f98 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -139,6 +139,20 @@ */ allowTouchScrolling: false, + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: [1, 0, 0, 1, 0, 0], + + /** + * Color of canvas border + * @type String + * @default + */ + canvasBorderColor: '', + /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -427,6 +441,68 @@ return this; }, + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + + /** + * Returns point at center of viewport + * @return {fabric.Point} the top left corner of the viewport + */ + getViewportCenter: function () { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ), + x = this.viewportTransform[4], + y = this.viewportTransform[5]; + + return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + // TODO: just change the scale, preserve other transformations + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + return this; + }, + + /** + * Centers viewport of this canvas instance on given point + * @param {Numer} x value for center of viewport + * @param {Numer} y value for center of viewport + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportCenter: function (x, y) { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ); + this.viewportTransform[4] = x - wh.x/2; + this.viewportTransform[5] = y - wh.y/2; + return this; + }, + + /** + * Centers viewport of this canvas instance + * @return {fabric.Canvas} instance + * @chainable true + */ + centerViewport: function () { + return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + }, + /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} @@ -581,6 +657,10 @@ if (typeof this.backgroundImage === 'object') { this._drawBackroundImage(canvasToDrawOn); } + + if (this.canvasBorderColor) { + this._drawCanvasBorder(canvasToDrawOn); + } var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { @@ -637,6 +717,23 @@ canvasToDrawOn.restore(); }, + /** + * @private + * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on + */ + _drawCanvasBorder: function(canvasToDrawOn) { + var xy = fabric.util.transformPoint(new fabric.Point(0, 0), this.viewportTransform), + wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform, true + ); + canvasToDrawOn.save(); + canvasToDrawOn.lineWidth = 1; + canvasToDrawOn.strokeStyle = this.canvasBorderColor; + canvasToDrawOn.strokeRect(xy.x - 1.5, xy.y - 1.5, wh.x + 2, wh.y + 2); + canvasToDrawOn.restore(); + }, + /** * Method to render only the top canvas. * Also used to render the group selection box. diff --git a/src/util/misc.js b/src/util/misc.js index e99e50d1..cdaa840e 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -81,7 +81,46 @@ return new fabric.Point(rx, ry).addEquals(origin); } + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + function transformPoint(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[1] * p.y, + t[2] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[1] * p.y + t[4], + t[2] * p.x + t[3] * p.y + t[5] + ); + } + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + function invertTransform(t) { + var r = t.slice(), + a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; + var o = transformPoint({x: t[4], y: t[5]}, r); + r[4] = -o.x; + r[5] = -o.y; + return r + } + /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static @@ -519,6 +558,8 @@ fabric.util.degreesToRadians = degreesToRadians; fabric.util.radiansToDegrees = radiansToDegrees; fabric.util.rotatePoint = rotatePoint; + fabric.util.transformPoint = transformPoint; + fabric.util.invertTransform = invertTransform; fabric.util.toFixed = toFixed; fabric.util.getRandomInt = getRandomInt; fabric.util.falseFunction = falseFunction; From 88b589b3d60846aa92b4b097b85594c735746430 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 8 Nov 2013 15:54:55 +0000 Subject: [PATCH 02/27] Implement zoom for brushes, various zoom fixes, remove canvasBorder. --- dist/all.js | 2540 +++++++++++++++++-- dist/all.require.js | 2842 +++++++++++++++++++--- src/brushes/circle_brush.class.js | 1 + src/brushes/pencil_brush.class.js | 4 + src/brushes/spray_brush.class.js | 13 +- src/canvas.class.js | 21 +- src/mixins/canvas_events.mixin.js | 15 +- src/mixins/object_geometry.mixin.js | 95 +- src/mixins/object_interactivity.mixin.js | 17 +- src/shapes/group.class.js | 24 +- src/shapes/object.class.js | 6 +- src/static_canvas.class.js | 41 +- 12 files changed, 5063 insertions(+), 556 deletions(-) diff --git a/dist/all.js b/dist/all.js index 3ef9f0ae..3179ebb1 100644 --- a/dist/all.js +++ b/dist/all.js @@ -1,4 +1,4 @@ -/* build: `node build.js modules=ALL exclude=gestures` */ +/* build: `node build.js modules=ALL` */ /*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: "1.3.7" }; @@ -1749,6 +1749,1912 @@ if (!JSON) { } }()); +/* + ---------------------------------------------------- + Event.js : 1.1.1 : 2012/11/19 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- + 1 : click, dblclick, dbltap + 1+ : tap, longpress, drag, swipe + 2+ : pinch, rotate + : mousewheel, devicemotion, shake + ---------------------------------------------------- + TODO + ---------------------------------------------------- + * switch configuration to 4th argument on addEventListener + * bbox calculation for elements scaled with transform. + ---------------------------------------------------- + NOTES + ---------------------------------------------------- + * When using other libraries that may have built in "Event" namespace, + i.e. Typescript, you can use "eventjs" instead of "Event" for all example calls. + ---------------------------------------------------- + REQUIREMENTS: querySelector, querySelectorAll + ---------------------------------------------------- + * There are two ways to add/remove events with this library. + ---------------------------------------------------- + // Retains "this" attribute as target, and overrides native addEventListener. + target.addEventListener(type, listener, useCapture); + target.removeEventListener(type, listener, useCapture); + + // Attempts to perform as fast as possible. + Event.add(type, listener, configure); + Event.remove(type, listener, configure); + + * You can turn prototyping on/off for individual features. + ---------------------------------------------------- + Event.modifyEventListener = true; // add custom *EventListener commands to HTMLElements. + Event.modifySelectors = true; // add bulk *EventListener commands on NodeLists from querySelectorAll and others. + + * Example of setting up a single listener with a custom configuration. + ---------------------------------------------------- + // optional configuration. + var configure = { + fingers: 2, // listen for specifically two fingers. + snap: 90 // snap to 90 degree intervals. + }; + // adding with addEventListener() + target.addEventListener("swipe", function(event) { + // additional variables can be found on the event object. + console.log(event.velocity, event.angle, event.fingers); + }, configure); + + // adding with Event.add() + Event.add("swipe", function(event, self) { + // additional variables can be found on the self object. + console.log(self.velocity, self.angle, self.fingers); + }, configure); + + * Multiple listeners glued together. + ---------------------------------------------------- + // adding with addEventListener() + target.addEventListener("click swipe", function(event) { }); + + // adding with Event.add() + Event.add(target, "click swipe", function(event, self) { }); + + * Use query selectors to create an event (querySelectorAll) + ---------------------------------------------------- + // adding events to NodeList from querySelectorAll() + document.querySelectorAll("#element a.link").addEventListener("click", callback); + + // adding with Event.add() + Event.add("#element a.link", "click", callback); + + * Listen for selector to become available (querySelector) + ---------------------------------------------------- + Event.add("body", "ready", callback); + // or... + Event.add({ + target: "body", + type: "ready", + timeout: 10000, // set a timeout to stop checking. + interval: 30, // set how often to check for element. + listener: callback + }); + + * Multiple listeners bound to one callback w/ single configuration. + ---------------------------------------------------- + var bindings = Event.add({ + target: target, + type: "click swipe", + snap: 90, // snap to 90 degree intervals. + minFingers: 2, // minimum required fingers to start event. + maxFingers: 4, // maximum fingers in one event. + listener: function(event, self) { + console.log(self.gesture); // will be click or swipe. + console.log(self.x); + console.log(self.y); + console.log(self.identifier); + console.log(self.start); + console.log(self.fingers); // somewhere between "2" and "4". + self.pause(); // disable event. + self.resume(); // enable event. + self.remove(); // remove event. + } + }); + + * Multiple listeners bound to multiple callbacks w/ single configuration. + ---------------------------------------------------- + var bindings = Event.add({ + target: target, + minFingers: 1, + maxFingers: 12, + listeners: { + click: function(event, self) { + self.remove(); // removes this click listener. + }, + swipe: function(event, self) { + binding.remove(); // removes both the click + swipe listeners. + } + } + }); + + * Multiple listeners bound to multiple callbacks w/ multiple configurations. + ---------------------------------------------------- + var binding = Event.add({ + target: target, + listeners: { + longpress: { + fingers: 1, + wait: 500, // milliseconds + listener: function(event, self) { + console.log(self.fingers); // "1" finger. + } + }, + drag: { + fingers: 3, + position: "relative", // "relative", "absolute", "difference", "move" + listener: function(event, self) { + console.log(self.fingers); // "3" fingers. + console.log(self.x); // coordinate is relative to edge of target. + } + } + } + }); + + * Capturing an event and manually forwarding it to a proxy (tiered events). + ---------------------------------------------------- + Event.add(target, "down", function(event, self) { + var x = event.pageX; // local variables that wont change. + var y = event.pageY; + Event.proxy.drag({ + event: event, + target: target, + listener: function(event, self) { + console.log(x - event.pageX); // measure movement. + console.log(y - event.pageY); + } + }); + }); + ---------------------------------------------------- + + * Event proxies. + * type, fingers, state, start, x, y, position, bbox + * rotation, scale, velocity, angle, delay, timeout + ---------------------------------------------------- + // "Click" :: fingers, minFingers, maxFingers. + Event.add(window, "click", function(event, self) { + console.log(self.gesture, self.x, self.y); + }); + // "Double-Click" :: fingers, minFingers, maxFingers. + Event.add(window, "dblclick", function(event, self) { + console.log(self.gesture, self.x, self.y); + }); + // "Drag" :: fingers, maxFingers, position + Event.add(window, "drag", function(event, self) { + console.log(self.gesture, self.fingers, self.state, self.start, self.x, self.y, self.bbox); + }); + // "Gesture" :: fingers, minFingers, maxFingers. + Event.add(window, "gesture", function(event, self) { + console.log(self.gesture, self.fingers, self.state, self.rotation, self.scale); + }); + // "Swipe" :: fingers, minFingers, maxFingers, snap, threshold. + Event.add(window, "swipe", function(event, self) { + console.log(self.gesture, self.fingers, self.velocity, self.angle, self.start, self.x, self.y); + }); + // "Tap" :: fingers, minFingers, maxFingers, timeout. + Event.add(window, "tap", function(event, self) { + console.log(self.gesture, self.fingers); + }); + // "Longpress" :: fingers, minFingers, maxFingers, delay. + Event.add(window, "longpress", function(event, self) { + console.log(self.gesture, self.fingers); + }); + // + Event.add(window, "shake", function(event, self) { + console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); + }); + // + Event.add(window, "devicemotion", function(event, self) { + console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); + }); + // + Event.add(window, "wheel", function(event, self) { + console.log(self.gesture, self.state, self.wheelDelta); + }); + + * Stop, prevent and cancel. + ---------------------------------------------------- + Event.stop(event); // stop bubble. + Event.prevent(event); // prevent default. + Event.cancel(event); // stop and prevent. + + * Track for proper command/control-key for Mac/PC. + ---------------------------------------------------- + Event.add(window, "keyup keydown", Event.proxy.metaTracker); + console.log(Event.proxy.metaKey); + + * Test for event features, in this example Drag & Drop file support. + ---------------------------------------------------- + console.log(Event.supports('dragstart') && Event.supports('drop') && !!window.FileReader); + + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(eventjs) === "undefined") + var eventjs = Event; + +Event = (function(root) { + "use strict"; + +// Add custom *EventListener commands to HTMLElements. + root.modifyEventListener = false; + +// Add bulk *EventListener commands on NodeLists from querySelectorAll and others. + root.modifySelectors = false; + +// Event maintenance. + root.add = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "add"); + }; + + root.remove = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "remove"); + }; + + root.stop = function(event) { + if (event.stopPropagation) + event.stopPropagation(); + event.cancelBubble = true; // <= IE8 + event.bubble = 0; + }; + + root.prevent = function(event) { + if (event.preventDefault) + event.preventDefault(); + event.returnValue = false; // <= IE8 + }; + + root.cancel = function(event) { + root.stop(event); + root.prevent(event); + }; + +// Check whether event is natively supported (via @kangax) + root.supports = function(target, type) { + if (typeof(target) === "string") { + type = target; + target = window; + } + type = "on" + type; + if (type in target) + return true; + if (!target.setAttribute) + target = document.createElement("div"); + if (target.setAttribute && target.removeAttribute) { + target.setAttribute(type, ""); + var isSupported = typeof target[type] === "function"; + if (typeof target[type] !== "undefined") + target[type] = null; + target.removeAttribute(type); + return isSupported; + } + }; + + var clone = function(obj) { + if (!obj || typeof (obj) !== 'object') + return obj; + var temp = new obj.constructor(); + for (var key in obj) { + if (!obj[key] || typeof (obj[key]) !== 'object') { + temp[key] = obj[key]; + } else { // clone sub-object + temp[key] = clone(obj[key]); + } + } + return temp; + }; + +/// Handle custom *EventListener commands. + var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { + configure = configure || {}; + // Check for element to load on interval (before onload). + if (typeof(target) === "string" && type === "ready") { + var time = (new Date()).getTime(); + var timeout = configure.timeout; + var ms = configure.interval || 1000 / 60; + var interval = window.setInterval(function() { + if ((new Date()).getTime() - time > timeout) { + window.clearInterval(interval); + } + if (document.querySelector(target)) { + window.clearInterval(interval); + listener(); + } + }, ms); + return; + } + // Get DOM element from Query Selector. + if (typeof(target) === "string") { + target = document.querySelectorAll(target); + if (target.length === 0) + return createError("Missing target on listener!"); // No results. + if (target.length === 1) { // Single target. + target = target[0]; + } + } + /// Handle multiple targets. + var event; + var events = {}; + if (target.length > 0) { + for (var n0 = 0, length0 = target.length; n0 < length0; n0++) { + event = eventManager(target[n0], type, listener, clone(configure), trigger); + if (event) + events[n0] = event; + } + return createBatchCommands(events); + } + // Check for multiple events in one string. + if (type.indexOf && type.indexOf(" ") !== -1) + type = type.split(" "); + if (type.indexOf && type.indexOf(",") !== -1) + type = type.split(","); + // Attach or remove multiple events associated with a target. + if (typeof(type) !== "string") { // Has multiple events. + if (typeof(type.length) === "number") { // Handle multiple listeners glued together. + for (var n1 = 0, length1 = type.length; n1 < length1; n1++) { // Array [type] + event = eventManager(target, type[n1], listener, clone(configure), trigger); + if (event) + events[type[n1]] = event; + } + } else { // Handle multiple listeners. + for (var key in type) { // Object {type} + if (typeof(type[key]) === "function") { // without configuration. + event = eventManager(target, key, type[key], clone(configure), trigger); + } else { // with configuration. + event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); + } + if (event) + events[key] = event; + } + } + return createBatchCommands(events); + } + // Ensure listener is a function. + if (typeof(listener) !== "function") + return createError("Listener is not a function!"); + // Generate a unique wrapper identifier. + var useCapture = configure.useCapture || false; + var id = normalize(type) + getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); + // Handle the event. + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event. + if (trigger === "remove") { // Remove event listener. + if (!wrappers[id]) + return; // Already removed. + wrappers[id].remove(); + delete wrappers[id]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[id]) + return wrappers[id]; // Already attached. + // Retains "this" orientation. + if (configure.useCall && !root.modifyEventListener) { + var tmp = listener; + listener = function(event, self) { + for (var key in self) + event[key] = self[key]; + return tmp.call(target, event); + }; + } + // Create listener proxy. + configure.gesture = type; + configure.target = target; + configure.listener = listener; + configure.fromOverwrite = fromOverwrite; + // Record wrapper. + wrappers[id] = root.proxy[type](configure); + } + } else { // Fire native event. + type = normalize(type); + if (trigger === "remove") { // Remove event listener. + if (!wrappers[id]) + return; // Already removed. + target[remove](type, listener, useCapture); + delete wrappers[id]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[id]) + return wrappers[id]; // Already attached. + target[add](type, listener, useCapture); + // Record wrapper. + wrappers[id] = { + type: type, + target: target, + listener: listener, + remove: function() { + root.remove(target, type, listener, configure); + } + }; + } + } + return wrappers[id]; + }; + +/// Perform batch actions on multiple events. + var createBatchCommands = function(events) { + return { + remove: function() { // Remove multiple events. + for (var key in events) { + events[key].remove(); + } + }, + add: function() { // Add multiple events. + for (var key in events) { + events[key].add(); + } + } + }; + }; + +/// Display error message in console. + var createError = function(message) { + if (typeof(console) === "undefined") + return; + if (typeof(console.error) === "undefined") + return; + console.error(message); + }; + +/// Handle naming discrepancies between platforms. + var normalize = (function() { + var translate = {}; + return function(type) { + if (!root.pointerType) { + if (window.navigator.msPointerEnabled) { + root.pointerType = "mspointer"; + translate = { + "mousedown": "MSPointerDown", + "mousemove": "MSPointerMove", + "mouseup": "MSPointerUp" + }; + } else if (root.supports("touchstart")) { + root.pointerType = "touch"; + translate = { + "mousedown": "touchstart", + "mouseup": "touchend", + "mousemove": "touchmove" + }; + } else { + root.pointerType = "mouse"; + } + } + if (translate[type]) + type = translate[type]; + if (!document.addEventListener) { // IE + return "on" + type; + } else { + return type; + } + }; + })(); + +/// Event wrappers to keep track of all events placed in the window. + var wrappers = {}; + var counter = 0; + var getID = function(object) { + if (object === window) + return "#window"; + if (object === document) + return "#document"; + if (!object) + return createError("Missing target on listener!"); + if (!object.uniqueID) + object.uniqueID = "id" + counter++; + return object.uniqueID; + }; + +/// Detect platforms native *EventListener command. + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + + /* + Pointer.js + ------------------------ + Modified from; https://github.com/borismus/pointer.js + */ + + root.createPointerEvent = function(event, self, preventRecord) { + var eventName = self.gesture; + var target = self.target; + var pts = event.changedTouches || root.proxy.getCoords(event); + if (pts.length) { + var pt = pts[0]; + self.pointers = preventRecord ? [] : pts; + self.pageX = pt.pageX; + self.pageY = pt.pageY; + self.x = self.pageX; + self.y = self.pageY; + } + /// + var newEvent = document.createEvent("Event"); + newEvent.initEvent(eventName, true, true); + newEvent.originalEvent = event; + for (var k in self) { + if (k === "target") + continue; + newEvent[k] = self[k]; + } + target.dispatchEvent(newEvent); + }; + +/// Allows *EventListener to use custom event proxies. + if (root.modifyEventListener && window.HTMLElement) + (function() { + var augmentEventListener = function(proto) { + var recall = function(trigger) { // overwrite native *EventListener's + var handle = trigger + "EventListener"; + var handler = proto[handle]; + proto[handle] = function(type, listener, useCapture) { + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. + var configure = useCapture; + if (typeof(useCapture) === "object") { + configure.useCall = true; + } else { // convert to configuration object. + configure = { + useCall: true, + useCapture: useCapture + }; + } + eventManager(this, type, listener, configure, trigger, true); + handler.call(this, type, listener, useCapture); + } else { // use native function. + handler.call(this, normalize(type), listener, useCapture); + } + }; + }; + recall("add"); + recall("remove"); + }; + // NOTE: overwriting HTMLElement doesn't do anything in Firefox. + if (navigator.userAgent.match(/Firefox/)) { + // TODO: fix Firefox for the general case. + augmentEventListener(HTMLDivElement.prototype); + augmentEventListener(HTMLCanvasElement.prototype); + } else { + augmentEventListener(HTMLElement.prototype); + } + augmentEventListener(document); + augmentEventListener(window); + })(); + +/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk. + if (root.modifySelectors) + (function() { + var proto = NodeList.prototype; + proto.removeEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].removeEventListener(type, listener, useCapture); + } + }; + proto.addEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].addEventListener(type, listener, useCapture); + } + }; + })(); + + return root; + +})(Event); +/* + ---------------------------------------------------- + Event.proxy : 0.4.2 : 2012/07/29 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- + Pointer Gestures + ---------------------------------------------------- + 1 : click, dblclick, dbltap + 1+ : tap, taphold, drag, swipe + 2+ : pinch, rotate + ---------------------------------------------------- + Gyroscope Gestures + ---------------------------------------------------- + * shake + ---------------------------------------------------- + Fixes issues with + ---------------------------------------------------- + * mousewheel-Firefox uses DOMMouseScroll and does not return wheelDelta. + * devicemotion-Fixes issue where event.acceleration is not returned. + ---------------------------------------------------- + Ideas for the future + ---------------------------------------------------- + * Keyboard, GamePad, and other input abstractions. + * Event batching - i.e. for every x fingers down a new gesture is created. + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + /* + Create a new pointer gesture instance. + */ + + root.pointerSetup = function(conf, self) { + /// Configure. + conf.doc = conf.target.ownerDocument || conf.target; // Associated document. + conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers. + conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers. + conf.position = conf.position || "relative"; // Determines what coordinate system points are returned. + delete conf.fingers; //- + /// Convenience data. + self = self || {}; + self.gesture = conf.gesture; + self.target = conf.target; + self.pointerType = Event.pointerType; + /// + if (Event.modifyEventListener && conf.fromOverwrite) + conf.listener = Event.createPointerEvent; + /// Convenience commands. + var fingers = 0; + var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; + self.listener = conf.listener; + self.proxy = function(listener) { + self.defaultListener = conf.listener; + conf.listener = listener; + listener(conf.event, self); + }; + self.remove = function() { + if (conf.onPointerDown) + Event.remove(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) + Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) + Event.remove(conf.doc, type + "up", conf.onPointerUp); + }; + self.resume = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) + Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.move)) + Event.add(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = fingers; + }; + self.pause = function(opt) { + fingers = conf.fingers; + if (conf.onPointerMove && (!opt || opt.move)) + Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) + Event.remove(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = 0; + }; + /// + return self; + }; + + /* + Begin proxied pointer command. + */ + + root.pointerStart = function(event, self, conf) { + var addTouchStart = function(touch, sid) { + var bbox = conf.bbox; + var pt = track[sid] = {}; + /// + switch (conf.position) { + case "absolute": // Absolute from within window. + pt.offsetX = 0; + pt.offsetY = 0; + break; + case "difference": // Relative from origin. + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; + case "move": // Move target element. + pt.offsetX = touch.pageX - bbox.x1; + pt.offsetY = touch.pageY - bbox.y1; + break; + default: // Relative from within target. + pt.offsetX = bbox.x1; + pt.offsetY = bbox.y1; + break; + } + /// + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; + var y = (touch.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; + } else { + var x = (touch.pageX - pt.offsetX); + var y = (touch.pageY - pt.offsetY); + } + /// + pt.rotation = 0; + pt.scale = 1; + pt.startTime = pt.moveTime = (new Date).getTime(); + pt.move = {x: x, y: y}; + pt.start = {x: x, y: y}; + /// + conf.fingers++; + }; + /// + conf.event = event; + if (self.defaultListener) { + conf.listener = self.defaultListener; + delete self.defaultListener; + } + /// + var isTouchStart = !conf.fingers; + var track = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Adding touch events to tracking. + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; // Touch ID. + // Track the current state of the touches. + if (conf.fingers) { + if (conf.fingers >= conf.maxFingers) { + var ids = []; + for (var sid in conf.tracker) + ids.push(sid); + self.identifier = ids.join(","); + return isTouchStart; + } + var fingers = 0; // Finger ID. + for (var rid in track) { + // Replace removed finger. + if (track[rid].up) { + delete track[rid]; + addTouchStart(touch, sid); + conf.cancel = true; + break; + } + fingers++; + } + // Add additional finger. + if (track[sid]) + continue; + addTouchStart(touch, sid); + } else { // Start tracking fingers. + track = conf.tracker = {}; + self.bbox = conf.bbox = root.getBoundingBox(conf.target); + conf.fingers = 0; + conf.cancel = false; + addTouchStart(touch, sid); + } + } + /// + var ids = []; + for (var sid in conf.tracker) + ids.push(sid); + self.identifier = ids.join(","); + /// + return isTouchStart; + }; + + /* + End proxied pointer command. + */ + + root.pointerEnd = function(event, self, conf, onPointerUp) { + // Record changed touches have ended (iOS changedTouches is not reliable). + var touches = event.touches || []; + var length = touches.length; + var exists = {}; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier; + exists[sid || Infinity] = true; + } + for (var sid in conf.tracker) { + var track = conf.tracker[sid]; + if (exists[sid] || track.up) + continue; + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + pageX: track.pageX, + pageY: track.pageY, + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers--; + } + /* // This should work but fails in Safari on iOS4 so not using it. + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Record changed touches have ended (this should work). + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var track = conf.tracker[sid]; + if (track && !track.up) { + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers --; + } + } */ + // Wait for all fingers to be released. + if (conf.fingers !== 0) + return false; + // Record total number of fingers gesture used. + var ids = []; + conf.gestureFingers = 0; + for (var sid in conf.tracker) { + conf.gestureFingers++; + ids.push(sid); + } + self.identifier = ids.join(","); + // Our pointer gesture has ended. + return true; + }; + + /* + Returns mouse coords in an array to match event.*Touches + ------------------------------------------------------------ + var touch = event.changedTouches || root.getCoords(event); + */ + + root.getCoords = function(event) { + if (typeof(event.pageX) !== "undefined") { // Desktop browsers. + root.getCoords = function(event) { + return Array({ + type: "mouse", + x: event.pageX, + y: event.pageY, + pageX: event.pageX, + pageY: event.pageY, + identifier: Infinity + }); + }; + } else { // Internet Explorer <= 8.0 + root.getCoords = function(event) { + event = event || window.event; + return Array({ + type: "mouse", + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop, + pageX: event.clientX + document.documentElement.scrollLeft, + pageY: event.clientY + document.documentElement.scrollTop, + identifier: Infinity + }); + }; + } + return root.getCoords(event); + }; + + /* + Returns single coords in an object. + ------------------------------------------------------------ + var mouse = root.getCoord(event); + */ + + root.getCoord = function(event) { + if ("ontouchstart" in window) { // Mobile browsers. + var pX = 0; + var pY = 0; + root.getCoord = function(event) { + var touches = event.changedTouches; + if (touches.length) { // ontouchstart + ontouchmove + return { + x: pX = touches[0].pageX, + y: pY = touches[0].pageY + }; + } else { // ontouchend + return { + x: pX, + y: pY + }; + } + }; + } else if (typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers. + root.getCoord = function(event) { + return { + x: event.pageX, + y: event.pageY + }; + }; + } else { // Internet Explorer <=8.0 + root.getCoord = function(event) { + event = event || window.event; + return { + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop + }; + }; + } + return root.getCoord(event); + }; + + /* + Get target scale and position in space. + */ + + root.getBoundingBox = function(o) { + if (o === window || o === document) + o = document.body; + /// + var bbox = { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + scrollLeft: 0, + scrollTop: 0 + }; + /// + if (o === document.body) { + bbox.height = window.innerHeight; + bbox.width = window.innerWidth; + } else { + bbox.height = o.offsetHeight; + bbox.width = o.offsetWidth; + } + /// Get the scale of the element. + bbox.scaleX = o.width / bbox.width || 1; + bbox.scaleY = o.height / bbox.height || 1; + /// Get the offset of element. + var tmp = o; + while (tmp !== null) { + bbox.x1 += tmp.offsetLeft; + bbox.y1 += tmp.offsetTop; + tmp = tmp.offsetParent; + } + ; + /// Get the scroll of container element. + var tmp = o.parentNode; + while (tmp !== null) { + if (tmp === document.body) + break; + if (tmp.scrollTop === undefined) + break; + bbox.scrollLeft += tmp.scrollLeft; + bbox.scrollTop += tmp.scrollTop; + tmp = tmp.parentNode; + } + ; + /// Record the extent of box. + bbox.x2 = bbox.x1 + bbox.width; + bbox.y2 = bbox.y1 + bbox.height; + /// + return bbox; + }; + + /* + Keep track of metaKey, the proper ctrlKey for users platform. + */ + + (function() { + var agent = navigator.userAgent.toLowerCase(); + var mac = agent.indexOf("macintosh") !== -1; + if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari. + var watch = {91: true, 93: true}; + } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox. + var watch = {224: true}; + } else { // windows, linux, or mac opera. + var watch = {17: true}; + } + root.isMetaKey = function(event) { + return !!watch[event.keyCode]; + }; + root.metaTracker = function(event) { + if (watch[event.keyCode]) { + root.metaKey = event.type === "keydown"; + } + }; + })(); + + return root; + +})(Event.proxy); +/* + "Click" event proxy. + ---------------------------------------------------- + Event.add(window, "click", function(event, self) {}); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.click = function(conf) { + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var EVENT; + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + EVENT = event; + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (EVENT.cancelBubble && ++EVENT.bubble > 1) + return; + var pointers = EVENT.changedTouches || root.getCoords(EVENT); + var pointer = pointers[0]; + var bbox = conf.bbox; + var newbbox = root.getBoundingBox(conf.target); + if (conf.position === "relative") { + var ax = (pointer.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var ay = (pointer.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var ax = (pointer.pageX - bbox.x1); + var ay = (pointer.pageY - bbox.y1); + } + if (ax > 0 && ax < bbox.width && // Within target coordinates. + ay > 0 && ay < bbox.height && + bbox.scrollTop === newbbox.scrollTop) { + conf.listener(EVENT, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "click"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.click = root.click; + + return root; + +})(Event.proxy); +/* + "Double-Click" aka "Double-Tap" event proxy. + ---------------------------------------------------- + Event.add(window, "dblclick", function(event, self) {}); + ---------------------------------------------------- + Touch an target twice for <= 700ms, with less than 25 pixel drift. + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.dbltap = + root.dblclick = function(conf) { + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var delay = 700; // in milliseconds + var time0, time1, timeout; + var pointer0, pointer1; + // Tracking the events. + conf.onPointerDown = function(event) { + var pointers = event.changedTouches || root.getCoords(event); + if (time0 && !time1) { // Click #2 + pointer1 = pointers[0]; + time1 = (new Date).getTime() - time0; + } else { // Click #1 + pointer0 = pointers[0]; + time0 = (new Date).getTime(); + time1 = 0; + clearTimeout(timeout); + timeout = setTimeout(function() { + time0 = 0; + }, delay); + } + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + if (time0 && !time1) { + var pointers = event.changedTouches || root.getCoords(event); + pointer1 = pointers[0]; + } + var bbox = conf.bbox; + if (conf.position === "relative") { + var ax = (pointer1.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var ay = (pointer1.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var ax = (pointer1.pageX - bbox.x1); + var ay = (pointer1.pageY - bbox.y1); + } + if (!(ax > 0 && ax < bbox.width && // Within target coordinates.. + ay > 0 && ay < bbox.height && + Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance. + Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + if (time0 && time1) { + if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { + self.state = conf.gesture; + conf.listener(event, self); + } + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "dblclick"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.dbltap = root.dbltap; + Event.Gesture._gestureHandlers.dblclick = root.dblclick; + + return root; + +})(Event.proxy); +/* + "Drag" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: maxFingers, position. + ---------------------------------------------------- + Event.add(window, "drag", function(event, self) { + console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.dragElement = function(that, event) { + root.drag({ + event: event, + target: that, + position: "move", + listener: function(event, self) { + that.style.left = self.x + "px"; + that.style.top = self.y + "px"; + Event.prevent(event); + } + }); + }; + + root.drag = function(conf) { + conf.gesture = "drag"; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + if (!conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Process event listener. + conf.onPointerMove(event, "down"); + }; + conf.onPointerMove = function(event, state) { + if (!conf.tracker) + return conf.onPointerDown(event); + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + // Identifier defined outside of listener. + if (!pt) + continue; + pt.pageX = touch.pageX; + pt.pageY = touch.pageY; + // Record data. + self.state = state || "move"; + self.identifier = identifier; + self.start = pt.start; + self.fingers = conf.fingers; + if (conf.position === "relative") { + self.x = (pt.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; + self.y = (pt.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; + } else { + self.x = (pt.pageX - pt.offsetX); + self.y = (pt.pageY - pt.offsetY); + } + /// + conf.listener(event, self); + } + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { + if (!conf.monitor) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + if (conf.event) { + conf.onPointerDown(conf.event); + } else { // + Event.add(conf.target, "mousedown", conf.onPointerDown); + if (conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.drag = root.drag; + + return root; + +})(Event.proxy); +/* + "Gesture" event proxy (2+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + var RAD_DEG = Math.PI / 180; + + root.gesture = function(conf) { + conf.minFingers = conf.minFingers || conf.fingers || 2; + // Tracking the events. + conf.onPointerDown = function(event) { + var fingers = conf.fingers; + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + // Record gesture start. + if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { + self.fingers = conf.minFingers; + self.scale = 1; + self.rotation = 0; + self.state = "start"; + var sids = ""; //- FIXME(mud): can generate duplicate IDs. + for (var key in conf.tracker) + sids += key; + self.identifier = parseInt(sids); + conf.listener(event, self); + } + }; + /// + conf.onPointerMove = function(event, state) { + var bbox = conf.bbox; + var points = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Update tracker coordinates. + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var pt = points[sid]; + // Check whether "pt" is used by another gesture. + if (!pt) + continue; + // Find the actual coordinates. + if (conf.position === "relative") { + pt.move.x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + pt.move.y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + pt.move.x = (touch.pageX - bbox.x1); + pt.move.y = (touch.pageY - bbox.y1); + } + } + /// + if (conf.fingers < conf.minFingers) + return; + /// + var touches = []; + var scale = 0; + var rotation = 0; + /// Calculate centroid of gesture. + var centroidx = 0; + var centroidy = 0; + var length = 0; + for (var sid in points) { + var touch = points[sid]; + if (touch.up) + continue; + centroidx += touch.move.x; + centroidy += touch.move.y; + length++; + } + centroidx /= length; + centroidy /= length; + /// + for (var sid in points) { + var touch = points[sid]; + if (touch.up) + continue; + var start = touch.start; + if (!start.distance) { + var dx = start.x - centroidx; + var dy = start.y - centroidy; + start.distance = Math.sqrt(dx * dx + dy * dy); + start.angle = Math.atan2(dx, dy) / RAD_DEG; + } + // Calculate scale. + var dx = touch.move.x - centroidx; + var dy = touch.move.y - centroidy; + var distance = Math.sqrt(dx * dx + dy * dy); + scale += distance / start.distance; + // Calculate rotation. + var angle = Math.atan2(dx, dy) / RAD_DEG; + var rotate = (start.angle - angle + 360) % 360 - 180; + touch.DEG2 = touch.DEG1; // Previous degree. + touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree. + if (typeof(touch.DEG2) !== "undefined") { + if (rotate > 0) { + touch.rotation += touch.DEG1 - touch.DEG2; + } else { + touch.rotation -= touch.DEG1 - touch.DEG2; + } + rotation += touch.rotation; + } + // Attach current points to self. + touches.push(touch.move); + } + /// + self.touches = touches; + self.fingers = conf.fingers; + self.scale = scale / conf.fingers; + self.rotation = rotation / conf.fingers; + self.state = "change"; + conf.listener(event, self); + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + var fingers = conf.fingers; + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + // Check whether fingers has dropped below minFingers. + if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { + self.fingers = conf.fingers; + self.state = "end"; + conf.listener(event, self); + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.gesture = root.gesture; + + return root; + +})(Event.proxy); +/* + "Pointer" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.pointerdown = + root.pointermove = + root.pointerup = function(conf) { + if (conf.target.isPointerEmitter) + return; + // Tracking the events. + var isDown = true; + conf.onPointerDown = function(event) { + isDown = false; + self.gesture = "pointerdown"; + conf.listener(event, self); + }; + conf.onPointerMove = function(event) { + self.gesture = "pointermove"; + conf.listener(event, self, isDown); + }; + conf.onPointerUp = function(event) { + isDown = true; + self.gesture = "pointerup"; + conf.listener(event, self, true); + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + Event.add(conf.target, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Return this object. + conf.target.isPointerEmitter = true; + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; + Event.Gesture._gestureHandlers.pointermove = root.pointermove; + Event.Gesture._gestureHandlers.pointerup = root.pointerup; + + return root; + +})(Event.proxy); +/* + "Device Motion" and "Shake" event proxy. + ---------------------------------------------------- + http://developer.android.com/reference/android/hardware/SensorEvent.html#values + ---------------------------------------------------- + Event.add(window, "shake", function(event, self) {}); + Event.add(window, "devicemotion", function(event, self) { + console.log(self.acceleration, self.accelerationIncludingGravity); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.shake = function(conf) { + // Externally accessible data. + var self = { + gesture: "devicemotion", + acceleration: {}, + accelerationIncludingGravity: {}, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener('devicemotion', onDeviceMotion, false); + } + }; + // Setting up local variables. + var threshold = 4; // Gravitational threshold. + var timeout = 1000; // Timeout between shake events. + var timeframe = 200; // Time between shakes. + var shakes = 3; // Minimum shakes to trigger event. + var lastShake = (new Date).getTime(); + var gravity = {x: 0, y: 0, z: 0}; + var delta = { + x: {count: 0, value: 0}, + y: {count: 0, value: 0}, + z: {count: 0, value: 0} + }; + // Tracking the events. + var onDeviceMotion = function(e) { + var alpha = 0.8; // Low pass filter. + var o = e.accelerationIncludingGravity; + gravity.x = alpha * gravity.x + (1 - alpha) * o.x; + gravity.y = alpha * gravity.y + (1 - alpha) * o.y; + gravity.z = alpha * gravity.z + (1 - alpha) * o.z; + self.accelerationIncludingGravity = gravity; + self.acceleration.x = o.x - gravity.x; + self.acceleration.y = o.y - gravity.y; + self.acceleration.z = o.z - gravity.z; + /// + if (conf.gesture === "devicemotion") { + conf.listener(e, self); + return; + } + var data = "xyz"; + var now = (new Date).getTime(); + for (var n = 0, length = data.length; n < length; n++) { + var letter = data[n]; + var ACCELERATION = self.acceleration[letter]; + var DELTA = delta[letter]; + var abs = Math.abs(ACCELERATION); + /// Check whether another shake event was recently registered. + if (now - lastShake < timeout) + continue; + /// Check whether delta surpasses threshold. + if (abs > threshold) { + var idx = now * ACCELERATION / abs; + var span = Math.abs(idx + DELTA.value); + // Check whether last delta was registered within timeframe. + if (DELTA.value && span < timeframe) { + DELTA.value = idx; + DELTA.count++; + // Check whether delta count has enough shakes. + if (DELTA.count === shakes) { + conf.listener(e, self); + // Reset tracking. + lastShake = now; + DELTA.value = 0; + DELTA.count = 0; + } + } else { + // Track first shake. + DELTA.value = idx; + DELTA.count = 1; + } + } + } + }; + // Attach events. + if (!window.addEventListener) + return; + window.addEventListener('devicemotion', onDeviceMotion, false); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.shake = root.shake; + + return root; + +})(Event.proxy); +/* + "Swipe" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: snap, threshold, maxFingers. + ---------------------------------------------------- + Event.add(window, "swipe", function(event, self) { + console.log(self.velocity, self.angle); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + var RAD_DEG = Math.PI / 180; + + root.swipe = function(conf) { + conf.snap = conf.snap || 90; // angle snap. + conf.threshold = conf.threshold || 1; // velocity threshold. + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var o = conf.tracker[sid]; + // Identifier defined outside of listener. + if (!o) + continue; + o.move.x = touch.pageX; + o.move.y = touch.pageY; + o.moveTime = (new Date).getTime(); + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + /// + var velocity1; + var velocity2 + var degree1; + var degree2; + /// Calculate centroid of gesture. + var start = {x: 0, y: 0}; + var endx = 0; + var endy = 0; + var length = 0; + /// + for (var sid in conf.tracker) { + var touch = conf.tracker[sid]; + var xdist = touch.move.x - touch.start.x; + var ydist = touch.move.y - touch.start.y; + + endx += touch.move.x; + endy += touch.move.y; + start.x += touch.start.x; + start.y += touch.start.y; + length++; + + + var distance = Math.sqrt(xdist * xdist + ydist * ydist); + var ms = touch.moveTime - touch.startTime; + var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; + var velocity2 = ms ? distance / ms : 0; + if (typeof(degree1) === "undefined") { + degree1 = degree2; + velocity1 = velocity2; + } else if (Math.abs(degree2 - degree1) <= 20) { + degree1 = (degree1 + degree2) / 2; + velocity1 = (velocity1 + velocity2) / 2; + } else { + return; + } + } + /// + if (velocity1 > conf.threshold) { + start.x /= length; + start.y /= length; + self.start = start; + self.x = endx / length; + self.y = endy / length; + self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360); + self.velocity = velocity1; + self.fingers = conf.gestureFingers; + self.state = "swipe"; + conf.listener(event, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.swipe = root.swipe; + + return root; + +})(Event.proxy); +/* + "Tap" and "Longpress" event proxy. + ---------------------------------------------------- + CONFIGURE: delay (longpress), timeout (tap). + ---------------------------------------------------- + Event.add(window, "tap", function(event, self) { + console.log(self.fingers); + }); + ---------------------------------------------------- + multi-finger tap // touch an target for <= 250ms. + multi-finger longpress // touch an target for >= 500ms + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.tap = + root.longpress = function(conf) { + conf.delay = conf.delay || 500; + conf.timeout = conf.timeout || 250; + // Setting up local variables. + var timestamp, timeout; + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + timestamp = (new Date).getTime(); + // Initialize event listeners. + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Make sure this is a "longpress" event. + if (conf.gesture !== "longpress") + return; + timeout = setTimeout(function() { + if (event.cancelBubble && ++event.bubble > 1) + return; + // Make sure no fingers have been changed. + var fingers = 0; + for (var key in conf.tracker) { + if (conf.tracker[key].end === true) + return; + if (conf.cancel) + return; + fingers++; + } + // Send callback. + self.state = "start"; + self.fingers = fingers; + conf.listener(event, self); + }, conf.delay); + } + }; + conf.onPointerMove = function(event) { + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + if (!pt) + continue; + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var x = (touch.pageX - bbox.x1); + var y = (touch.pageY - bbox.y1); + } + if (!(x > 0 && x < bbox.width && // Within target coordinates.. + y > 0 && y < bbox.height && + Math.abs(x - pt.start.x) <= 25 && // Within drift deviance. + Math.abs(y - pt.start.y) <= 25)) { + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + conf.cancel = true; + return; + } + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + clearTimeout(timeout); + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (event.cancelBubble && ++event.bubble > 1) + return; + // Callback release on longpress. + if (conf.gesture === "longpress") { + if (self.state === "start") { + self.state = "end"; + conf.listener(event, self); + } + return; + } + // Cancel event due to movement. + if (conf.cancel) + return; + // Ensure delay is within margins. + if ((new Date).getTime() - timestamp > conf.timeout) + return; + // Send callback. + self.state = "tap"; + self.fingers = conf.gestureFingers; + conf.listener(event, self); + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.tap = root.tap; + Event.Gesture._gestureHandlers.longpress = root.longpress; + + return root; + +})(Event.proxy); +/* + "Mouse Wheel" event proxy. + ---------------------------------------------------- + Event.add(window, "wheel", function(event, self) { + console.log(self.state, self.wheelDelta); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.wheel = function(conf) { + // Configure event listener. + var interval; + var timeout = conf.timeout || 150; + var count = 0; + // Externally accessible data. + var self = { + gesture: "wheel", + state: "start", + wheelDelta: 0, + target: conf.target, + listener: conf.listener, + remove: function() { + conf.target[remove](type, onMouseWheel, false); + } + }; + // Tracking the events. + var onMouseWheel = function(event) { + event = event || window.event; + self.state = count++ ? "change" : "start"; + self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; + conf.listener(event, self); + clearTimeout(interval); + interval = setTimeout(function() { + count = 0; + self.state = "end"; + self.wheelDelta = 0; + conf.listener(event, self); + }, timeout); + }; + // Attach events. + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + var type = Event.supports("mousewheel") ? "mousewheel" : "DOMMouseScroll"; + conf.target[add](type, onMouseWheel, false); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.wheel = root.wheel; + + return root; + +})(Event.proxy); + + /** * Wrapper around `console.log` (when available) * @param {Any} values Values to log @@ -2097,7 +4003,46 @@ fabric.Collection = { return new fabric.Point(rx, ry).addEquals(origin); } + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + function transformPoint(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[1] * p.y, + t[2] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[1] * p.y + t[4], + t[2] * p.x + t[3] * p.y + t[5] + ); + } + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + function invertTransform(t) { + var r = t.slice(), + a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; + var o = transformPoint({x: t[4], y: t[5]}, r); + r[4] = -o.x; + r[5] = -o.y; + return r + } + /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static @@ -2535,6 +4480,8 @@ fabric.Collection = { fabric.util.degreesToRadians = degreesToRadians; fabric.util.radiansToDegrees = radiansToDegrees; fabric.util.rotatePoint = rotatePoint; + fabric.util.transformPoint = transformPoint; + fabric.util.invertTransform = invertTransform; fabric.util.toFixed = toFixed; fabric.util.getRandomInt = getRandomInt; fabric.util.falseFunction = falseFunction; @@ -6711,6 +8658,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ allowTouchScrolling: false, + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: [1, 0, 0, 1, 0, 0], + /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -6999,6 +8953,73 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this; }, + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + + /** + * Returns point at center of viewport + * @return {fabric.Point} the top left corner of the viewport + */ + getViewportCenter: function () { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ), + x = this.viewportTransform[4], + y = this.viewportTransform[5]; + + return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + // TODO: just change the scale, preserve other transformations + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Centers viewport of this canvas instance on given point + * @param {Numer} x value for center of viewport + * @param {Numer} y value for center of viewport + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportCenter: function (x, y) { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ); + this.viewportTransform[4] = x - wh.x/2; + this.viewportTransform[5] = y - wh.y/2; + this.renderAll(); + return this; + }, + + /** + * Centers viewport of this canvas instance + * @return {fabric.Canvas} instance + * @chainable true + */ + centerViewport: function () { + return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + }, + /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} @@ -7050,8 +9071,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); - obj.setCoords(); obj.canvas = this; + obj.setCoords(); + if (obj._objects) { + for (var i = 0, len = obj._objects.length; i < len; i++) { + obj._objects[i].canvas = this; + obj._objects[i].setCoords(); + } + } this.fire('object:added', { target: obj }); obj.fire('added'); }, @@ -7959,6 +9986,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private */ _getSVGPathData: function() { + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + for (var i = 0, len = this._points.length; i < len; i++) { + this._points[i] = fabric.util.transformPoint(this._points[i], ivt); + } this.box = this.getPathBoundingBox(this._points); return this.convertPointsToSVGPath( this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); @@ -8168,6 +10199,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric circles.push(circle); } var group = new fabric.Group(circles); + group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -8314,6 +10346,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(rects); + group.canvas = this.canvas; + this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -8349,9 +10383,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; ctx.save(); + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; + var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); + point.x = tpoint.x; + point.y = tpoint.y; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } @@ -8367,6 +10405,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; + var vpt = this.canvas.viewportTransform; for (var i = 0; i < this.density; i++) { @@ -8382,8 +10421,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric else { width = this.dotWidth; } - - var point = { x: x, y: y, width: width }; + + var point = new fabric.Point(x, y); + point = fabric.util.transformPoint(point, vpt); + point.width = width if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; @@ -8705,7 +10746,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target) { - var pointer = this.getPointer(e), + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html @@ -8719,7 +10760,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), x = pointer.x, - y = pointer.y; + y = pointer.y, + lt; var isObjectInGroup = ( activeGroup && @@ -8728,8 +10770,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab ); if (isObjectInGroup) { - x -= activeGroup.left; - y -= activeGroup.top; + lt = new fabric.Point(activeGroup.left, activeGroup.top); + lt = fabric.util.transformPoint(lt, this.viewportTransform, true); + x -= lt.x; + y -= lt.y; } return { x: x, y: y }; }, @@ -8838,7 +10882,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var action = 'drag', corner, - pointer = getPointer(e, target.canvas.upperCanvasEl); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -8954,6 +11001,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); var group = new fabric.Group( isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); + group.canvas = this; this.setActiveGroup(group); this._activeObject = null; @@ -9233,6 +11281,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else if (group.length > 1) { group = new fabric.Group(group.reverse()); + group.canvas = this; this.setActiveGroup(group); group.saveCoords(); this.fire('selection:created', { target: group }); @@ -9249,7 +11298,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.skipTargetFind) return; var target, - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && @@ -9289,7 +11338,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } } for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); if (!isTransparent) { target = possibleTargets[j]; @@ -9306,8 +11355,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e * @return {Object} object with "x" and "y" number values */ - getPointer: function (e) { - var pointer = getPointer(e, this.upperCanvasEl); + getPointer: function (e, ignoreZoom, upperCanvasEl) { + if (!upperCanvasEl) { + upperCanvasEl = this.upperCanvasEl; + } + var pointer = getPointer(e, upperCanvasEl); + if (!ignoreZoom) { + pointer = fabric.util.transformPoint( + pointer, + fabric.util.invertTransform(this.viewportTransform) + ); + } + return { x: pointer.x - this._offset.left, y: pointer.y - this._offset.top @@ -9753,7 +11812,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } } else { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); } render = this._shouldRender(target, pointer); @@ -9801,7 +11860,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e)); + this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); this.fire('mouse:down', { e: e }); }, @@ -9827,7 +11886,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this._currentTransform) return; var target = this.findTarget(e), - pointer = this.getPointer(e), + pointer = this.getPointer(e, true), corner, render; @@ -9928,8 +11987,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.isDrawingMode) { if (this._isCurrentlyDrawing) { - pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); + this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; this.fire('mouse:move', { e: e }); @@ -9940,10 +11998,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // We initially clicked in an empty area, so we draw a box for multiple selection. if (groupSelector) { - pointer = getPointer(e, this.upperCanvasEl); + pointer = this.getPointer(e, true); - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } else if (!this._currentTransform) { @@ -9968,7 +12026,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else { // object is being transformed (scaled/rotated/moved/etc.) - pointer = getPointer(e, this.upperCanvasEl); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); var x = pointer.x, y = pointer.y, @@ -10523,6 +12584,83 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }); +(function() { + + var degreesToRadians = fabric.util.degreesToRadians, + radiansToDegrees = fabric.util.radiansToDegrees; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports + * 2 finger gestures. + * + * @param e Event object by Event.js + * @param self Event proxy object by Event.js + */ + __onTransformGesture: function(e, self) { + + if (this.isDrawingMode || e.touches.length !== 2 || 'gesture' !== self.gesture) { + return; + } + + var target = this.findTarget(e); + if ('undefined' !== typeof target) { + this.onBeforeScaleRotate(target); + this._rotateObjectByAngle(self.rotation); + this._scaleObjectBy(self.scale); + } + + this.fire('touch:gesture', {target: target, e: e, self: self}); + }, + + /** + * Scales an object by a factor + * @param s {Number} The scale factor to apply to the current scale level + * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. + * When not provided, an object is scaled by both dimensions equally + */ + _scaleObjectBy: function(s, by) { + var t = this._currentTransform, + target = t.target; + + var lockScalingX = target.get('lockScalingX'), + lockScalingY = target.get('lockScalingY'); + + if (lockScalingX && lockScalingY) return; + + target._scaling = true; + + if (!by) { + if (!lockScalingX) { + target.set('scaleX', t.scaleX * s); + } + if (!lockScalingY) { + target.set('scaleY', t.scaleY * s); + } + } + else if (by === 'x' && !target.get('lockUniScaling')) { + lockScalingX || target.set('scaleX', t.scaleX * s); + } + else if (by === 'y' && !target.get('lockUniScaling')) { + lockScalingY || target.set('scaleY', t.scaleY * s); + } + }, + + /** + * Rotates object by an angle + * @param curAngle {Number} the angle of rotation in degrees + */ + _rotateObjectByAngle: function(curAngle) { + var t = this._currentTransform; + + if (t.target.get('lockRotation')) return; + t.target.angle = radiansToDegrees(degreesToRadians(curAngle) + t.theta); + } + }); +})(); + + (function(global) { "use strict"; @@ -11255,6 +13393,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node */ transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } ctx.globalAlpha = this.opacity; var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); @@ -11537,6 +13678,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -11571,8 +13722,23 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -12558,10 +14724,21 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, - theta = degreesToRadians(this.angle); + theta = degreesToRadians(this.angle), + vpt; + if (this.canvas) { + vpt = this.canvas.viewportTransform; + } + if (!vpt) { // TODO + vpt = [1, 0, 0, 1, 0, 0]; + }; - this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; - this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; + var f = function (p) { + return fabric.util.transformPoint(p, vpt); + } + + this.currentWidth = (this.width + strokeWidth) * this.scaleX; + this.currentHeight = (this.height + strokeWidth) * this.scaleY; // If width is negative, make postive. Fixes path selection issue if (this.currentWidth < 0) { @@ -12578,45 +14755,35 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), - cosTh = Math.cos(theta); + cosTh = Math.cos(theta), + coords = this.getCenterPoint(), + wh = new fabric.Point(this.currentWidth, this.currentHeight); + var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); + var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); + var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); + var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); + var tl = f(_tl); + var tr = f(_tr); + var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); + var bl = f(_bl); + var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); + var mt = f(_mt); + var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); + var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); + var mtr = f(new fabric.Point(_mt.x, _mt.y)); - var coords = this.getCenterPoint(); - var tl = { - x: coords.x - offsetX, - y: coords.y - offsetY - }; - var tr = { - x: tl.x + (this.currentWidth * cosTh), - y: tl.y + (this.currentWidth * sinTh) - }; - var br = { - x: tr.x - (this.currentHeight * sinTh), - y: tr.y + (this.currentHeight * cosTh) - }; - var bl = { - x: tl.x - (this.currentHeight * sinTh), - y: tl.y + (this.currentHeight * cosTh) - }; - var ml = { - x: tl.x - (this.currentHeight/2 * sinTh), - y: tl.y + (this.currentHeight/2 * cosTh) - }; - var mt = { - x: tl.x + (this.currentWidth/2 * cosTh), - y: tl.y + (this.currentWidth/2 * sinTh) - }; - var mr = { - x: tr.x - (this.currentHeight/2 * sinTh), - y: tr.y + (this.currentHeight/2 * cosTh) - }; - var mb = { - x: bl.x + (this.currentWidth/2 * cosTh), - y: bl.y + (this.currentWidth/2 * sinTh) - }; - var mtr = { - x: mt.x, - y: mt.y - }; + // padding + var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), + padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); // debugging @@ -12714,9 +14881,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _findTargetCorner: function(e, offset) { if (!this.hasControls || !this.active) return false; - var pointer = getPointer(e, this.canvas.upperCanvasEl), - ex = pointer.x - offset.left, - ey = pointer.y - offset.top, + var pointer = this.canvas.getPointer(e, true), + ex = pointer.x, + ey = pointer.y, xPoints, lines; @@ -12965,25 +15132,32 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot scaleY = 1 / this._constrainScale(this.scaleY); ctx.lineWidth = 1 / this.borderScaleFactor; - - ctx.scale(scaleX, scaleY); - - var w = this.getWidth(), - h = this.getHeight(); + + var vpt = this.canvas.viewportTransform, + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), + sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), + w = wh.x, + h = wh.y, + sx= sxy.x, + sy= sxy.y; + if (this.group) { + w = w * this.group.scaleX; + h = h * this.group.scaleY; + } ctx.strokeRect( - ~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) - 0.5, // offset needed to make lines look sharper - ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) - 0.5, - ~~(w + padding2 + strokeWidth * this.scaleX) + 1, // double offset needed to make lines look sharper - ~~(h + padding2 + strokeWidth * this.scaleY) + 1 + ~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper + ~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5, + ~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper + ~~(h + padding2 + strokeWidth * sy) + 1 ); if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { var rotateHeight = ( this.flipY - ? h + (strokeWidth * this.scaleY) + (padding * 2) - : -h - (strokeWidth * this.scaleY) - (padding * 2) + ? h + (strokeWidth * sx) + (padding * 2) + : -h - (strokeWidth * sy) - (padding * 2) ) / 2; ctx.beginPath(); @@ -12999,7 +15173,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * Draws corners of an object's bounding box. - * Requires public properties: width, height, scaleX, scaleY + * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg @@ -13011,99 +15185,95 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - left = -(this.width / 2), - top = -(this.height / 2), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + width = wh.x, + height = wh.y, + left = -(width / 2), + top = -(height / 2), _left, _top, - sizeX = size / this.scaleX, - sizeY = size / this.scaleY, - paddingX = this.padding / this.scaleX, - paddingY = this.padding / this.scaleY, - scaleOffsetY = size2 / this.scaleY, - scaleOffsetX = size2 / this.scaleX, - scaleOffsetSizeX = (size2 - size) / this.scaleX, - scaleOffsetSizeY = (size2 - size) / this.scaleY, - height = this.height, - width = this.width, + padding = this.padding, + scaleOffset = size2, + scaleOffsetSize = size2 - size, methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', transparent = this.transparentCorners, isVML = typeof G_vmlCanvasManager !== 'undefined'; ctx.save(); - ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); + ctx.lineWidth = 1; ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = ctx.fillStyle = this.cornerColor; // top-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // top-right - _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width - scaleOffset + strokeWidth2 + padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); if (!this.get('lockUniScaling')) { // middle-top - _left = left + width/2 - scaleOffsetX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width/2 - scaleOffset; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-bottom - _left = left + width/2 - scaleOffsetX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width/2 - scaleOffset; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } // middle-top-rotate if (this.hasRotatingPoint) { - _left = left + width/2 - scaleOffsetX; + _left = left + width/2 - scaleOffset; _top = this.flipY ? - (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + (top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding) + : (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding); - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } ctx.restore(); @@ -15173,6 +17343,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -15206,8 +17386,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -15541,6 +17727,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -15554,8 +17750,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -15718,7 +17920,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke; + invoke = fabric.util.array.invoke, + degreesToRadians = fabric.util.degreesToRadians; if (fabric.Group) { return; @@ -15764,6 +17967,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; + this._objects[i].setCoords(); } this.originalState = { }; @@ -15927,9 +18131,12 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!this.visible) return; ctx.save(); - this.transform(ctx); + var v = this.canvas.viewportTransform; - var groupScaleFactor = Math.max(this.scaleX, this.scaleY); + var sxy = fabric.util.transformPoint( + new fabric.Point(this.scaleX, this.scaleY), + v, true), + groupScaleFactor = Math.max(sxy.x, sxy.y); this.clipTo && fabric.util.clipContext(this, ctx); @@ -15943,22 +18150,21 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot // do not render if object is not visible if (!object.visible) continue; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; - object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; } this.clipTo && ctx.restore(); - if (!noTransform && this.active) { + if (this.active && !noTransform) { + var center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } ctx.restore(); - this.setCoords(); }, /** @@ -16106,7 +18312,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _calcBounds: function() { var aX = [], aY = [], - minX, minY, maxX, maxY, o, width, height, + minX, minY, maxX, maxY, o, width, height, minXY, maxXY, i = 0, len = this._objects.length; @@ -16118,20 +18324,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot aY.push(o.oCoords[prop].y); } } + + var ivt = fabric.util.invertTransform(canvas.viewportTransform); - minX = min(aX); - maxX = max(aX); - minY = min(aY); - maxY = max(aY); + minXY = new fabric.Point(min(aX), min(aY)); + maxXY = new fabric.Point(max(aX), max(aY)); - width = (maxX - minX) || 0; - height = (maxY - minY) || 0; + minXY = fabric.util.transformPoint(minXY, ivt); + maxXY = fabric.util.transformPoint(maxXY, ivt); - this.width = width; - this.height = height; - - this.left = (minX + width / 2) || 0; - this.top = (minY + height / 2) || 0; + this.width = (maxXY.x - minXY.x) || 0; + this.height = (maxXY.y - minXY.y) || 0; + + this.left = (minXY.x + maxXY.x) / 2 || 0; + this.top = (minXY.y + maxXY.y) / 2 || 0; }, /* _TO_SVG_START_ */ @@ -16315,8 +18521,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } var isInPathGroup = this.group && this.group.type !== 'group'; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); @@ -16338,8 +18553,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -18602,8 +20832,22 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/dist/all.require.js b/dist/all.require.js index dcc1e18c..d832fe79 100644 --- a/dist/all.require.js +++ b/dist/all.require.js @@ -1,4 +1,4 @@ -/* build: `node build.js modules=ALL exclude=gestures` */ +/* build: `node build.js modules=ALL` */ /*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: "1.3.7" }; @@ -1749,6 +1749,1912 @@ if (!JSON) { } }()); +/* + ---------------------------------------------------- + Event.js : 1.1.1 : 2012/11/19 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- + 1 : click, dblclick, dbltap + 1+ : tap, longpress, drag, swipe + 2+ : pinch, rotate + : mousewheel, devicemotion, shake + ---------------------------------------------------- + TODO + ---------------------------------------------------- + * switch configuration to 4th argument on addEventListener + * bbox calculation for elements scaled with transform. + ---------------------------------------------------- + NOTES + ---------------------------------------------------- + * When using other libraries that may have built in "Event" namespace, + i.e. Typescript, you can use "eventjs" instead of "Event" for all example calls. + ---------------------------------------------------- + REQUIREMENTS: querySelector, querySelectorAll + ---------------------------------------------------- + * There are two ways to add/remove events with this library. + ---------------------------------------------------- + // Retains "this" attribute as target, and overrides native addEventListener. + target.addEventListener(type, listener, useCapture); + target.removeEventListener(type, listener, useCapture); + + // Attempts to perform as fast as possible. + Event.add(type, listener, configure); + Event.remove(type, listener, configure); + + * You can turn prototyping on/off for individual features. + ---------------------------------------------------- + Event.modifyEventListener = true; // add custom *EventListener commands to HTMLElements. + Event.modifySelectors = true; // add bulk *EventListener commands on NodeLists from querySelectorAll and others. + + * Example of setting up a single listener with a custom configuration. + ---------------------------------------------------- + // optional configuration. + var configure = { + fingers: 2, // listen for specifically two fingers. + snap: 90 // snap to 90 degree intervals. + }; + // adding with addEventListener() + target.addEventListener("swipe", function(event) { + // additional variables can be found on the event object. + console.log(event.velocity, event.angle, event.fingers); + }, configure); + + // adding with Event.add() + Event.add("swipe", function(event, self) { + // additional variables can be found on the self object. + console.log(self.velocity, self.angle, self.fingers); + }, configure); + + * Multiple listeners glued together. + ---------------------------------------------------- + // adding with addEventListener() + target.addEventListener("click swipe", function(event) { }); + + // adding with Event.add() + Event.add(target, "click swipe", function(event, self) { }); + + * Use query selectors to create an event (querySelectorAll) + ---------------------------------------------------- + // adding events to NodeList from querySelectorAll() + document.querySelectorAll("#element a.link").addEventListener("click", callback); + + // adding with Event.add() + Event.add("#element a.link", "click", callback); + + * Listen for selector to become available (querySelector) + ---------------------------------------------------- + Event.add("body", "ready", callback); + // or... + Event.add({ + target: "body", + type: "ready", + timeout: 10000, // set a timeout to stop checking. + interval: 30, // set how often to check for element. + listener: callback + }); + + * Multiple listeners bound to one callback w/ single configuration. + ---------------------------------------------------- + var bindings = Event.add({ + target: target, + type: "click swipe", + snap: 90, // snap to 90 degree intervals. + minFingers: 2, // minimum required fingers to start event. + maxFingers: 4, // maximum fingers in one event. + listener: function(event, self) { + console.log(self.gesture); // will be click or swipe. + console.log(self.x); + console.log(self.y); + console.log(self.identifier); + console.log(self.start); + console.log(self.fingers); // somewhere between "2" and "4". + self.pause(); // disable event. + self.resume(); // enable event. + self.remove(); // remove event. + } + }); + + * Multiple listeners bound to multiple callbacks w/ single configuration. + ---------------------------------------------------- + var bindings = Event.add({ + target: target, + minFingers: 1, + maxFingers: 12, + listeners: { + click: function(event, self) { + self.remove(); // removes this click listener. + }, + swipe: function(event, self) { + binding.remove(); // removes both the click + swipe listeners. + } + } + }); + + * Multiple listeners bound to multiple callbacks w/ multiple configurations. + ---------------------------------------------------- + var binding = Event.add({ + target: target, + listeners: { + longpress: { + fingers: 1, + wait: 500, // milliseconds + listener: function(event, self) { + console.log(self.fingers); // "1" finger. + } + }, + drag: { + fingers: 3, + position: "relative", // "relative", "absolute", "difference", "move" + listener: function(event, self) { + console.log(self.fingers); // "3" fingers. + console.log(self.x); // coordinate is relative to edge of target. + } + } + } + }); + + * Capturing an event and manually forwarding it to a proxy (tiered events). + ---------------------------------------------------- + Event.add(target, "down", function(event, self) { + var x = event.pageX; // local variables that wont change. + var y = event.pageY; + Event.proxy.drag({ + event: event, + target: target, + listener: function(event, self) { + console.log(x - event.pageX); // measure movement. + console.log(y - event.pageY); + } + }); + }); + ---------------------------------------------------- + + * Event proxies. + * type, fingers, state, start, x, y, position, bbox + * rotation, scale, velocity, angle, delay, timeout + ---------------------------------------------------- + // "Click" :: fingers, minFingers, maxFingers. + Event.add(window, "click", function(event, self) { + console.log(self.gesture, self.x, self.y); + }); + // "Double-Click" :: fingers, minFingers, maxFingers. + Event.add(window, "dblclick", function(event, self) { + console.log(self.gesture, self.x, self.y); + }); + // "Drag" :: fingers, maxFingers, position + Event.add(window, "drag", function(event, self) { + console.log(self.gesture, self.fingers, self.state, self.start, self.x, self.y, self.bbox); + }); + // "Gesture" :: fingers, minFingers, maxFingers. + Event.add(window, "gesture", function(event, self) { + console.log(self.gesture, self.fingers, self.state, self.rotation, self.scale); + }); + // "Swipe" :: fingers, minFingers, maxFingers, snap, threshold. + Event.add(window, "swipe", function(event, self) { + console.log(self.gesture, self.fingers, self.velocity, self.angle, self.start, self.x, self.y); + }); + // "Tap" :: fingers, minFingers, maxFingers, timeout. + Event.add(window, "tap", function(event, self) { + console.log(self.gesture, self.fingers); + }); + // "Longpress" :: fingers, minFingers, maxFingers, delay. + Event.add(window, "longpress", function(event, self) { + console.log(self.gesture, self.fingers); + }); + // + Event.add(window, "shake", function(event, self) { + console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); + }); + // + Event.add(window, "devicemotion", function(event, self) { + console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); + }); + // + Event.add(window, "wheel", function(event, self) { + console.log(self.gesture, self.state, self.wheelDelta); + }); + + * Stop, prevent and cancel. + ---------------------------------------------------- + Event.stop(event); // stop bubble. + Event.prevent(event); // prevent default. + Event.cancel(event); // stop and prevent. + + * Track for proper command/control-key for Mac/PC. + ---------------------------------------------------- + Event.add(window, "keyup keydown", Event.proxy.metaTracker); + console.log(Event.proxy.metaKey); + + * Test for event features, in this example Drag & Drop file support. + ---------------------------------------------------- + console.log(Event.supports('dragstart') && Event.supports('drop') && !!window.FileReader); + + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(eventjs) === "undefined") + var eventjs = Event; + +Event = (function(root) { + "use strict"; + +// Add custom *EventListener commands to HTMLElements. + root.modifyEventListener = false; + +// Add bulk *EventListener commands on NodeLists from querySelectorAll and others. + root.modifySelectors = false; + +// Event maintenance. + root.add = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "add"); + }; + + root.remove = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "remove"); + }; + + root.stop = function(event) { + if (event.stopPropagation) + event.stopPropagation(); + event.cancelBubble = true; // <= IE8 + event.bubble = 0; + }; + + root.prevent = function(event) { + if (event.preventDefault) + event.preventDefault(); + event.returnValue = false; // <= IE8 + }; + + root.cancel = function(event) { + root.stop(event); + root.prevent(event); + }; + +// Check whether event is natively supported (via @kangax) + root.supports = function(target, type) { + if (typeof(target) === "string") { + type = target; + target = window; + } + type = "on" + type; + if (type in target) + return true; + if (!target.setAttribute) + target = document.createElement("div"); + if (target.setAttribute && target.removeAttribute) { + target.setAttribute(type, ""); + var isSupported = typeof target[type] === "function"; + if (typeof target[type] !== "undefined") + target[type] = null; + target.removeAttribute(type); + return isSupported; + } + }; + + var clone = function(obj) { + if (!obj || typeof (obj) !== 'object') + return obj; + var temp = new obj.constructor(); + for (var key in obj) { + if (!obj[key] || typeof (obj[key]) !== 'object') { + temp[key] = obj[key]; + } else { // clone sub-object + temp[key] = clone(obj[key]); + } + } + return temp; + }; + +/// Handle custom *EventListener commands. + var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { + configure = configure || {}; + // Check for element to load on interval (before onload). + if (typeof(target) === "string" && type === "ready") { + var time = (new Date()).getTime(); + var timeout = configure.timeout; + var ms = configure.interval || 1000 / 60; + var interval = window.setInterval(function() { + if ((new Date()).getTime() - time > timeout) { + window.clearInterval(interval); + } + if (document.querySelector(target)) { + window.clearInterval(interval); + listener(); + } + }, ms); + return; + } + // Get DOM element from Query Selector. + if (typeof(target) === "string") { + target = document.querySelectorAll(target); + if (target.length === 0) + return createError("Missing target on listener!"); // No results. + if (target.length === 1) { // Single target. + target = target[0]; + } + } + /// Handle multiple targets. + var event; + var events = {}; + if (target.length > 0) { + for (var n0 = 0, length0 = target.length; n0 < length0; n0++) { + event = eventManager(target[n0], type, listener, clone(configure), trigger); + if (event) + events[n0] = event; + } + return createBatchCommands(events); + } + // Check for multiple events in one string. + if (type.indexOf && type.indexOf(" ") !== -1) + type = type.split(" "); + if (type.indexOf && type.indexOf(",") !== -1) + type = type.split(","); + // Attach or remove multiple events associated with a target. + if (typeof(type) !== "string") { // Has multiple events. + if (typeof(type.length) === "number") { // Handle multiple listeners glued together. + for (var n1 = 0, length1 = type.length; n1 < length1; n1++) { // Array [type] + event = eventManager(target, type[n1], listener, clone(configure), trigger); + if (event) + events[type[n1]] = event; + } + } else { // Handle multiple listeners. + for (var key in type) { // Object {type} + if (typeof(type[key]) === "function") { // without configuration. + event = eventManager(target, key, type[key], clone(configure), trigger); + } else { // with configuration. + event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); + } + if (event) + events[key] = event; + } + } + return createBatchCommands(events); + } + // Ensure listener is a function. + if (typeof(listener) !== "function") + return createError("Listener is not a function!"); + // Generate a unique wrapper identifier. + var useCapture = configure.useCapture || false; + var id = normalize(type) + getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); + // Handle the event. + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event. + if (trigger === "remove") { // Remove event listener. + if (!wrappers[id]) + return; // Already removed. + wrappers[id].remove(); + delete wrappers[id]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[id]) + return wrappers[id]; // Already attached. + // Retains "this" orientation. + if (configure.useCall && !root.modifyEventListener) { + var tmp = listener; + listener = function(event, self) { + for (var key in self) + event[key] = self[key]; + return tmp.call(target, event); + }; + } + // Create listener proxy. + configure.gesture = type; + configure.target = target; + configure.listener = listener; + configure.fromOverwrite = fromOverwrite; + // Record wrapper. + wrappers[id] = root.proxy[type](configure); + } + } else { // Fire native event. + type = normalize(type); + if (trigger === "remove") { // Remove event listener. + if (!wrappers[id]) + return; // Already removed. + target[remove](type, listener, useCapture); + delete wrappers[id]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[id]) + return wrappers[id]; // Already attached. + target[add](type, listener, useCapture); + // Record wrapper. + wrappers[id] = { + type: type, + target: target, + listener: listener, + remove: function() { + root.remove(target, type, listener, configure); + } + }; + } + } + return wrappers[id]; + }; + +/// Perform batch actions on multiple events. + var createBatchCommands = function(events) { + return { + remove: function() { // Remove multiple events. + for (var key in events) { + events[key].remove(); + } + }, + add: function() { // Add multiple events. + for (var key in events) { + events[key].add(); + } + } + }; + }; + +/// Display error message in console. + var createError = function(message) { + if (typeof(console) === "undefined") + return; + if (typeof(console.error) === "undefined") + return; + console.error(message); + }; + +/// Handle naming discrepancies between platforms. + var normalize = (function() { + var translate = {}; + return function(type) { + if (!root.pointerType) { + if (window.navigator.msPointerEnabled) { + root.pointerType = "mspointer"; + translate = { + "mousedown": "MSPointerDown", + "mousemove": "MSPointerMove", + "mouseup": "MSPointerUp" + }; + } else if (root.supports("touchstart")) { + root.pointerType = "touch"; + translate = { + "mousedown": "touchstart", + "mouseup": "touchend", + "mousemove": "touchmove" + }; + } else { + root.pointerType = "mouse"; + } + } + if (translate[type]) + type = translate[type]; + if (!document.addEventListener) { // IE + return "on" + type; + } else { + return type; + } + }; + })(); + +/// Event wrappers to keep track of all events placed in the window. + var wrappers = {}; + var counter = 0; + var getID = function(object) { + if (object === window) + return "#window"; + if (object === document) + return "#document"; + if (!object) + return createError("Missing target on listener!"); + if (!object.uniqueID) + object.uniqueID = "id" + counter++; + return object.uniqueID; + }; + +/// Detect platforms native *EventListener command. + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + + /* + Pointer.js + ------------------------ + Modified from; https://github.com/borismus/pointer.js + */ + + root.createPointerEvent = function(event, self, preventRecord) { + var eventName = self.gesture; + var target = self.target; + var pts = event.changedTouches || root.proxy.getCoords(event); + if (pts.length) { + var pt = pts[0]; + self.pointers = preventRecord ? [] : pts; + self.pageX = pt.pageX; + self.pageY = pt.pageY; + self.x = self.pageX; + self.y = self.pageY; + } + /// + var newEvent = document.createEvent("Event"); + newEvent.initEvent(eventName, true, true); + newEvent.originalEvent = event; + for (var k in self) { + if (k === "target") + continue; + newEvent[k] = self[k]; + } + target.dispatchEvent(newEvent); + }; + +/// Allows *EventListener to use custom event proxies. + if (root.modifyEventListener && window.HTMLElement) + (function() { + var augmentEventListener = function(proto) { + var recall = function(trigger) { // overwrite native *EventListener's + var handle = trigger + "EventListener"; + var handler = proto[handle]; + proto[handle] = function(type, listener, useCapture) { + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. + var configure = useCapture; + if (typeof(useCapture) === "object") { + configure.useCall = true; + } else { // convert to configuration object. + configure = { + useCall: true, + useCapture: useCapture + }; + } + eventManager(this, type, listener, configure, trigger, true); + handler.call(this, type, listener, useCapture); + } else { // use native function. + handler.call(this, normalize(type), listener, useCapture); + } + }; + }; + recall("add"); + recall("remove"); + }; + // NOTE: overwriting HTMLElement doesn't do anything in Firefox. + if (navigator.userAgent.match(/Firefox/)) { + // TODO: fix Firefox for the general case. + augmentEventListener(HTMLDivElement.prototype); + augmentEventListener(HTMLCanvasElement.prototype); + } else { + augmentEventListener(HTMLElement.prototype); + } + augmentEventListener(document); + augmentEventListener(window); + })(); + +/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk. + if (root.modifySelectors) + (function() { + var proto = NodeList.prototype; + proto.removeEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].removeEventListener(type, listener, useCapture); + } + }; + proto.addEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].addEventListener(type, listener, useCapture); + } + }; + })(); + + return root; + +})(Event); +/* + ---------------------------------------------------- + Event.proxy : 0.4.2 : 2012/07/29 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- + Pointer Gestures + ---------------------------------------------------- + 1 : click, dblclick, dbltap + 1+ : tap, taphold, drag, swipe + 2+ : pinch, rotate + ---------------------------------------------------- + Gyroscope Gestures + ---------------------------------------------------- + * shake + ---------------------------------------------------- + Fixes issues with + ---------------------------------------------------- + * mousewheel-Firefox uses DOMMouseScroll and does not return wheelDelta. + * devicemotion-Fixes issue where event.acceleration is not returned. + ---------------------------------------------------- + Ideas for the future + ---------------------------------------------------- + * Keyboard, GamePad, and other input abstractions. + * Event batching - i.e. for every x fingers down a new gesture is created. + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + /* + Create a new pointer gesture instance. + */ + + root.pointerSetup = function(conf, self) { + /// Configure. + conf.doc = conf.target.ownerDocument || conf.target; // Associated document. + conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers. + conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers. + conf.position = conf.position || "relative"; // Determines what coordinate system points are returned. + delete conf.fingers; //- + /// Convenience data. + self = self || {}; + self.gesture = conf.gesture; + self.target = conf.target; + self.pointerType = Event.pointerType; + /// + if (Event.modifyEventListener && conf.fromOverwrite) + conf.listener = Event.createPointerEvent; + /// Convenience commands. + var fingers = 0; + var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; + self.listener = conf.listener; + self.proxy = function(listener) { + self.defaultListener = conf.listener; + conf.listener = listener; + listener(conf.event, self); + }; + self.remove = function() { + if (conf.onPointerDown) + Event.remove(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) + Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) + Event.remove(conf.doc, type + "up", conf.onPointerUp); + }; + self.resume = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) + Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.move)) + Event.add(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = fingers; + }; + self.pause = function(opt) { + fingers = conf.fingers; + if (conf.onPointerMove && (!opt || opt.move)) + Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) + Event.remove(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = 0; + }; + /// + return self; + }; + + /* + Begin proxied pointer command. + */ + + root.pointerStart = function(event, self, conf) { + var addTouchStart = function(touch, sid) { + var bbox = conf.bbox; + var pt = track[sid] = {}; + /// + switch (conf.position) { + case "absolute": // Absolute from within window. + pt.offsetX = 0; + pt.offsetY = 0; + break; + case "difference": // Relative from origin. + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; + case "move": // Move target element. + pt.offsetX = touch.pageX - bbox.x1; + pt.offsetY = touch.pageY - bbox.y1; + break; + default: // Relative from within target. + pt.offsetX = bbox.x1; + pt.offsetY = bbox.y1; + break; + } + /// + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; + var y = (touch.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; + } else { + var x = (touch.pageX - pt.offsetX); + var y = (touch.pageY - pt.offsetY); + } + /// + pt.rotation = 0; + pt.scale = 1; + pt.startTime = pt.moveTime = (new Date).getTime(); + pt.move = {x: x, y: y}; + pt.start = {x: x, y: y}; + /// + conf.fingers++; + }; + /// + conf.event = event; + if (self.defaultListener) { + conf.listener = self.defaultListener; + delete self.defaultListener; + } + /// + var isTouchStart = !conf.fingers; + var track = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Adding touch events to tracking. + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; // Touch ID. + // Track the current state of the touches. + if (conf.fingers) { + if (conf.fingers >= conf.maxFingers) { + var ids = []; + for (var sid in conf.tracker) + ids.push(sid); + self.identifier = ids.join(","); + return isTouchStart; + } + var fingers = 0; // Finger ID. + for (var rid in track) { + // Replace removed finger. + if (track[rid].up) { + delete track[rid]; + addTouchStart(touch, sid); + conf.cancel = true; + break; + } + fingers++; + } + // Add additional finger. + if (track[sid]) + continue; + addTouchStart(touch, sid); + } else { // Start tracking fingers. + track = conf.tracker = {}; + self.bbox = conf.bbox = root.getBoundingBox(conf.target); + conf.fingers = 0; + conf.cancel = false; + addTouchStart(touch, sid); + } + } + /// + var ids = []; + for (var sid in conf.tracker) + ids.push(sid); + self.identifier = ids.join(","); + /// + return isTouchStart; + }; + + /* + End proxied pointer command. + */ + + root.pointerEnd = function(event, self, conf, onPointerUp) { + // Record changed touches have ended (iOS changedTouches is not reliable). + var touches = event.touches || []; + var length = touches.length; + var exists = {}; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier; + exists[sid || Infinity] = true; + } + for (var sid in conf.tracker) { + var track = conf.tracker[sid]; + if (exists[sid] || track.up) + continue; + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + pageX: track.pageX, + pageY: track.pageY, + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers--; + } + /* // This should work but fails in Safari on iOS4 so not using it. + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Record changed touches have ended (this should work). + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var track = conf.tracker[sid]; + if (track && !track.up) { + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers --; + } + } */ + // Wait for all fingers to be released. + if (conf.fingers !== 0) + return false; + // Record total number of fingers gesture used. + var ids = []; + conf.gestureFingers = 0; + for (var sid in conf.tracker) { + conf.gestureFingers++; + ids.push(sid); + } + self.identifier = ids.join(","); + // Our pointer gesture has ended. + return true; + }; + + /* + Returns mouse coords in an array to match event.*Touches + ------------------------------------------------------------ + var touch = event.changedTouches || root.getCoords(event); + */ + + root.getCoords = function(event) { + if (typeof(event.pageX) !== "undefined") { // Desktop browsers. + root.getCoords = function(event) { + return Array({ + type: "mouse", + x: event.pageX, + y: event.pageY, + pageX: event.pageX, + pageY: event.pageY, + identifier: Infinity + }); + }; + } else { // Internet Explorer <= 8.0 + root.getCoords = function(event) { + event = event || window.event; + return Array({ + type: "mouse", + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop, + pageX: event.clientX + document.documentElement.scrollLeft, + pageY: event.clientY + document.documentElement.scrollTop, + identifier: Infinity + }); + }; + } + return root.getCoords(event); + }; + + /* + Returns single coords in an object. + ------------------------------------------------------------ + var mouse = root.getCoord(event); + */ + + root.getCoord = function(event) { + if ("ontouchstart" in window) { // Mobile browsers. + var pX = 0; + var pY = 0; + root.getCoord = function(event) { + var touches = event.changedTouches; + if (touches.length) { // ontouchstart + ontouchmove + return { + x: pX = touches[0].pageX, + y: pY = touches[0].pageY + }; + } else { // ontouchend + return { + x: pX, + y: pY + }; + } + }; + } else if (typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers. + root.getCoord = function(event) { + return { + x: event.pageX, + y: event.pageY + }; + }; + } else { // Internet Explorer <=8.0 + root.getCoord = function(event) { + event = event || window.event; + return { + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop + }; + }; + } + return root.getCoord(event); + }; + + /* + Get target scale and position in space. + */ + + root.getBoundingBox = function(o) { + if (o === window || o === document) + o = document.body; + /// + var bbox = { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + scrollLeft: 0, + scrollTop: 0 + }; + /// + if (o === document.body) { + bbox.height = window.innerHeight; + bbox.width = window.innerWidth; + } else { + bbox.height = o.offsetHeight; + bbox.width = o.offsetWidth; + } + /// Get the scale of the element. + bbox.scaleX = o.width / bbox.width || 1; + bbox.scaleY = o.height / bbox.height || 1; + /// Get the offset of element. + var tmp = o; + while (tmp !== null) { + bbox.x1 += tmp.offsetLeft; + bbox.y1 += tmp.offsetTop; + tmp = tmp.offsetParent; + } + ; + /// Get the scroll of container element. + var tmp = o.parentNode; + while (tmp !== null) { + if (tmp === document.body) + break; + if (tmp.scrollTop === undefined) + break; + bbox.scrollLeft += tmp.scrollLeft; + bbox.scrollTop += tmp.scrollTop; + tmp = tmp.parentNode; + } + ; + /// Record the extent of box. + bbox.x2 = bbox.x1 + bbox.width; + bbox.y2 = bbox.y1 + bbox.height; + /// + return bbox; + }; + + /* + Keep track of metaKey, the proper ctrlKey for users platform. + */ + + (function() { + var agent = navigator.userAgent.toLowerCase(); + var mac = agent.indexOf("macintosh") !== -1; + if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari. + var watch = {91: true, 93: true}; + } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox. + var watch = {224: true}; + } else { // windows, linux, or mac opera. + var watch = {17: true}; + } + root.isMetaKey = function(event) { + return !!watch[event.keyCode]; + }; + root.metaTracker = function(event) { + if (watch[event.keyCode]) { + root.metaKey = event.type === "keydown"; + } + }; + })(); + + return root; + +})(Event.proxy); +/* + "Click" event proxy. + ---------------------------------------------------- + Event.add(window, "click", function(event, self) {}); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.click = function(conf) { + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var EVENT; + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + EVENT = event; + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (EVENT.cancelBubble && ++EVENT.bubble > 1) + return; + var pointers = EVENT.changedTouches || root.getCoords(EVENT); + var pointer = pointers[0]; + var bbox = conf.bbox; + var newbbox = root.getBoundingBox(conf.target); + if (conf.position === "relative") { + var ax = (pointer.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var ay = (pointer.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var ax = (pointer.pageX - bbox.x1); + var ay = (pointer.pageY - bbox.y1); + } + if (ax > 0 && ax < bbox.width && // Within target coordinates. + ay > 0 && ay < bbox.height && + bbox.scrollTop === newbbox.scrollTop) { + conf.listener(EVENT, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "click"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.click = root.click; + + return root; + +})(Event.proxy); +/* + "Double-Click" aka "Double-Tap" event proxy. + ---------------------------------------------------- + Event.add(window, "dblclick", function(event, self) {}); + ---------------------------------------------------- + Touch an target twice for <= 700ms, with less than 25 pixel drift. + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.dbltap = + root.dblclick = function(conf) { + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var delay = 700; // in milliseconds + var time0, time1, timeout; + var pointer0, pointer1; + // Tracking the events. + conf.onPointerDown = function(event) { + var pointers = event.changedTouches || root.getCoords(event); + if (time0 && !time1) { // Click #2 + pointer1 = pointers[0]; + time1 = (new Date).getTime() - time0; + } else { // Click #1 + pointer0 = pointers[0]; + time0 = (new Date).getTime(); + time1 = 0; + clearTimeout(timeout); + timeout = setTimeout(function() { + time0 = 0; + }, delay); + } + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + if (time0 && !time1) { + var pointers = event.changedTouches || root.getCoords(event); + pointer1 = pointers[0]; + } + var bbox = conf.bbox; + if (conf.position === "relative") { + var ax = (pointer1.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var ay = (pointer1.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var ax = (pointer1.pageX - bbox.x1); + var ay = (pointer1.pageY - bbox.y1); + } + if (!(ax > 0 && ax < bbox.width && // Within target coordinates.. + ay > 0 && ay < bbox.height && + Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance. + Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + if (time0 && time1) { + if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { + self.state = conf.gesture; + conf.listener(event, self); + } + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "dblclick"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.dbltap = root.dbltap; + Event.Gesture._gestureHandlers.dblclick = root.dblclick; + + return root; + +})(Event.proxy); +/* + "Drag" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: maxFingers, position. + ---------------------------------------------------- + Event.add(window, "drag", function(event, self) { + console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.dragElement = function(that, event) { + root.drag({ + event: event, + target: that, + position: "move", + listener: function(event, self) { + that.style.left = self.x + "px"; + that.style.top = self.y + "px"; + Event.prevent(event); + } + }); + }; + + root.drag = function(conf) { + conf.gesture = "drag"; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + if (!conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Process event listener. + conf.onPointerMove(event, "down"); + }; + conf.onPointerMove = function(event, state) { + if (!conf.tracker) + return conf.onPointerDown(event); + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + // Identifier defined outside of listener. + if (!pt) + continue; + pt.pageX = touch.pageX; + pt.pageY = touch.pageY; + // Record data. + self.state = state || "move"; + self.identifier = identifier; + self.start = pt.start; + self.fingers = conf.fingers; + if (conf.position === "relative") { + self.x = (pt.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; + self.y = (pt.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; + } else { + self.x = (pt.pageX - pt.offsetX); + self.y = (pt.pageY - pt.offsetY); + } + /// + conf.listener(event, self); + } + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { + if (!conf.monitor) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + if (conf.event) { + conf.onPointerDown(conf.event); + } else { // + Event.add(conf.target, "mousedown", conf.onPointerDown); + if (conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.drag = root.drag; + + return root; + +})(Event.proxy); +/* + "Gesture" event proxy (2+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + var RAD_DEG = Math.PI / 180; + + root.gesture = function(conf) { + conf.minFingers = conf.minFingers || conf.fingers || 2; + // Tracking the events. + conf.onPointerDown = function(event) { + var fingers = conf.fingers; + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + // Record gesture start. + if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { + self.fingers = conf.minFingers; + self.scale = 1; + self.rotation = 0; + self.state = "start"; + var sids = ""; //- FIXME(mud): can generate duplicate IDs. + for (var key in conf.tracker) + sids += key; + self.identifier = parseInt(sids); + conf.listener(event, self); + } + }; + /// + conf.onPointerMove = function(event, state) { + var bbox = conf.bbox; + var points = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Update tracker coordinates. + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var pt = points[sid]; + // Check whether "pt" is used by another gesture. + if (!pt) + continue; + // Find the actual coordinates. + if (conf.position === "relative") { + pt.move.x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + pt.move.y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + pt.move.x = (touch.pageX - bbox.x1); + pt.move.y = (touch.pageY - bbox.y1); + } + } + /// + if (conf.fingers < conf.minFingers) + return; + /// + var touches = []; + var scale = 0; + var rotation = 0; + /// Calculate centroid of gesture. + var centroidx = 0; + var centroidy = 0; + var length = 0; + for (var sid in points) { + var touch = points[sid]; + if (touch.up) + continue; + centroidx += touch.move.x; + centroidy += touch.move.y; + length++; + } + centroidx /= length; + centroidy /= length; + /// + for (var sid in points) { + var touch = points[sid]; + if (touch.up) + continue; + var start = touch.start; + if (!start.distance) { + var dx = start.x - centroidx; + var dy = start.y - centroidy; + start.distance = Math.sqrt(dx * dx + dy * dy); + start.angle = Math.atan2(dx, dy) / RAD_DEG; + } + // Calculate scale. + var dx = touch.move.x - centroidx; + var dy = touch.move.y - centroidy; + var distance = Math.sqrt(dx * dx + dy * dy); + scale += distance / start.distance; + // Calculate rotation. + var angle = Math.atan2(dx, dy) / RAD_DEG; + var rotate = (start.angle - angle + 360) % 360 - 180; + touch.DEG2 = touch.DEG1; // Previous degree. + touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree. + if (typeof(touch.DEG2) !== "undefined") { + if (rotate > 0) { + touch.rotation += touch.DEG1 - touch.DEG2; + } else { + touch.rotation -= touch.DEG1 - touch.DEG2; + } + rotation += touch.rotation; + } + // Attach current points to self. + touches.push(touch.move); + } + /// + self.touches = touches; + self.fingers = conf.fingers; + self.scale = scale / conf.fingers; + self.rotation = rotation / conf.fingers; + self.state = "change"; + conf.listener(event, self); + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + var fingers = conf.fingers; + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + // Check whether fingers has dropped below minFingers. + if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { + self.fingers = conf.fingers; + self.state = "end"; + conf.listener(event, self); + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.gesture = root.gesture; + + return root; + +})(Event.proxy); +/* + "Pointer" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.pointerdown = + root.pointermove = + root.pointerup = function(conf) { + if (conf.target.isPointerEmitter) + return; + // Tracking the events. + var isDown = true; + conf.onPointerDown = function(event) { + isDown = false; + self.gesture = "pointerdown"; + conf.listener(event, self); + }; + conf.onPointerMove = function(event) { + self.gesture = "pointermove"; + conf.listener(event, self, isDown); + }; + conf.onPointerUp = function(event) { + isDown = true; + self.gesture = "pointerup"; + conf.listener(event, self, true); + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + Event.add(conf.target, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Return this object. + conf.target.isPointerEmitter = true; + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; + Event.Gesture._gestureHandlers.pointermove = root.pointermove; + Event.Gesture._gestureHandlers.pointerup = root.pointerup; + + return root; + +})(Event.proxy); +/* + "Device Motion" and "Shake" event proxy. + ---------------------------------------------------- + http://developer.android.com/reference/android/hardware/SensorEvent.html#values + ---------------------------------------------------- + Event.add(window, "shake", function(event, self) {}); + Event.add(window, "devicemotion", function(event, self) { + console.log(self.acceleration, self.accelerationIncludingGravity); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.shake = function(conf) { + // Externally accessible data. + var self = { + gesture: "devicemotion", + acceleration: {}, + accelerationIncludingGravity: {}, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener('devicemotion', onDeviceMotion, false); + } + }; + // Setting up local variables. + var threshold = 4; // Gravitational threshold. + var timeout = 1000; // Timeout between shake events. + var timeframe = 200; // Time between shakes. + var shakes = 3; // Minimum shakes to trigger event. + var lastShake = (new Date).getTime(); + var gravity = {x: 0, y: 0, z: 0}; + var delta = { + x: {count: 0, value: 0}, + y: {count: 0, value: 0}, + z: {count: 0, value: 0} + }; + // Tracking the events. + var onDeviceMotion = function(e) { + var alpha = 0.8; // Low pass filter. + var o = e.accelerationIncludingGravity; + gravity.x = alpha * gravity.x + (1 - alpha) * o.x; + gravity.y = alpha * gravity.y + (1 - alpha) * o.y; + gravity.z = alpha * gravity.z + (1 - alpha) * o.z; + self.accelerationIncludingGravity = gravity; + self.acceleration.x = o.x - gravity.x; + self.acceleration.y = o.y - gravity.y; + self.acceleration.z = o.z - gravity.z; + /// + if (conf.gesture === "devicemotion") { + conf.listener(e, self); + return; + } + var data = "xyz"; + var now = (new Date).getTime(); + for (var n = 0, length = data.length; n < length; n++) { + var letter = data[n]; + var ACCELERATION = self.acceleration[letter]; + var DELTA = delta[letter]; + var abs = Math.abs(ACCELERATION); + /// Check whether another shake event was recently registered. + if (now - lastShake < timeout) + continue; + /// Check whether delta surpasses threshold. + if (abs > threshold) { + var idx = now * ACCELERATION / abs; + var span = Math.abs(idx + DELTA.value); + // Check whether last delta was registered within timeframe. + if (DELTA.value && span < timeframe) { + DELTA.value = idx; + DELTA.count++; + // Check whether delta count has enough shakes. + if (DELTA.count === shakes) { + conf.listener(e, self); + // Reset tracking. + lastShake = now; + DELTA.value = 0; + DELTA.count = 0; + } + } else { + // Track first shake. + DELTA.value = idx; + DELTA.count = 1; + } + } + } + }; + // Attach events. + if (!window.addEventListener) + return; + window.addEventListener('devicemotion', onDeviceMotion, false); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.shake = root.shake; + + return root; + +})(Event.proxy); +/* + "Swipe" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: snap, threshold, maxFingers. + ---------------------------------------------------- + Event.add(window, "swipe", function(event, self) { + console.log(self.velocity, self.angle); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + var RAD_DEG = Math.PI / 180; + + root.swipe = function(conf) { + conf.snap = conf.snap || 90; // angle snap. + conf.threshold = conf.threshold || 1; // velocity threshold. + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var o = conf.tracker[sid]; + // Identifier defined outside of listener. + if (!o) + continue; + o.move.x = touch.pageX; + o.move.y = touch.pageY; + o.moveTime = (new Date).getTime(); + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + /// + var velocity1; + var velocity2 + var degree1; + var degree2; + /// Calculate centroid of gesture. + var start = {x: 0, y: 0}; + var endx = 0; + var endy = 0; + var length = 0; + /// + for (var sid in conf.tracker) { + var touch = conf.tracker[sid]; + var xdist = touch.move.x - touch.start.x; + var ydist = touch.move.y - touch.start.y; + + endx += touch.move.x; + endy += touch.move.y; + start.x += touch.start.x; + start.y += touch.start.y; + length++; + + + var distance = Math.sqrt(xdist * xdist + ydist * ydist); + var ms = touch.moveTime - touch.startTime; + var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; + var velocity2 = ms ? distance / ms : 0; + if (typeof(degree1) === "undefined") { + degree1 = degree2; + velocity1 = velocity2; + } else if (Math.abs(degree2 - degree1) <= 20) { + degree1 = (degree1 + degree2) / 2; + velocity1 = (velocity1 + velocity2) / 2; + } else { + return; + } + } + /// + if (velocity1 > conf.threshold) { + start.x /= length; + start.y /= length; + self.start = start; + self.x = endx / length; + self.y = endy / length; + self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360); + self.velocity = velocity1; + self.fingers = conf.gestureFingers; + self.state = "swipe"; + conf.listener(event, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.swipe = root.swipe; + + return root; + +})(Event.proxy); +/* + "Tap" and "Longpress" event proxy. + ---------------------------------------------------- + CONFIGURE: delay (longpress), timeout (tap). + ---------------------------------------------------- + Event.add(window, "tap", function(event, self) { + console.log(self.fingers); + }); + ---------------------------------------------------- + multi-finger tap // touch an target for <= 250ms. + multi-finger longpress // touch an target for >= 500ms + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.tap = + root.longpress = function(conf) { + conf.delay = conf.delay || 500; + conf.timeout = conf.timeout || 250; + // Setting up local variables. + var timestamp, timeout; + // Tracking the events. + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + timestamp = (new Date).getTime(); + // Initialize event listeners. + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Make sure this is a "longpress" event. + if (conf.gesture !== "longpress") + return; + timeout = setTimeout(function() { + if (event.cancelBubble && ++event.bubble > 1) + return; + // Make sure no fingers have been changed. + var fingers = 0; + for (var key in conf.tracker) { + if (conf.tracker[key].end === true) + return; + if (conf.cancel) + return; + fingers++; + } + // Send callback. + self.state = "start"; + self.fingers = fingers; + conf.listener(event, self); + }, conf.delay); + } + }; + conf.onPointerMove = function(event) { + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + if (!pt) + continue; + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; + var y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; + } else { + var x = (touch.pageX - bbox.x1); + var y = (touch.pageY - bbox.y1); + } + if (!(x > 0 && x < bbox.width && // Within target coordinates.. + y > 0 && y < bbox.height && + Math.abs(x - pt.start.x) <= 25 && // Within drift deviance. + Math.abs(y - pt.start.y) <= 25)) { + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + conf.cancel = true; + return; + } + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + clearTimeout(timeout); + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (event.cancelBubble && ++event.bubble > 1) + return; + // Callback release on longpress. + if (conf.gesture === "longpress") { + if (self.state === "start") { + self.state = "end"; + conf.listener(event, self); + } + return; + } + // Cancel event due to movement. + if (conf.cancel) + return; + // Ensure delay is within margins. + if ((new Date).getTime() - timestamp > conf.timeout) + return; + // Send callback. + self.state = "tap"; + self.fingers = conf.gestureFingers; + conf.listener(event, self); + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.tap = root.tap; + Event.Gesture._gestureHandlers.longpress = root.longpress; + + return root; + +})(Event.proxy); +/* + "Mouse Wheel" event proxy. + ---------------------------------------------------- + Event.add(window, "wheel", function(event, self) { + console.log(self.state, self.wheelDelta); + }); + */ + +if (typeof(Event) === "undefined") + var Event = {}; +if (typeof(Event.proxy) === "undefined") + Event.proxy = {}; + +Event.proxy = (function(root) { + "use strict"; + + root.wheel = function(conf) { + // Configure event listener. + var interval; + var timeout = conf.timeout || 150; + var count = 0; + // Externally accessible data. + var self = { + gesture: "wheel", + state: "start", + wheelDelta: 0, + target: conf.target, + listener: conf.listener, + remove: function() { + conf.target[remove](type, onMouseWheel, false); + } + }; + // Tracking the events. + var onMouseWheel = function(event) { + event = event || window.event; + self.state = count++ ? "change" : "start"; + self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; + conf.listener(event, self); + clearTimeout(interval); + interval = setTimeout(function() { + count = 0; + self.state = "end"; + self.wheelDelta = 0; + conf.listener(event, self); + }, timeout); + }; + // Attach events. + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + var type = Event.supports("mousewheel") ? "mousewheel" : "DOMMouseScroll"; + conf.target[add](type, onMouseWheel, false); + // Return this object. + return self; + }; + + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.wheel = root.wheel; + + return root; + +})(Event.proxy); + + /** * Wrapper around `console.log` (when available) * @param {Any} values Values to log @@ -2097,7 +4003,46 @@ fabric.Collection = { return new fabric.Point(rx, ry).addEquals(origin); } + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + function transformPoint(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[1] * p.y, + t[2] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[1] * p.y + t[4], + t[2] * p.x + t[3] * p.y + t[5] + ); + } + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + function invertTransform(t) { + var r = t.slice(), + a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; + var o = transformPoint({x: t[4], y: t[5]}, r); + r[4] = -o.x; + r[5] = -o.y; + return r + } + /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static @@ -2535,6 +4480,8 @@ fabric.Collection = { fabric.util.degreesToRadians = degreesToRadians; fabric.util.radiansToDegrees = radiansToDegrees; fabric.util.rotatePoint = rotatePoint; + fabric.util.transformPoint = transformPoint; + fabric.util.invertTransform = invertTransform; fabric.util.toFixed = toFixed; fabric.util.getRandomInt = getRandomInt; fabric.util.falseFunction = falseFunction; @@ -5280,167 +7227,167 @@ fabric.util.string = { })(typeof exports !== 'undefined' ? exports : this); -(function(global) { - - "use strict"; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (u_b !== 0) { - var ua = ua_t / u_b, - ub = ub_t / u_b; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection("Intersection"); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (ua_t === 0 || ub_t === 0) { - result = new Intersection("Coincident"); - } - else { - result = new Intersection("Parallel"); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i+1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i+1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Intersection) { + fabric.warn('fabric.Intersection is already defined'); + return; + } + + /** + * Intersection class + * @class fabric.Intersection + * @memberOf fabric + * @constructor + */ + function Intersection(status) { + this.status = status; + this.points = []; + } + + fabric.Intersection = Intersection; + + fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { + + /** + * Appends a point to intersection + * @param {fabric.Point} point + */ + appendPoint: function (point) { + this.points.push(point); + }, + + /** + * Appends points to intersection + * @param {Array} points + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + } + }; + + /** + * Checks if one line intersects another + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {fabric.Point} b1 + * @param {fabric.Point} b2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (u_b !== 0) { + var ua = ua_t / u_b, + ub = ub_t / u_b; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection("Intersection"); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection(); + } + } + else { + if (ua_t === 0 || ub_t === 0) { + result = new Intersection("Coincident"); + } + else { + result = new Intersection("Parallel"); + } + } + return result; + }; + + /** + * Checks if line intersects polygon + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {Array} points + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ + var result = new Intersection(), + length = points.length; + + for (var i = 0; i < length; i++) { + var b1 = points[i], + b2 = points[(i+1) % length], + inter = Intersection.intersectLineLine(a1, a2, b1, b2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * Checks if polygon intersects another polygon + * @static + * @param {Array} points1 + * @param {Array} points2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection(), + length = points1.length; + + for (var i = 0; i < length; i++) { + var a1 = points1[i], + a2 = points1[(i+1) % length], + inter = Intersection.intersectLinePolygon(a1, a2, points2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * Checks if polygon intersects rectangle + * @static + * @param {Array} points + * @param {Number} r1 + * @param {Number} r2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection(); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + +})(typeof exports !== 'undefined' ? exports : this); (function(global) { @@ -6711,6 +8658,20 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ allowTouchScrolling: false, + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: [1, 0, 0, 1, 0, 0], + + /** + * Color of canvas border + * @type String + * @default + */ + canvasBorderColor: '', + /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -6999,6 +8960,68 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this; }, + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + + /** + * Returns point at center of viewport + * @return {fabric.Point} the top left corner of the viewport + */ + getViewportCenter: function () { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ), + x = this.viewportTransform[4], + y = this.viewportTransform[5]; + + return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + // TODO: just change the scale, preserve other transformations + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + return this; + }, + + /** + * Centers viewport of this canvas instance on given point + * @param {Numer} x value for center of viewport + * @param {Numer} y value for center of viewport + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportCenter: function (x, y) { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ); + this.viewportTransform[4] = x - wh.x/2; + this.viewportTransform[5] = y - wh.y/2; + return this; + }, + + /** + * Centers viewport of this canvas instance + * @return {fabric.Canvas} instance + * @chainable true + */ + centerViewport: function () { + return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + }, + /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} @@ -7050,8 +9073,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); - obj.setCoords(); obj.canvas = this; + obj.setCoords(); + if (obj._objects) { + for (var i = 0, len = obj._objects; i < len; i++) { + obj._objects[i].canvas = this; + obj._objects[i].setCoords(); + } + } this.fire('object:added', { target: obj }); obj.fire('added'); }, @@ -7153,6 +9182,10 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ if (typeof this.backgroundImage === 'object') { this._drawBackroundImage(canvasToDrawOn); } + + if (this.canvasBorderColor) { + this._drawCanvasBorder(canvasToDrawOn); + } var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { @@ -7209,6 +9242,23 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ canvasToDrawOn.restore(); }, + /** + * @private + * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on + */ + _drawCanvasBorder: function(canvasToDrawOn) { + var xy = fabric.util.transformPoint(new fabric.Point(0, 0), this.viewportTransform), + wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform, true + ); + canvasToDrawOn.save(); + canvasToDrawOn.lineWidth = 1; + canvasToDrawOn.strokeStyle = this.canvasBorderColor; + canvasToDrawOn.strokeRect(xy.x - 1.5, xy.y - 1.5, wh.x + 2, wh.y + 2); + canvasToDrawOn.restore(); + }, + /** * Method to render only the top canvas. * Also used to render the group selection box. @@ -7959,6 +10009,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private */ _getSVGPathData: function() { + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + for (var i = 0, len = this._points.length; i < len; i++) { + this._points[i] = fabric.util.transformPoint(this._points[i], ivt); + } this.box = this.getPathBoundingBox(this._points); return this.convertPointsToSVGPath( this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); @@ -8168,6 +10222,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric circles.push(circle); } var group = new fabric.Group(circles); + group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -8367,6 +10422,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; + var vpt = this.canvas.viewportTransform; for (var i = 0; i < this.density; i++) { @@ -8382,8 +10438,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric else { width = this.dotWidth; } - - var point = { x: x, y: y, width: width }; + + var point = fabric.point(x, y); + point = fabric.util.transformPoint(point, vpt); + point.width = width if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; @@ -8705,7 +10763,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target) { - var pointer = this.getPointer(e), + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html @@ -8719,7 +10777,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), x = pointer.x, - y = pointer.y; + y = pointer.y, + lt; var isObjectInGroup = ( activeGroup && @@ -8728,8 +10787,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab ); if (isObjectInGroup) { - x -= activeGroup.left; - y -= activeGroup.top; + lt = new fabric.Point(activeGroup.left, activeGroup.top); + lt = fabric.util.transformPoint(lt, this.viewportTransform, true); + x -= lt.x; + y -= lt.y; } return { x: x, y: y }; }, @@ -8838,7 +10899,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var action = 'drag', corner, - pointer = getPointer(e, target.canvas.upperCanvasEl); + pointer = getPointer(e, target.canvas.UpperCanvasEl); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -8954,6 +11015,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); var group = new fabric.Group( isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); + group.canvas = this; this.setActiveGroup(group); this._activeObject = null; @@ -9233,6 +11295,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else if (group.length > 1) { group = new fabric.Group(group.reverse()); + group.canvas = this; this.setActiveGroup(group); group.saveCoords(); this.fire('selection:created', { target: group }); @@ -9249,7 +11312,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.skipTargetFind) return; var target, - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && @@ -9289,7 +11352,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } } for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); if (!isTransparent) { target = possibleTargets[j]; @@ -9306,8 +11369,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e * @return {Object} object with "x" and "y" number values */ - getPointer: function (e) { - var pointer = getPointer(e, this.upperCanvasEl); + getPointer: function (e, ignoreZoom, upperCanvasEl) { + if (!upperCanvasEl) { + upperCanvasEl = this.upperCanvasEl; + } + var pointer = getPointer(e, upperCanvasEl); + if (!ignoreZoom) { + pointer = fabric.util.transformPoint( + pointer, + fabric.util.invertTransform(this.viewportTransform) + ); + } + return { x: pointer.x - this._offset.left, y: pointer.y - this._offset.top @@ -9753,7 +11826,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } } else { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); } render = this._shouldRender(target, pointer); @@ -9801,7 +11874,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e)); + this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); this.fire('mouse:down', { e: e }); }, @@ -9827,7 +11900,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this._currentTransform) return; var target = this.findTarget(e), - pointer = this.getPointer(e), + pointer = this.getPointer(e, true), corner, render; @@ -9928,8 +12001,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.isDrawingMode) { if (this._isCurrentlyDrawing) { - pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); + this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; this.fire('mouse:move', { e: e }); @@ -9940,10 +12012,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // We initially clicked in an empty area, so we draw a box for multiple selection. if (groupSelector) { - pointer = getPointer(e, this.upperCanvasEl); + pointer = this.getPointer(e, true); - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } else if (!this._currentTransform) { @@ -9968,7 +12040,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else { // object is being transformed (scaled/rotated/moved/etc.) - pointer = getPointer(e, this.upperCanvasEl); + pointer = this.getPointer(e); var x = pointer.x, y = pointer.y, @@ -10523,6 +12595,83 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }); +(function() { + + var degreesToRadians = fabric.util.degreesToRadians, + radiansToDegrees = fabric.util.radiansToDegrees; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports + * 2 finger gestures. + * + * @param e Event object by Event.js + * @param self Event proxy object by Event.js + */ + __onTransformGesture: function(e, self) { + + if (this.isDrawingMode || e.touches.length !== 2 || 'gesture' !== self.gesture) { + return; + } + + var target = this.findTarget(e); + if ('undefined' !== typeof target) { + this.onBeforeScaleRotate(target); + this._rotateObjectByAngle(self.rotation); + this._scaleObjectBy(self.scale); + } + + this.fire('touch:gesture', {target: target, e: e, self: self}); + }, + + /** + * Scales an object by a factor + * @param s {Number} The scale factor to apply to the current scale level + * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. + * When not provided, an object is scaled by both dimensions equally + */ + _scaleObjectBy: function(s, by) { + var t = this._currentTransform, + target = t.target; + + var lockScalingX = target.get('lockScalingX'), + lockScalingY = target.get('lockScalingY'); + + if (lockScalingX && lockScalingY) return; + + target._scaling = true; + + if (!by) { + if (!lockScalingX) { + target.set('scaleX', t.scaleX * s); + } + if (!lockScalingY) { + target.set('scaleY', t.scaleY * s); + } + } + else if (by === 'x' && !target.get('lockUniScaling')) { + lockScalingX || target.set('scaleX', t.scaleX * s); + } + else if (by === 'y' && !target.get('lockUniScaling')) { + lockScalingY || target.set('scaleY', t.scaleY * s); + } + }, + + /** + * Rotates object by an angle + * @param curAngle {Number} the angle of rotation in degrees + */ + _rotateObjectByAngle: function(curAngle) { + var t = this._currentTransform; + + if (t.target.get('lockRotation')) return; + t.target.angle = radiansToDegrees(degreesToRadians(curAngle) + t.theta); + } + }); +})(); + + (function(global) { "use strict"; @@ -11537,11 +13686,24 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); } if (!noTransform) { + if (this.group) { + this.group.transform(ctx); + } this.transform(ctx); } @@ -11571,8 +13733,23 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -12578,40 +14755,40 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), - cosTh = Math.cos(theta); - - var coords = this.getCenterPoint(); + cosTh = Math.cos(theta), + coords = this.getCenterPoint(), + wh = new fabric.Point(this.currentWidth, this.currentHeight); var tl = { x: coords.x - offsetX, y: coords.y - offsetY }; var tr = { - x: tl.x + (this.currentWidth * cosTh), - y: tl.y + (this.currentWidth * sinTh) + x: tl.x + (wh.x * cosTh), + y: tl.y + (wh.x * sinTh) }; var br = { - x: tr.x - (this.currentHeight * sinTh), - y: tr.y + (this.currentHeight * cosTh) + x: tr.x - (wh.y * sinTh), + y: tr.y + (wh.y * cosTh) }; var bl = { - x: tl.x - (this.currentHeight * sinTh), - y: tl.y + (this.currentHeight * cosTh) + x: tl.x - (wh.y * sinTh), + y: tl.y + (wh.y * cosTh) }; var ml = { - x: tl.x - (this.currentHeight/2 * sinTh), - y: tl.y + (this.currentHeight/2 * cosTh) + x: tl.x - (wh.y/2 * sinTh), + y: tl.y + (wh.y/2 * cosTh) }; var mt = { - x: tl.x + (this.currentWidth/2 * cosTh), - y: tl.y + (this.currentWidth/2 * sinTh) + x: tl.x + (wh.x/2 * cosTh), + y: tl.y + (wh.x/2 * sinTh) }; var mr = { - x: tr.x - (this.currentHeight/2 * sinTh), - y: tr.y + (this.currentHeight/2 * cosTh) + x: tr.x - (wh.y/2 * sinTh), + y: tr.y + (wh.y/2 * cosTh) }; var mb = { - x: bl.x + (this.currentWidth/2 * cosTh), - y: bl.y + (this.currentWidth/2 * sinTh) + x: bl.x + (wh.x/2 * cosTh), + y: bl.y + (wh.x/2 * sinTh) }; var mtr = { x: mt.x, @@ -12641,6 +14818,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati mtr: mtr }; + var vpt; + if (this.canvas) { + vpt = this.canvas.viewportTransform; + } + if (!vpt) { // TODO + vpt = [1, 0, 0, 1, 0, 0]; + } + for (c in this.oCoords) { + this.oCoords[c] = fabric.util.transformPoint(this.oCoords[c], vpt); + } + // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); @@ -12965,25 +15153,38 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot scaleY = 1 / this._constrainScale(this.scaleY); ctx.lineWidth = 1 / this.borderScaleFactor; - - ctx.scale(scaleX, scaleY); - - var w = this.getWidth(), - h = this.getHeight(); + + var vpt = this.canvas.viewportTransform; + // debugging + if (!vpt) { + vpt = [1, 0, 0, 1, 0, 0] + console.log("No vpt! interactivity", this.canvas, this.get('canvas'), this); + } + + var wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), + sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), + w = wh.x, + h = wh.y, + sx= sxy.x, + sy= sxy.y; + if (this.get('group')) { + w = w * this.get('group').scaleX; + h = h * this.get('group').scaleY; + } ctx.strokeRect( - ~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) - 0.5, // offset needed to make lines look sharper - ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) - 0.5, - ~~(w + padding2 + strokeWidth * this.scaleX) + 1, // double offset needed to make lines look sharper - ~~(h + padding2 + strokeWidth * this.scaleY) + 1 + ~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper + ~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5, + ~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper + ~~(h + padding2 + strokeWidth * sy) + 1 ); if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { var rotateHeight = ( this.flipY - ? h + (strokeWidth * this.scaleY) + (padding * 2) - : -h - (strokeWidth * this.scaleY) - (padding * 2) + ? h + (strokeWidth * sx) + (padding * 2) + : -h - (strokeWidth * sy) - (padding * 2) ) / 2; ctx.beginPath(); @@ -12999,7 +15200,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * Draws corners of an object's bounding box. - * Requires public properties: width, height, scaleX, scaleY + * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg @@ -13011,99 +15212,95 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - left = -(this.width / 2), - top = -(this.height / 2), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + width = wh.x, + height = wh.y, + left = -(width / 2), + top = -(height / 2), _left, _top, - sizeX = size / this.scaleX, - sizeY = size / this.scaleY, - paddingX = this.padding / this.scaleX, - paddingY = this.padding / this.scaleY, - scaleOffsetY = size2 / this.scaleY, - scaleOffsetX = size2 / this.scaleX, - scaleOffsetSizeX = (size2 - size) / this.scaleX, - scaleOffsetSizeY = (size2 - size) / this.scaleY, - height = this.height, - width = this.width, + padding = this.padding, + scaleOffset = size2, + scaleOffsetSize = size2 - size, methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', transparent = this.transparentCorners, isVML = typeof G_vmlCanvasManager !== 'undefined'; ctx.save(); - ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); + ctx.lineWidth = 1; ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = ctx.fillStyle = this.cornerColor; // top-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // top-right - _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width - scaleOffset + strokeWidth2 + padding; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // bottom-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); if (!this.get('lockUniScaling')) { // middle-top - _left = left + width/2 - scaleOffsetX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; + _left = left + width/2 - scaleOffset; + _top = top - scaleOffset - strokeWidth2 - padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-bottom - _left = left + width/2 - scaleOffsetX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + _left = left + width/2 - scaleOffset; + _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left + width + scaleOffsetSize + strokeWidth2 + padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); // middle-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height/2 - scaleOffsetY; + _left = left - scaleOffset - strokeWidth2 - padding; + _top = top + height/2 - scaleOffset; - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } // middle-top-rotate if (this.hasRotatingPoint) { - _left = left + width/2 - scaleOffsetX; + _left = left + width/2 - scaleOffset; _top = this.flipY ? - (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + (top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding) + : (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding); - isVML || transparent || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); + isVML || transparent || ctx.clearRect(_left, _top, size, size); + ctx[methodName](_left, _top, size, size); } ctx.restore(); @@ -15173,6 +17370,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -15206,8 +17413,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -15541,6 +17754,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -15554,8 +17777,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); + ctx.save(); if (this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -15718,7 +17947,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke; + invoke = fabric.util.array.invoke, + degreesToRadians = fabric.util.degreesToRadians; if (fabric.Group) { return; @@ -15927,9 +18157,12 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!this.visible) return; ctx.save(); - this.transform(ctx); + var v = this.canvas.viewportTransform; - var groupScaleFactor = Math.max(this.scaleX, this.scaleY); + var sxy = fabric.util.transformPoint( + new fabric.Point(this.scaleX, this.scaleY), + v, true), + groupScaleFactor = Math.max(sxy.x, sxy.y); this.clipTo && fabric.util.clipContext(this, ctx); @@ -15943,22 +18176,21 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot // do not render if object is not visible if (!object.visible) continue; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; - object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; } this.clipTo && ctx.restore(); - if (!noTransform && this.active) { + if (this.active && !noTransform) { + var center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } ctx.restore(); - this.setCoords(); }, /** @@ -16106,9 +18338,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _calcBounds: function() { var aX = [], aY = [], - minX, minY, maxX, maxY, o, width, height, + minX, minY, maxX, maxY, o, width, height, minXY, maxXY, ivt, // TODO: cleanup i = 0, - len = this._objects.length; + len = this._objects.length, + vpt; + if (this.canvas) { + vpt = this.canvas.viewportTransform; + } + if (!vpt) { // TODO: this always happens when new groups are created + vpt = [1, 0, 0, 1, 0, 0]; + } for (; i < len; ++i) { o = this._objects[i]; @@ -16119,19 +18358,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - minX = min(aX); - maxX = max(aX); - minY = min(aY); - maxY = max(aY); + minXY = new fabric.Point(min(aX), min(aY)); + maxXY = new fabric.Point(max(aX), max(aY)); - width = (maxX - minX) || 0; - height = (maxY - minY) || 0; + ivt = fabric.util.invertTransform(vpt); + this.width = (maxXY.x - minXY.x) || 0; + this.height = (maxXY.y - minXY.y) || 0; - this.width = width; - this.height = height; + minXY = fabric.util.transformPoint(minXY, ivt); + maxXY = fabric.util.transformPoint(maxXY, ivt); + this.width = (maxXY.x - minXY.x) || 0; + this.height = (maxXY.y - minXY.y) || 0; - this.left = (minX + width / 2) || 0; - this.top = (minY + height / 2) || 0; + this.left = (minXY.x + maxXY.x) / 2 || 0; + this.top = (minXY.y + maxXY.y) / 2 || 0; }, /* _TO_SVG_START_ */ @@ -16315,8 +18555,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } var isInPathGroup = this.group && this.group.type !== 'group'; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); @@ -16338,8 +18587,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); + ctx.restore(); + ctx.save(); if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } @@ -18602,8 +20866,22 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); + ctx.restore(); + ctx.save(); if (!noTransform && this.active) { + var center; + center = fabric.util.transformPoint(this.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(fabric.util.degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index bebebde2..f790b6d1 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -76,6 +76,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric circles.push(circle); } var group = new fabric.Group(circles); + group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 9a42186a..464c5c4a 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -141,6 +141,10 @@ * @private */ _getSVGPathData: function() { + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + for (var i = 0, len = this._points.length; i < len; i++) { + this._points[i] = fabric.util.transformPoint(this._points[i], ivt); + } this.box = this.getPathBoundingBox(this._points); return this.convertPointsToSVGPath( this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index aac186d2..4ff8fd9a 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -110,6 +110,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(rects); + group.canvas = this.canvas; + this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -145,9 +147,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; ctx.save(); + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; + var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); + point.x = tpoint.x; + point.y = tpoint.y; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } @@ -163,6 +169,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; + var vpt = this.canvas.viewportTransform; for (var i = 0; i < this.density; i++) { @@ -178,8 +185,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric else { width = this.dotWidth; } - - var point = { x: x, y: y, width: width }; + + var point = new fabric.Point(x, y); + point = fabric.util.transformPoint(point, vpt); + point.width = width if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; diff --git a/src/canvas.class.js b/src/canvas.class.js index a2c12606..04990ac8 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -249,7 +249,7 @@ * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target) { - var pointer = this.getPointer(e), + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html @@ -504,6 +504,7 @@ var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); var group = new fabric.Group( isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); + group.canvas = this; this.setActiveGroup(group); this._activeObject = null; @@ -783,6 +784,7 @@ } else if (group.length > 1) { group = new fabric.Group(group.reverse()); + group.canvas = this; this.setActiveGroup(group); group.saveCoords(); this.fire('selection:created', { target: group }); @@ -799,7 +801,7 @@ if (this.skipTargetFind) return; var target, - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && @@ -839,7 +841,7 @@ } } for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); if (!isTransparent) { target = possibleTargets[j]; @@ -856,8 +858,17 @@ * @param {Event} e * @return {Object} object with "x" and "y" number values */ - getPointer: function (e) { - var pointer = getPointer(e, this.upperCanvasEl); + getPointer: function (e, ignoreZoom, upperCanvasEl) { + if (!upperCanvasEl) { + upperCanvasEl = this.upperCanvasEl; + } + var pointer = getPointer(e, upperCanvasEl); + if (!ignoreZoom) { + pointer = fabric.util.transformPoint( + pointer, + fabric.util.invertTransform(this.viewportTransform) + ); + } return { x: pointer.x - this._offset.left, diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index 330d2877..077fab1d 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -187,7 +187,7 @@ } } else { - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); } render = this._shouldRender(target, pointer); @@ -235,7 +235,7 @@ if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e)); + this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); this.fire('mouse:down', { e: e }); }, @@ -261,7 +261,7 @@ if (this._currentTransform) return; var target = this.findTarget(e), - pointer = this.getPointer(e), + pointer = this.getPointer(e, true), corner, render; @@ -362,8 +362,7 @@ if (this.isDrawingMode) { if (this._isCurrentlyDrawing) { - pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); + this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; this.fire('mouse:move', { e: e }); @@ -374,10 +373,10 @@ // We initially clicked in an empty area, so we draw a box for multiple selection. if (groupSelector) { - pointer = getPointer(e, this.upperCanvasEl); + pointer = this.getPointer(e, true); - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } else if (!this._currentTransform) { diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 3deff055..c929a437 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -306,10 +306,21 @@ var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, - theta = degreesToRadians(this.angle); + theta = degreesToRadians(this.angle), + vpt; + if (this.canvas) { + vpt = this.canvas.viewportTransform; + } + if (!vpt) { // TODO + vpt = [1, 0, 0, 1, 0, 0]; + }; - this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; - this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; + var f = function (p) { + return fabric.util.transformPoint(p, vpt); + } + + this.currentWidth = (this.width + strokeWidth) * this.scaleX; + this.currentHeight = (this.height + strokeWidth) * this.scaleY; // If width is negative, make postive. Fixes path selection issue if (this.currentWidth < 0) { @@ -329,42 +340,32 @@ cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(this.currentWidth, this.currentHeight); - var tl = { - x: coords.x - offsetX, - y: coords.y - offsetY - }; - var tr = { - x: tl.x + (wh.x * cosTh), - y: tl.y + (wh.x * sinTh) - }; - var br = { - x: tr.x - (wh.y * sinTh), - y: tr.y + (wh.y * cosTh) - }; - var bl = { - x: tl.x - (wh.y * sinTh), - y: tl.y + (wh.y * cosTh) - }; - var ml = { - x: tl.x - (wh.y/2 * sinTh), - y: tl.y + (wh.y/2 * cosTh) - }; - var mt = { - x: tl.x + (wh.x/2 * cosTh), - y: tl.y + (wh.x/2 * sinTh) - }; - var mr = { - x: tr.x - (wh.y/2 * sinTh), - y: tr.y + (wh.y/2 * cosTh) - }; - var mb = { - x: bl.x + (wh.x/2 * cosTh), - y: bl.y + (wh.x/2 * sinTh) - }; - var mtr = { - x: mt.x, - y: mt.y - }; + var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); + var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); + var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); + var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); + var tl = f(_tl); + var tr = f(_tr); + var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); + var bl = f(_bl); + var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); + var mt = f(_mt); + var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); + var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); + var mtr = f(new fabric.Point(_mt.x, _mt.y)); + + // padding + var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), + padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); // debugging @@ -389,22 +390,6 @@ mtr: mtr }; - var tform; - if (typeof this.canvas == 'undefined') { - if (this.type == 'group') { - tform = this._objects[0].canvas.viewportTransform; - } - else { - tform = [1, 0, 0, 1, 0, 0]; - } - } - else { - tform = this.canvas.viewportTransform; - } - for (c in this.oCoords) { - this.oCoords[c] = fabric.util.transformPoint(this.oCoords[c], tform); - } - // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index 458e91b5..ba5e7e5f 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -15,9 +15,9 @@ _findTargetCorner: function(e, offset) { if (!this.hasControls || !this.active) return false; - var pointer = getPointer(e, this.canvas.upperCanvasEl), - ex = pointer.x - offset.left, - ey = pointer.y - offset.top, + var pointer = this.canvas.getPointer(e, true), + ex = pointer.x, + ey = pointer.y, xPoints, lines; @@ -267,15 +267,16 @@ ctx.lineWidth = 1 / this.borderScaleFactor; - var wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), - sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), this.canvas.viewportTransform, true), + var vpt = this.canvas.viewportTransform, + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), + sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, h = wh.y, sx= sxy.x, sy= sxy.y; - if (this.get('group')) { - w = w * this.get('group').scaleX; - h = h * this.get('group').scaleY; + if (this.group) { + w = w * this.group.scaleX; + h = h * this.group.scaleY; } ctx.strokeRect( diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 89c0fd90..4de2cf60 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -53,6 +53,7 @@ this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; + this._objects[i].setCoords(); } this.originalState = { }; @@ -216,13 +217,7 @@ if (!this.visible) return; ctx.save(); - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; var sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), @@ -403,10 +398,9 @@ _calcBounds: function() { var aX = [], aY = [], - minX, minY, maxX, maxY, o, width, height, minXY, maxXY, ivt, // TODO: cleanup + minX, minY, maxX, maxY, o, width, height, minXY, maxXY, i = 0, - len = this._objects.length, - canvas = this._objects[0].canvas; + len = this._objects.length; for (; i < len; ++i) { o = this._objects[i]; @@ -416,20 +410,18 @@ aY.push(o.oCoords[prop].y); } } + + var ivt = fabric.util.invertTransform(canvas.viewportTransform); minXY = new fabric.Point(min(aX), min(aY)); maxXY = new fabric.Point(max(aX), max(aY)); - // TODO: cleanup - ivt = fabric.util.invertTransform(canvas.viewportTransform); - this.width = (maxXY.x - minXY.x) || 0; - this.height = (maxXY.y - minXY.y) || 0; - // TODO: cleanup minXY = fabric.util.transformPoint(minXY, ivt); maxXY = fabric.util.transformPoint(maxXY, ivt); + this.width = (maxXY.x - minXY.x) || 0; this.height = (maxXY.y - minXY.y) || 0; - + this.left = (minXY.x + maxXY.x) / 2 || 0; this.top = (minXY.y + maxXY.y) / 2 || 0; }, diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 764c72cb..dc6129f3 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -730,6 +730,9 @@ * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node */ transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } ctx.globalAlpha = this.opacity; var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); @@ -1027,9 +1030,6 @@ } if (!noTransform) { - if (this.group) { - this.group.transform(ctx); - } this.transform(ctx); } diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index a7011f98..9e22540b 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -146,13 +146,6 @@ */ viewportTransform: [1, 0, 0, 1, 0, 0], - /** - * Color of canvas border - * @type String - * @default - */ - canvasBorderColor: '', - /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -474,6 +467,10 @@ // TODO: just change the scale, preserve other transformations this.viewportTransform[0] = value; this.viewportTransform[3] = value; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } return this; }, @@ -491,6 +488,7 @@ ); this.viewportTransform[4] = x - wh.x/2; this.viewportTransform[5] = y - wh.y/2; + this.renderAll(); return this; }, @@ -554,8 +552,14 @@ */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); - obj.setCoords(); obj.canvas = this; + obj.setCoords(); + if (obj._objects) { + for (var i = 0, len = obj._objects.length; i < len; i++) { + obj._objects[i].canvas = this; + obj._objects[i].setCoords(); + } + } this.fire('object:added', { target: obj }); obj.fire('added'); }, @@ -657,10 +661,6 @@ if (typeof this.backgroundImage === 'object') { this._drawBackroundImage(canvasToDrawOn); } - - if (this.canvasBorderColor) { - this._drawCanvasBorder(canvasToDrawOn); - } var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { @@ -717,23 +717,6 @@ canvasToDrawOn.restore(); }, - /** - * @private - * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on - */ - _drawCanvasBorder: function(canvasToDrawOn) { - var xy = fabric.util.transformPoint(new fabric.Point(0, 0), this.viewportTransform), - wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform, true - ); - canvasToDrawOn.save(); - canvasToDrawOn.lineWidth = 1; - canvasToDrawOn.strokeStyle = this.canvasBorderColor; - canvasToDrawOn.strokeRect(xy.x - 1.5, xy.y - 1.5, wh.x + 2, wh.y + 2); - canvasToDrawOn.restore(); - }, - /** * Method to render only the top canvas. * Also used to render the group selection box. From 08d575422c12b4da14ad5fc3f4ba45b440a49af6 Mon Sep 17 00:00:00 2001 From: Tom French Date: Tue, 19 Nov 2013 16:21:29 +0000 Subject: [PATCH 03/27] Fixes to zoom for groups and brushes. --- dist/all.js | 173 ++++++++++---------- dist/all.require.js | 192 ++++++++++------------- src/brushes/circle_brush.class.js | 10 +- src/brushes/pencil_brush.class.js | 8 +- src/brushes/spray_brush.class.js | 9 +- src/canvas.class.js | 5 +- src/mixins/canvas_events.mixin.js | 8 +- src/mixins/object_interactivity.mixin.js | 4 +- src/shapes/group.class.js | 35 +++-- src/shapes/image.class.js | 20 +-- src/shapes/object.class.js | 16 ++ src/shapes/path.class.js | 11 +- src/shapes/path_group.class.js | 11 +- src/shapes/text.class.js | 12 +- src/static_canvas.class.js | 24 ++- 15 files changed, 255 insertions(+), 283 deletions(-) diff --git a/dist/all.js b/dist/all.js index 3179ebb1..397041a6 100644 --- a/dist/all.js +++ b/dist/all.js @@ -8976,6 +8976,21 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); }, + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + this.viewportTransform = vpt + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out @@ -9008,6 +9023,9 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this.viewportTransform[4] = x - wh.x/2; this.viewportTransform[5] = y - wh.y/2; this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } return this; }, @@ -9072,13 +9090,15 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; - obj.setCoords(); if (obj._objects) { + obj._calcBounds(); for (var i = 0, len = obj._objects.length; i < len; i++) { obj._objects[i].canvas = this; - obj._objects[i].setCoords(); + this._onObjectAdded(obj._objects[i]); } + obj._updateObjectsCoords() } + obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); }, @@ -9949,6 +9969,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _render: function() { var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); var p1 = this._points[0]; @@ -9978,6 +10001,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); + ctx.restore(); }, /** @@ -9986,10 +10010,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @private */ _getSVGPathData: function() { - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); - for (var i = 0, len = this._points.length; i < len; i++) { - this._points[i] = fabric.util.transformPoint(this._points[i], ivt); - } this.box = this.getPathBoundingBox(this._points); return this.convertPointsToSVGPath( this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); @@ -10151,11 +10171,17 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var point = this.addPoint(pointer); var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); + + ctx.restore(); }, /** @@ -10188,10 +10214,10 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric for (var i = 0, len = this.points.length; i < len; i++) { var point = this.points[i]; var circle = new fabric.Circle({ - radius: point.radius, + radius: this.points[i].radius, left: point.x, top: point.y, - fill: point.fill + fill: this.points[i].fill }); this.shadow && circle.setShadow(this.shadow); @@ -10382,14 +10408,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric render: function() { var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; + + var v = this.canvas.viewportTransform; ctx.save(); - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; - var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); - point.x = tpoint.x; - point.y = tpoint.y; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } @@ -10405,7 +10430,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; - var vpt = this.canvas.viewportTransform; for (var i = 0; i < this.density; i++) { @@ -10423,7 +10447,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var point = new fabric.Point(x, y); - point = fabric.util.transformPoint(point, vpt); point.width = width if (this.randomOpacity) { @@ -11001,7 +11024,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); var group = new fabric.Group( isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); - group.canvas = this; this.setActiveGroup(group); this._activeObject = null; @@ -11281,7 +11303,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else if (group.length > 1) { group = new fabric.Group(group.reverse()); - group.canvas = this; this.setActiveGroup(group); group.saveCoords(); this.fire('selection:created', { target: group }); @@ -11516,6 +11537,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this._activeGroup = group; if (group) { group.canvas = this; + group._calcBounds(); + group._updateObjectsCoords(); + group.setCoords(); group.set('active', true); } return this; @@ -11860,7 +11884,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); + var ivt = fabric.util.invertTransform(this.viewportTransform); + var pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseDown(pointer); this.fire('mouse:down', { e: e }); }, @@ -11987,7 +12013,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.isDrawingMode) { if (this._isCurrentlyDrawing) { - this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); + var ivt = fabric.util.invertTransform(this.viewportTransform); + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; this.fire('mouse:move', { e: e }); @@ -13723,7 +13751,23 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); + + this._renderControls(ctx, noTransform); + }, + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } ctx.save(); if (this.active && !noTransform) { var center; @@ -15141,8 +15185,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot sx= sxy.x, sy= sxy.y; if (this.group) { - w = w * this.group.scaleX; - h = h * this.group.scaleY; + w = w * this.group.scaleX; + h = h * this.group.scaleY; } ctx.strokeRect( @@ -17388,16 +17432,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._removeShadow(ctx); ctx.restore(); - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -17752,16 +17787,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._removeShadow(ctx); ctx.restore(); - ctx.save(); - if (this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx); }, /** @@ -17964,25 +17990,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot initialize: function(objects, options) { options = options || { }; + // NOTE: all the coords calculations need to have a canvas before they make sense this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; - this._objects[i].setCoords(); + //this._objects[i].setCoords(); } this.originalState = { }; this.callSuper('initialize'); - this._calcBounds(); - this._updateObjectsCoords(); + //this._calcBounds(); + //this._updateObjectsCoords(); if (options) { extend(this, options); } this._setOpacityIfSame(); - this.setCoords(true); - this.saveCoords(); + //this.setCoords(true); + //this.saveCoords(); }, /** @@ -18130,12 +18157,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot // do not render if object is not visible if (!this.visible) return; - ctx.save(); var v = this.canvas.viewportTransform; - + ctx.save(); var sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), - v, true), + v, + true + ), groupScaleFactor = Math.max(sxy.x, sxy.y); this.clipTo && fabric.util.clipContext(this, ctx); @@ -18157,13 +18185,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.clipTo && ctx.restore(); - if (this.active && !noTransform) { - var center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } + this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, @@ -18325,7 +18347,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - var ivt = fabric.util.invertTransform(canvas.viewportTransform); + var ivt; + if (this.canvas) { + ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + } + else { // BUG: this always happens when new groups are created + ivt = [1, 0, 0, 1, 0, 0]; + console.log('no canvas'); + } minXY = new fabric.Point(min(aX), min(aY)); maxXY = new fabric.Point(max(aX), max(aY)); @@ -18555,25 +18584,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.restore(); ctx.restore(); - ctx.save(); - if (this.active && !noTransform) { - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); - } - center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -20842,16 +20853,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); ctx.restore(); - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + + this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/dist/all.require.js b/dist/all.require.js index d832fe79..deb5fad2 100644 --- a/dist/all.require.js +++ b/dist/all.require.js @@ -8665,13 +8665,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ viewportTransform: [1, 0, 0, 1, 0, 0], - /** - * Color of canvas border - * @type String - * @default - */ - canvasBorderColor: '', - /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -8993,6 +8986,10 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ // TODO: just change the scale, preserve other transformations this.viewportTransform[0] = value; this.viewportTransform[3] = value; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } return this; }, @@ -9010,6 +9007,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ ); this.viewportTransform[4] = x - wh.x/2; this.viewportTransform[5] = y - wh.y/2; + this.renderAll(); return this; }, @@ -9076,7 +9074,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ obj.canvas = this; obj.setCoords(); if (obj._objects) { - for (var i = 0, len = obj._objects; i < len; i++) { + for (var i = 0, len = obj._objects.length; i < len; i++) { obj._objects[i].canvas = this; obj._objects[i].setCoords(); } @@ -9182,10 +9180,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ if (typeof this.backgroundImage === 'object') { this._drawBackroundImage(canvasToDrawOn); } - - if (this.canvasBorderColor) { - this._drawCanvasBorder(canvasToDrawOn); - } var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { @@ -9242,23 +9236,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ canvasToDrawOn.restore(); }, - /** - * @private - * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on - */ - _drawCanvasBorder: function(canvasToDrawOn) { - var xy = fabric.util.transformPoint(new fabric.Point(0, 0), this.viewportTransform), - wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform, true - ); - canvasToDrawOn.save(); - canvasToDrawOn.lineWidth = 1; - canvasToDrawOn.strokeStyle = this.canvasBorderColor; - canvasToDrawOn.strokeRect(xy.x - 1.5, xy.y - 1.5, wh.x + 2, wh.y + 2); - canvasToDrawOn.restore(); - }, - /** * Method to render only the top canvas. * Also used to render the group selection box. @@ -10369,6 +10346,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(rects); + group.canvas = this.canvas; + this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -10404,9 +10383,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; ctx.save(); + var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; + var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); + point.x = tpoint.x; + point.y = tpoint.y; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } @@ -10439,7 +10422,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric width = this.dotWidth; } - var point = fabric.point(x, y); + var point = new fabric.Point(x, y); point = fabric.util.transformPoint(point, vpt); point.width = width @@ -10899,7 +10882,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var action = 'drag', corner, - pointer = getPointer(e, target.canvas.UpperCanvasEl); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -12040,7 +12026,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } else { // object is being transformed (scaled/rotated/moved/etc.) - pointer = this.getPointer(e); + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ); var x = pointer.x, y = pointer.y, @@ -13404,6 +13393,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node */ transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } ctx.globalAlpha = this.opacity; var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); @@ -13701,9 +13693,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati } if (!noTransform) { - if (this.group) { - this.group.transform(ctx); - } this.transform(ctx); } @@ -14735,10 +14724,21 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, - theta = degreesToRadians(this.angle); + theta = degreesToRadians(this.angle), + vpt; + if (this.canvas) { + vpt = this.canvas.viewportTransform; + } + if (!vpt) { // TODO + vpt = [1, 0, 0, 1, 0, 0]; + }; - this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; - this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; + var f = function (p) { + return fabric.util.transformPoint(p, vpt); + } + + this.currentWidth = (this.width + strokeWidth) * this.scaleX; + this.currentHeight = (this.height + strokeWidth) * this.scaleY; // If width is negative, make postive. Fixes path selection issue if (this.currentWidth < 0) { @@ -14758,42 +14758,32 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(this.currentWidth, this.currentHeight); - var tl = { - x: coords.x - offsetX, - y: coords.y - offsetY - }; - var tr = { - x: tl.x + (wh.x * cosTh), - y: tl.y + (wh.x * sinTh) - }; - var br = { - x: tr.x - (wh.y * sinTh), - y: tr.y + (wh.y * cosTh) - }; - var bl = { - x: tl.x - (wh.y * sinTh), - y: tl.y + (wh.y * cosTh) - }; - var ml = { - x: tl.x - (wh.y/2 * sinTh), - y: tl.y + (wh.y/2 * cosTh) - }; - var mt = { - x: tl.x + (wh.x/2 * cosTh), - y: tl.y + (wh.x/2 * sinTh) - }; - var mr = { - x: tr.x - (wh.y/2 * sinTh), - y: tr.y + (wh.y/2 * cosTh) - }; - var mb = { - x: bl.x + (wh.x/2 * cosTh), - y: bl.y + (wh.x/2 * sinTh) - }; - var mtr = { - x: mt.x, - y: mt.y - }; + var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); + var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); + var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); + var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); + var tl = f(_tl); + var tr = f(_tr); + var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); + var bl = f(_bl); + var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); + var mt = f(_mt); + var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); + var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); + var mtr = f(new fabric.Point(_mt.x, _mt.y)); + + // padding + var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), + padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); // debugging @@ -14818,17 +14808,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati mtr: mtr }; - var vpt; - if (this.canvas) { - vpt = this.canvas.viewportTransform; - } - if (!vpt) { // TODO - vpt = [1, 0, 0, 1, 0, 0]; - } - for (c in this.oCoords) { - this.oCoords[c] = fabric.util.transformPoint(this.oCoords[c], vpt); - } - // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); @@ -14902,9 +14881,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _findTargetCorner: function(e, offset) { if (!this.hasControls || !this.active) return false; - var pointer = getPointer(e, this.canvas.upperCanvasEl), - ex = pointer.x - offset.left, - ey = pointer.y - offset.top, + var pointer = this.canvas.getPointer(e, true), + ex = pointer.x, + ey = pointer.y, xPoints, lines; @@ -15154,22 +15133,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.lineWidth = 1 / this.borderScaleFactor; - var vpt = this.canvas.viewportTransform; - // debugging - if (!vpt) { - vpt = [1, 0, 0, 1, 0, 0] - console.log("No vpt! interactivity", this.canvas, this.get('canvas'), this); - } - - var wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), + var vpt = this.canvas.viewportTransform, + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, h = wh.y, sx= sxy.x, sy= sxy.y; - if (this.get('group')) { - w = w * this.get('group').scaleX; - h = h * this.get('group').scaleY; + if (this.group) { + w = w * this.group.scaleX; + h = h * this.group.scaleY; } ctx.strokeRect( @@ -17994,6 +17967,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; + this._objects[i].setCoords(); } this.originalState = { }; @@ -18338,16 +18312,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _calcBounds: function() { var aX = [], aY = [], - minX, minY, maxX, maxY, o, width, height, minXY, maxXY, ivt, // TODO: cleanup + minX, minY, maxX, maxY, o, width, height, minXY, maxXY, i = 0, - len = this._objects.length, - vpt; - if (this.canvas) { - vpt = this.canvas.viewportTransform; - } - if (!vpt) { // TODO: this always happens when new groups are created - vpt = [1, 0, 0, 1, 0, 0]; - } + len = this._objects.length; for (; i < len; ++i) { o = this._objects[i]; @@ -18357,19 +18324,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot aY.push(o.oCoords[prop].y); } } + + var ivt = fabric.util.invertTransform(canvas.viewportTransform) || [1, 0, 0, 1, 0, 0]; minXY = new fabric.Point(min(aX), min(aY)); maxXY = new fabric.Point(max(aX), max(aY)); - ivt = fabric.util.invertTransform(vpt); - this.width = (maxXY.x - minXY.x) || 0; - this.height = (maxXY.y - minXY.y) || 0; - minXY = fabric.util.transformPoint(minXY, ivt); maxXY = fabric.util.transformPoint(maxXY, ivt); + this.width = (maxXY.x - minXY.x) || 0; this.height = (maxXY.y - minXY.y) || 0; - + this.left = (minXY.x + maxXY.x) / 2 || 0; this.top = (minXY.y + maxXY.y) / 2 || 0; }, diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index f790b6d1..39794345 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -28,11 +28,17 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var point = this.addPoint(pointer); var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); + + ctx.restore(); }, /** @@ -65,10 +71,10 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric for (var i = 0, len = this.points.length; i < len; i++) { var point = this.points[i]; var circle = new fabric.Circle({ - radius: point.radius, + radius: this.points[i].radius, left: point.x, top: point.y, - fill: point.fill + fill: this.points[i].fill }); this.shadow && circle.setShadow(this.shadow); diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index 464c5c4a..2cf8d156 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -104,6 +104,9 @@ */ _render: function() { var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); var p1 = this._points[0]; @@ -133,6 +136,7 @@ // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); + ctx.restore(); }, /** @@ -141,10 +145,6 @@ * @private */ _getSVGPathData: function() { - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); - for (var i = 0, len = this._points.length; i < len; i++) { - this._points[i] = fabric.util.transformPoint(this._points[i], ivt); - } this.box = this.getPathBoundingBox(this._points); return this.convertPointsToSVGPath( this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 4ff8fd9a..80b0466e 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -146,14 +146,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric render: function() { var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; + + var v = this.canvas.viewportTransform; ctx.save(); - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; - var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); - point.x = tpoint.x; - point.y = tpoint.y; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } @@ -169,7 +168,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; - var vpt = this.canvas.viewportTransform; for (var i = 0; i < this.density; i++) { @@ -187,7 +185,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var point = new fabric.Point(x, y); - point = fabric.util.transformPoint(point, vpt); point.width = width if (this.randomOpacity) { diff --git a/src/canvas.class.js b/src/canvas.class.js index 04990ac8..baa12014 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -504,7 +504,6 @@ var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); var group = new fabric.Group( isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); - group.canvas = this; this.setActiveGroup(group); this._activeObject = null; @@ -784,7 +783,6 @@ } else if (group.length > 1) { group = new fabric.Group(group.reverse()); - group.canvas = this; this.setActiveGroup(group); group.saveCoords(); this.fire('selection:created', { target: group }); @@ -1019,6 +1017,9 @@ this._activeGroup = group; if (group) { group.canvas = this; + group._calcBounds(); + group._updateObjectsCoords(); + group.setCoords(); group.set('active', true); } return this; diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index 077fab1d..67d8babe 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -235,7 +235,9 @@ if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); + var ivt = fabric.util.invertTransform(this.viewportTransform); + var pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseDown(pointer); this.fire('mouse:down', { e: e }); }, @@ -362,7 +364,9 @@ if (this.isDrawingMode) { if (this._isCurrentlyDrawing) { - this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); + var ivt = fabric.util.invertTransform(this.viewportTransform); + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; this.fire('mouse:move', { e: e }); diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index ba5e7e5f..c5214f74 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -275,8 +275,8 @@ sx= sxy.x, sy= sxy.y; if (this.group) { - w = w * this.group.scaleX; - h = h * this.group.scaleY; + w = w * this.group.scaleX; + h = h * this.group.scaleY; } ctx.strokeRect( diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 4de2cf60..c324a833 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -50,25 +50,26 @@ initialize: function(objects, options) { options = options || { }; + // NOTE: all the coords calculations need to have a canvas before they make sense this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; - this._objects[i].setCoords(); + //this._objects[i].setCoords(); } this.originalState = { }; this.callSuper('initialize'); - this._calcBounds(); - this._updateObjectsCoords(); + //this._calcBounds(); + //this._updateObjectsCoords(); if (options) { extend(this, options); } this._setOpacityIfSame(); - this.setCoords(true); - this.saveCoords(); + //this.setCoords(true); + //this.saveCoords(); }, /** @@ -216,12 +217,13 @@ // do not render if object is not visible if (!this.visible) return; - ctx.save(); var v = this.canvas.viewportTransform; - + ctx.save(); var sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), - v, true), + v, + true + ), groupScaleFactor = Math.max(sxy.x, sxy.y); this.clipTo && fabric.util.clipContext(this, ctx); @@ -243,13 +245,7 @@ } this.clipTo && ctx.restore(); - if (this.active && !noTransform) { - var center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } + this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, @@ -411,7 +407,14 @@ } } - var ivt = fabric.util.invertTransform(canvas.viewportTransform); + var ivt; + if (this.canvas) { + ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + } + else { // BUG: this always happens when new groups are created + ivt = [1, 0, 0, 1, 0, 0]; + console.log('no canvas'); + } minXY = new fabric.Point(min(aX), min(aY)); maxXY = new fabric.Point(max(aX), max(aY)); diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index e16af047..8a25ccb5 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -135,25 +135,7 @@ ctx.restore(); ctx.restore(); - ctx.save(); - if (this.active && !noTransform) { - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); - } - center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index dc6129f3..4275bf36 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1060,7 +1060,23 @@ this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); + + this._renderControls(ctx, noTransform); + }, + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + var v; + if (this.canvas) { + v = this.canvas.viewportTransform; + } + else { + v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution + } ctx.save(); if (this.active && !noTransform) { var center; diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index 98be5a88..6fd33c85 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -476,16 +476,7 @@ this._removeShadow(ctx); ctx.restore(); - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/shapes/path_group.class.js b/src/shapes/path_group.class.js index 18e528a3..54431d06 100644 --- a/src/shapes/path_group.class.js +++ b/src/shapes/path_group.class.js @@ -93,16 +93,7 @@ this._removeShadow(ctx); ctx.restore(); - ctx.save(); - if (this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + this.callSuper('_renderControls', ctx); }, /** diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 3a03872d..ad737d7f 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -767,16 +767,8 @@ ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); ctx.restore(); - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + + this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 9e22540b..a14794eb 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -457,6 +457,21 @@ return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); }, + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + this.viewportTransform = vpt + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out @@ -489,6 +504,9 @@ this.viewportTransform[4] = x - wh.x/2; this.viewportTransform[5] = y - wh.y/2; this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } return this; }, @@ -553,13 +571,15 @@ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; - obj.setCoords(); if (obj._objects) { + obj._calcBounds(); for (var i = 0, len = obj._objects.length; i < len; i++) { obj._objects[i].canvas = this; - obj._objects[i].setCoords(); + this._onObjectAdded(obj._objects[i]); } + obj._updateObjectsCoords() } + obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); }, From 3cc191bdfb9ed158621cda025a7f3d79d155a3a1 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 6 Dec 2013 13:40:17 +0000 Subject: [PATCH 04/27] Fixed control and group selection rendering --- dist/all.js | 80 +- dist/all.require.js | 7545 +++++++++++++++------------ src/mixins/canvas_events.mixin.js | 4 +- src/mixins/object_geometry.mixin.js | 8 +- src/shapes/group.class.js | 17 +- src/shapes/image.class.js | 8 +- src/shapes/object.class.js | 17 +- src/shapes/path.class.js | 8 +- src/shapes/path_group.class.js | 9 +- src/shapes/text.class.js | 9 +- 10 files changed, 4114 insertions(+), 3591 deletions(-) diff --git a/dist/all.js b/dist/all.js index 846ff891..962a3e37 100644 --- a/dist/all.js +++ b/dist/all.js @@ -12485,8 +12485,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (groupSelector) { pointer = this.getPointer(e, true); - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } @@ -14365,13 +14365,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -14409,13 +14403,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; + ctx.save(); if (this.active && !noTransform) { var center; @@ -15365,12 +15354,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati padding = this.padding, theta = degreesToRadians(this.angle), vpt; - if (this.canvas) { - vpt = this.canvas.viewportTransform; - } - if (!vpt) { // TODO - vpt = [1, 0, 0, 1, 0, 0]; - }; + // TODO: ideally we should never setCoords an object which lacks a canvas + vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -18245,13 +18230,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18592,7 +18571,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.setOptions(options); - this.setCoords(); if (options.sourcePath) { this.setSourcePath(options.sourcePath); @@ -18611,13 +18589,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18839,26 +18811,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot initialize: function(objects, options) { options = options || { }; - // NOTE: all the coords calculations need to have a canvas before they make sense this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; - //this._objects[i].setCoords(); } this.originalState = { }; this.callSuper('initialize'); - //this._calcBounds(); - //this._updateObjectsCoords(); - if (options) { extend(this, options); } this._setOpacityIfSame(); - - //this.setCoords(true); - //this.saveCoords(); }, /** @@ -19035,19 +18999,16 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot true ); - var originalScaleFactor = object.borderScaleFactor, - originalHasRotatingPoint = object.hasRotatingPoint, + var originalHasRotatingPoint = object.hasRotatingPoint, groupScaleFactor = Math.max(sxy.x, sxy.y); // do not render if object is not visible if (!object.visible) return; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; }, @@ -19247,10 +19208,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (this.canvas) { ivt = fabric.util.invertTransform(this.canvas.viewportTransform); } - else { // BUG: this always happens when new groups are created - ivt = [1, 0, 0, 1, 0, 0]; - console.log('no canvas'); - } var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); @@ -19466,12 +19423,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + v = this.canvas.viewportTransform; + var isInPathGroup = this.group && this.group.type === 'path-group'; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -21399,7 +21352,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag this.setOptions(options); this.__skipDimension = false; this._initDimensions(); - this.setCoords(); }, /** @@ -21847,13 +21799,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); ctx.restore(); diff --git a/dist/all.require.js b/dist/all.require.js index 054341fb..b6812cd8 100644 --- a/dist/all.require.js +++ b/dist/all.require.js @@ -1,8 +1,4 @@ -<<<<<<< HEAD -/* build: `node build.js modules=ALL` */ -======= -/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */ ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f +/* build: `node build.js modules=ALL minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: "1.4.0" }; @@ -50,6 +46,3557 @@ fabric.SHARED_ATTRIBUTES = [ ]; +/*! + * Copyright (c) 2009 Simo Kinnunen. + * Licensed under the MIT license. + */ + +var Cufon = (function() { + + /** @ignore */ + var api = function() { + return api.replace.apply(null, arguments); + }; + + /** @ignore */ + var DOM = api.DOM = { + + ready: (function() { + + var complete = false, readyStatus = { loaded: 1, complete: 1 }; + + var queue = [], /** @ignore */ perform = function() { + if (complete) return; + complete = true; + for (var fn; fn = queue.shift(); fn()); + }; + + // Gecko, Opera, WebKit r26101+ + + if (fabric.document.addEventListener) { + fabric.document.addEventListener('DOMContentLoaded', perform, false); + fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages + } + + // Old WebKit, Internet Explorer + + if (!fabric.window.opera && fabric.document.readyState) (function() { + readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10); + })(); + + // Internet Explorer + + if (fabric.document.readyState && fabric.document.createStyleSheet) (function() { + try { + fabric.document.body.doScroll('left'); + perform(); + } + catch (e) { + setTimeout(arguments.callee, 1); + } + })(); + + addEvent(fabric.window, 'load', perform); // Fallback + + return function(listener) { + if (!arguments.length) perform(); + else complete ? listener() : queue.push(listener); + }; + + })() + + }; + + /** @ignore */ + var CSS = api.CSS = /** @ignore */ { + + /** @ignore */ + Size: function(value, base) { + + this.value = parseFloat(value); + this.unit = String(value).match(/[a-z%]*$/)[0] || 'px'; + + /** @ignore */ + this.convert = function(value) { + return value / base * this.value; + }; + + /** @ignore */ + this.convertFrom = function(value) { + return value / this.value * base; + }; + + /** @ignore */ + this.toString = function() { + return this.value + this.unit; + }; + + }, + + /** @ignore */ + getStyle: function(el) { + return new Style(el.style); + /* + var view = document.defaultView; + if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null)); + if (el.currentStyle) return new Style(el.currentStyle); + return new Style(el.style); + */ + }, + + quotedList: cached(function(value) { + // doesn't work properly with empty quoted strings (""), but + // it's not worth the extra code. + var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match; + while (match = re.exec(value)) list.push(match[3] || match[1]); + return list; + }), + + ready: (function() { + + var complete = false; + + var queue = [], perform = function() { + complete = true; + for (var fn; fn = queue.shift(); fn()); + }; + + // Safari 2 does not include '); + + function getFontSizeInPixels(el, value) { + return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value); + } + + // Original by Dead Edwards. + // Combined with getFontSizeInPixels it also works with relative units. + function getSizeInPixels(el, value) { + if (/px$/i.test(value)) return parseFloat(value); + var style = el.style.left, runtimeStyle = el.runtimeStyle.left; + el.runtimeStyle.left = el.currentStyle.left; + el.style.left = value; + var result = el.style.pixelLeft; + el.style.left = style; + el.runtimeStyle.left = runtimeStyle; + return result; + } + + return function(font, text, style, options, node, el, hasNext) { + var redraw = (text === null); + + if (redraw) text = node.alt; + + // @todo word-spacing, text-decoration + + var viewBox = font.viewBox; + + var size = style.computedFontSize || + (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize)); + + var letterSpacing = style.computedLSpacing; + + if (letterSpacing == undefined) { + letterSpacing = style.get('letterSpacing'); + style.computedLSpacing = letterSpacing = + (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing)); + } + + var wrapper, canvas; + + if (redraw) { + wrapper = node; + canvas = node.firstChild; + } + else { + wrapper = fabric.document.createElement('span'); + wrapper.className = 'cufon cufon-vml'; + wrapper.alt = text; + + canvas = fabric.document.createElement('span'); + canvas.className = 'cufon-vml-canvas'; + wrapper.appendChild(canvas); + + if (options.printable) { + var print = fabric.document.createElement('span'); + print.className = 'cufon-alt'; + print.appendChild(fabric.document.createTextNode(text)); + wrapper.appendChild(print); + } + + // ie6, for some reason, has trouble rendering the last VML element in the document. + // we can work around this by injecting a dummy element where needed. + // @todo find a better solution + if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape')); + } + + var wStyle = wrapper.style; + var cStyle = canvas.style; + + var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height); + var roundingFactor = roundedHeight / height; + var minX = viewBox.minX, minY = viewBox.minY; + + cStyle.height = roundedHeight; + cStyle.top = Math.round(size.convert(minY - font.ascent)); + cStyle.left = Math.round(size.convert(minX)); + + wStyle.height = size.convert(font.height) + 'px'; + + var textDecoration = Cufon.getTextDecoration(options); + + var color = style.get('color'); + + var chars = Cufon.CSS.textTransform(text, style).split(''); + + var width = 0, offsetX = 0, advance = null; + + var glyph, shape, shadows = options.textShadow; + + // pre-calculate width + for (var i = 0, k = 0, l = chars.length; i < l; ++i) { + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing; + } + + if (advance === null) return null; + + var fullWidth = -minX + width + (viewBox.width - advance); + + var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth); + + var coordSize = fullWidth + ',' + viewBox.height, coordOrigin; + var stretch = 'r' + coordSize + 'nsnf'; + + for (i = 0; i < l; ++i) { + + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + + if (redraw) { + // some glyphs may be missing so we can't use i + shape = canvas.childNodes[k]; + if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow + } + else { + shape = fabric.document.createElement('cvml:shape'); + canvas.appendChild(shape); + } + + shape.stroked = 'f'; + shape.coordsize = coordSize; + shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY; + shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch; + shape.fillcolor = color; + + // it's important to not set top/left or IE8 will grind to a halt + var sStyle = shape.style; + sStyle.width = roundedShapeWidth; + sStyle.height = roundedHeight; + + if (shadows) { + // due to the limitations of the VML shadow element there + // can only be two visible shadows. opacity is shared + // for all shadows. + var shadow1 = shadows[0], shadow2 = shadows[1]; + var color1 = Cufon.CSS.color(shadow1.color), color2; + var shadow = fabric.document.createElement('cvml:shadow'); + shadow.on = 't'; + shadow.color = color1.color; + shadow.offset = shadow1.offX + ',' + shadow1.offY; + if (shadow2) { + color2 = Cufon.CSS.color(shadow2.color); + shadow.type = 'double'; + shadow.color2 = color2.color; + shadow.offset2 = shadow2.offX + ',' + shadow2.offY; + } + shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1; + shape.appendChild(shadow); + } + + offsetX += ~~(glyph.w || font.w) + letterSpacing; + + ++k; + + } + + wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0); + + return wrapper; + + }; + +})()); + +Cufon.getTextDecoration = function(options) { + return { + underline: options.textDecoration === 'underline', + overline: options.textDecoration === 'overline', + 'line-through': options.textDecoration === 'line-through' + }; +}; + +if (typeof exports != 'undefined') { + exports.Cufon = Cufon; +} + + +/* + json2.js + 2011-10-19 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + /** @ignore */ + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + /** @ignore */ + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + /** @ignore */ + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + /** @ignore */ + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); + +/* + ---------------------------------------------------- + Event.js : 1.1.3 : 2013/07/17 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- + https://github.com/rykerwilliams/Event.js + ---------------------------------------------------- + 1 : click, dblclick, dbltap + 1+ : tap, longpress, drag, swipe + 2+ : pinch, rotate + : mousewheel, devicemotion, shake + ---------------------------------------------------- + Ideas for the future + ---------------------------------------------------- + * GamePad, and other input abstractions. + * Event batching - i.e. for every x fingers down a new gesture is created. + ---------------------------------------------------- + http://www.w3.org/TR/2011/WD-touch-events-20110505/ + ---------------------------------------------------- + +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(eventjs) === "undefined") var eventjs = Event; + +(function(root) { "use strict"; + +// Add custom *EventListener commands to HTMLElements (set false to prevent funkiness). +root.modifyEventListener = true; + +// Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness). +root.modifySelectors = true; + +// Event maintenance. +root.add = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "add"); +}; + +root.remove = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "remove"); +}; + +root.stop = function(event) { + if (!event) return; + if (event.stopPropagation) event.stopPropagation(); + event.cancelBubble = true; // <= IE8 + event.bubble = 0; +}; + +root.prevent = function(event) { + if (!event) return; + if (event.preventDefault) event.preventDefault(); + if (event.preventManipulation) event.preventManipulation(); // MS + event.returnValue = false; // <= IE8 +}; + +root.cancel = function(event) { + root.stop(event); + root.prevent(event); +}; + +// Check whether event is natively supported (via @kangax) +root.getEventSupport = function (target, type) { + if (typeof(target) === "string") { + type = target; + target = window; + } + type = "on" + type; + if (type in target) return true; + if (!target.setAttribute) target = document.createElement("div"); + if (target.setAttribute && target.removeAttribute) { + target.setAttribute(type, ""); + var isSupported = typeof target[type] === "function"; + if (typeof target[type] !== "undefined") target[type] = null; + target.removeAttribute(type); + return isSupported; + } +}; + +var clone = function (obj) { + if (!obj || typeof (obj) !== 'object') return obj; + var temp = new obj.constructor(); + for (var key in obj) { + if (!obj[key] || typeof (obj[key]) !== 'object') { + temp[key] = obj[key]; + } else { // clone sub-object + temp[key] = clone(obj[key]); + } + } + return temp; +}; + +/// Handle custom *EventListener commands. +var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { + configure = configure || {}; + // Check whether target is a configuration variable; + if (String(target) === "[object Object]") { + var data = target; + target = data.target; + type = data.type; + listener = data.listener; + delete data.target; + delete data.type; + delete data.listener; + for (var key in data) { + configure[key] = data[key]; + } + } + /// + if (!target || !type || !listener) return; + // Check for element to load on interval (before onload). + if (typeof(target) === "string" && type === "ready") { + var time = (new Date()).getTime(); + var timeout = configure.timeout; + var ms = configure.interval || 1000 / 60; + var interval = window.setInterval(function() { + if ((new Date()).getTime() - time > timeout) { + window.clearInterval(interval); + } + if (document.querySelector(target)) { + window.clearInterval(interval); + setTimeout(listener, 1); + } + }, ms); + return; + } + // Get DOM element from Query Selector. + if (typeof(target) === "string") { + target = document.querySelectorAll(target); + if (target.length === 0) return createError("Missing target on listener!", arguments); // No results. + if (target.length === 1) { // Single target. + target = target[0]; + } + } + + /// Handle multiple targets. + var event; + var events = {}; + if (target.length > 0 && target !== window) { + for (var n0 = 0, length0 = target.length; n0 < length0; n0 ++) { + event = eventManager(target[n0], type, listener, clone(configure), trigger); + if (event) events[n0] = event; + } + return createBatchCommands(events); + } + // Check for multiple events in one string. + if (type.indexOf && type.indexOf(" ") !== -1) type = type.split(" "); + if (type.indexOf && type.indexOf(",") !== -1) type = type.split(","); + // Attach or remove multiple events associated with a target. + if (typeof(type) !== "string") { // Has multiple events. + if (typeof(type.length) === "number") { // Handle multiple listeners glued together. + for (var n1 = 0, length1 = type.length; n1 < length1; n1 ++) { // Array [type] + event = eventManager(target, type[n1], listener, clone(configure), trigger); + if (event) events[type[n1]] = event; + } + } else { // Handle multiple listeners. + for (var key in type) { // Object {type} + if (typeof(type[key]) === "function") { // without configuration. + event = eventManager(target, key, type[key], clone(configure), trigger); + } else { // with configuration. + event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); + } + if (event) events[key] = event; + } + } + return createBatchCommands(events); + } + // Ensure listener is a function. + if (typeof(target) !== "object") return createError("Target is not defined!", arguments); + if (typeof(listener) !== "function") return createError("Listener is not a function!", arguments); + // Generate a unique wrapper identifier. + var useCapture = configure.useCapture || false; + var id = getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); + // Handle the event. + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event. + id = type + id; + if (trigger === "remove") { // Remove event listener. + if (!wrappers[id]) return; // Already removed. + wrappers[id].remove(); + delete wrappers[id]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[id]) { + wrappers[id].add(); + return wrappers[id]; // Already attached. + } + // Retains "this" orientation. + if (configure.useCall && !root.modifyEventListener) { + var tmp = listener; + listener = function(event, self) { + for (var key in self) event[key] = self[key]; + return tmp.call(target, event); + }; + } + // Create listener proxy. + configure.gesture = type; + configure.target = target; + configure.listener = listener; + configure.fromOverwrite = fromOverwrite; + // Record wrapper. + wrappers[id] = root.proxy[type](configure); + } + return wrappers[id]; + } else { // Fire native event. + var eventList = getEventList(type); + for (var n = 0, eventId; n < eventList.length; n ++) { + type = eventList[n]; + eventId = type + "." + id; + if (trigger === "remove") { // Remove event listener. + if (!wrappers[eventId]) continue; // Already removed. + target[remove](type, listener, useCapture); + delete wrappers[eventId]; + } else if (trigger === "add") { // Attach event listener. + if (wrappers[eventId]) return wrappers[eventId]; // Already attached. + target[add](type, listener, useCapture); + // Record wrapper. + wrappers[eventId] = { + id: eventId, + type: type, + target: target, + listener: listener, + remove: function() { + for (var n = 0; n < eventList.length; n ++) { + root.remove(target, eventList[n], listener, configure); + } + } + }; + } + } + return wrappers[eventId]; + } +}; + +/// Perform batch actions on multiple events. +var createBatchCommands = function(events) { + return { + remove: function() { // Remove multiple events. + for (var key in events) { + events[key].remove(); + } + }, + add: function() { // Add multiple events. + for (var key in events) { + events[key].add(); + } + } + }; +}; + +/// Display error message in console. +var createError = function(message, data) { + if (typeof(console) === "undefined") return; + if (typeof(console.error) === "undefined") return; + console.error(message, data); +}; + +/// Handle naming discrepancies between platforms. +var pointerDefs = { + "msPointer": [ "MSPointerDown", "MSPointerMove", "MSPointerUp" ], + "touch": [ "touchstart", "touchmove", "touchend" ], + "mouse": [ "mousedown", "mousemove", "mouseup" ] +}; + +var pointerDetect = { + // MSPointer + "MSPointerDown": 0, + "MSPointerMove": 1, + "MSPointerUp": 2, + // Touch + "touchstart": 0, + "touchmove": 1, + "touchend": 2, + // Mouse + "mousedown": 0, + "mousemove": 1, + "mouseup": 2 +}; + +var getEventSupport = (function() { + root.supports = {}; + if (window.navigator.msPointerEnabled) { + root.supports.msPointer = true; + } + if (root.getEventSupport("touchstart")) { + root.supports.touch = true; + } + if (root.getEventSupport("mousedown")) { + root.supports.mouse = true; + } +})(); + +var getEventList = (function() { + return function(type) { + var prefix = document.addEventListener ? "" : "on"; // IE + var idx = pointerDetect[type]; + if (isFinite(idx)) { + var types = []; + for (var key in root.supports) { + types.push(prefix + pointerDefs[key][idx]); + } + return types; + } else { + return [ prefix + type ]; + } + }; +})(); + +/// Event wrappers to keep track of all events placed in the window. +var wrappers = {}; +var counter = 0; +var getID = function(object) { + if (object === window) return "#window"; + if (object === document) return "#document"; + if (!object.uniqueID) object.uniqueID = "e" + counter ++; + return object.uniqueID; +}; + +/// Detect platforms native *EventListener command. +var add = document.addEventListener ? "addEventListener" : "attachEvent"; +var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + +/* + Pointer.js + ------------------------ + Modified from; https://github.com/borismus/pointer.js +*/ + +root.createPointerEvent = function (event, self, preventRecord) { + var eventName = self.gesture; + var target = self.target; + var pts = event.changedTouches || root.proxy.getCoords(event); + if (pts.length) { + var pt = pts[0]; + self.pointers = preventRecord ? [] : pts; + self.pageX = pt.pageX; + self.pageY = pt.pageY; + self.x = self.pageX; + self.y = self.pageY; + } + /// + var newEvent = document.createEvent("Event"); + newEvent.initEvent(eventName, true, true); + newEvent.originalEvent = event; + for (var k in self) { + if (k === "target") continue; + newEvent[k] = self[k]; + } + /// + var type = newEvent.type; + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. +// target.dispatchEvent(newEvent); + self.oldListener.call(target, newEvent, self, false); + } +}; + +/// Allows *EventListener to use custom event proxies. +if (root.modifyEventListener && window.HTMLElement) (function() { + var augmentEventListener = function(proto) { + var recall = function(trigger) { // overwrite native *EventListener's + var handle = trigger + "EventListener"; + var handler = proto[handle]; + proto[handle] = function (type, listener, useCapture) { + if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. + var configure = useCapture; + if (typeof(useCapture) === "object") { + configure.useCall = true; + } else { // convert to configuration object. + configure = { + useCall: true, + useCapture: useCapture + }; + } + eventManager(this, type, listener, configure, trigger, true); +// handler.call(this, type, listener, useCapture); + } else { // use native function. + var types = getEventList(type); + for (var n = 0; n < types.length; n ++) { + handler.call(this, types[n], listener, useCapture); + } + } + }; + }; + recall("add"); + recall("remove"); + }; + // NOTE: overwriting HTMLElement doesn't do anything in Firefox. + if (navigator.userAgent.match(/Firefox/)) { + // TODO: fix Firefox for the general case. + augmentEventListener(HTMLDivElement.prototype); + augmentEventListener(HTMLCanvasElement.prototype); + } else { + augmentEventListener(HTMLElement.prototype); + } + augmentEventListener(document); + augmentEventListener(window); +})(); + +/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk. +if (root.modifySelectors) (function() { + var proto = NodeList.prototype; + proto.removeEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n ++) { + this[n].removeEventListener(type, listener, useCapture); + } + }; + proto.addEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n ++) { + this[n].addEventListener(type, listener, useCapture); + } + }; +})(); + +return root; + +})(Event); +/* + ---------------------------------------------------- + Event.proxy : 0.4.3 : 2013/07/17 : MIT License + ---------------------------------------------------- + https://github.com/mudcube/Event.js + ---------------------------------------------------- +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +/* + Create a new pointer gesture instance. +*/ + +root.pointerSetup = function(conf, self) { + /// Configure. + conf.doc = conf.target.ownerDocument || conf.target; // Associated document. + conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers. + conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers. + conf.position = conf.position || "relative"; // Determines what coordinate system points are returned. + delete conf.fingers; //- + /// Convenience data. + self = self || {}; + self.enabled = true; + self.gesture = conf.gesture; + self.target = conf.target; + self.env = conf.env; + /// + if (Event.modifyEventListener && conf.fromOverwrite) { + conf.oldListener = conf.listener; + conf.listener = Event.createPointerEvent; + } + /// Convenience commands. + var fingers = 0; + var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; + if (conf.oldListener) self.oldListener = conf.oldListener; + self.listener = conf.listener; + self.proxy = function(listener) { + self.defaultListener = conf.listener; + conf.listener = listener; + listener(conf.event, self); + }; + self.add = function() { + if (self.enabled === true) return; + if (conf.onPointerDown) Event.add(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) Event.add(conf.doc, type + "up", conf.onPointerUp); + self.enabled = true; + }; + self.remove = function() { + if (self.enabled === false) return; + if (conf.onPointerDown) Event.remove(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) Event.remove(conf.doc, type + "up", conf.onPointerUp); + self.reset(); + self.enabled = false; + }; + self.pause = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) Event.remove(conf.doc, type + "up", conf.onPointerUp); + fingers = conf.fingers; + conf.fingers = 0; + }; + self.resume = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) Event.add(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = fingers; + }; + self.reset = function() { + conf.tracker = {}; + conf.fingers = 0; + }; + /// + return self; +}; + +/* + Begin proxied pointer command. +*/ + +var sp = Event.supports; +Event.pointerType = sp.mouse ? "mouse" : sp.touch ? "touch" : "mspointer"; +root.pointerStart = function(event, self, conf) { + var type = (event.type || "mousedown").toUpperCase(); + if (type.indexOf("MOUSE") === 0) Event.pointerType = "mouse"; + else if (type.indexOf("TOUCH") === 0) Event.pointerType = "touch"; + else if (type.indexOf("MSPOINTER") === 0) Event.pointerType = "mspointer"; + /// + var addTouchStart = function(touch, sid) { + var bbox = conf.bbox; + var pt = track[sid] = {}; + /// + switch(conf.position) { + case "absolute": // Absolute from within window. + pt.offsetX = 0; + pt.offsetY = 0; + break; + case "differenceFromLast": // Since last coordinate recorded. + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; + case "difference": // Relative from origin. + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; + case "move": // Move target element. + pt.offsetX = touch.pageX - bbox.x1; + pt.offsetY = touch.pageY - bbox.y1; + break; + default: // Relative from within target. + pt.offsetX = bbox.x1; + pt.offsetY = bbox.y1; + break; + } + /// + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - pt.offsetX); + var y = (touch.pageY + bbox.scrollTop - pt.offsetY); + } else { + var x = (touch.pageX - pt.offsetX); + var y = (touch.pageY - pt.offsetY); + } + /// + pt.rotation = 0; + pt.scale = 1; + pt.startTime = pt.moveTime = (new Date()).getTime(); + pt.move = { x: x, y: y }; + pt.start = { x: x, y: y }; + /// + conf.fingers ++; + }; + /// + conf.event = event; + if (self.defaultListener) { + conf.listener = self.defaultListener; + delete self.defaultListener; + } + /// + var isTouchStart = !conf.fingers; + var track = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Adding touch events to tracking. + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; // Touch ID. + // Track the current state of the touches. + if (conf.fingers) { + if (conf.fingers >= conf.maxFingers) { + var ids = []; + for (var sid in conf.tracker) ids.push(sid); + self.identifier = ids.join(","); + return isTouchStart; + } + var fingers = 0; // Finger ID. + for (var rid in track) { + // Replace removed finger. + if (track[rid].up) { + delete track[rid]; + addTouchStart(touch, sid); + conf.cancel = true; + break; + } + fingers ++; + } + // Add additional finger. + if (track[sid]) continue; + addTouchStart(touch, sid); + } else { // Start tracking fingers. + track = conf.tracker = {}; + self.bbox = conf.bbox = root.getBoundingBox(conf.target); + conf.fingers = 0; + conf.cancel = false; + addTouchStart(touch, sid); + } + } + /// + var ids = []; + for (var sid in conf.tracker) ids.push(sid); + self.identifier = ids.join(","); + /// + return isTouchStart; +}; + +/* + End proxied pointer command. +*/ + +root.pointerEnd = function(event, self, conf, onPointerUp) { + // Record changed touches have ended (iOS changedTouches is not reliable). + var touches = event.touches || []; + var length = touches.length; + var exists = {}; + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier; + exists[sid || Infinity] = true; + } + for (var sid in conf.tracker) { + var track = conf.tracker[sid]; + if (exists[sid] || track.up) continue; + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + pageX: track.pageX, + pageY: track.pageY, + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers --; + } +/* // This should work but fails in Safari on iOS4 so not using it. + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Record changed touches have ended (this should work). + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var track = conf.tracker[sid]; + if (track && !track.up) { + if (onPointerUp) { // add changedTouches to mouse. + onPointerUp({ + changedTouches: [{ + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + }] + }, "up"); + } + track.up = true; + conf.fingers --; + } + } */ + // Wait for all fingers to be released. + if (conf.fingers !== 0) return false; + // Record total number of fingers gesture used. + var ids = []; + conf.gestureFingers = 0; + for (var sid in conf.tracker) { + conf.gestureFingers ++; + ids.push(sid); + } + self.identifier = ids.join(","); + // Our pointer gesture has ended. + return true; +}; + +/* + Returns mouse coords in an array to match event.*Touches + ------------------------------------------------------------ + var touch = event.changedTouches || root.getCoords(event); +*/ + +root.getCoords = function(event) { + if (typeof(event.pageX) !== "undefined") { // Desktop browsers. + root.getCoords = function(event) { + return Array({ + type: "mouse", + x: event.pageX, + y: event.pageY, + pageX: event.pageX, + pageY: event.pageY, + identifier: event.pointerId || Infinity // pointerId is MS + }); + }; + } else { // Internet Explorer <= 8.0 + root.getCoords = function(event) { + event = event || window.event; + return Array({ + type: "mouse", + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop, + pageX: event.clientX + document.documentElement.scrollLeft, + pageY: event.clientY + document.documentElement.scrollTop, + identifier: Infinity + }); + }; + } + return root.getCoords(event); +}; + +/* + Returns single coords in an object. + ------------------------------------------------------------ + var mouse = root.getCoord(event); +*/ + +root.getCoord = function(event) { + if ("ontouchstart" in window) { // Mobile browsers. + var pX = 0; + var pY = 0; + root.getCoord = function(event) { + var touches = event.changedTouches; + if (touches && touches.length) { // ontouchstart + ontouchmove + return { + x: pX = touches[0].pageX, + y: pY = touches[0].pageY + }; + } else { // ontouchend + return { + x: pX, + y: pY + }; + } + }; + } else if(typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers. + root.getCoord = function(event) { + return { + x: event.pageX, + y: event.pageY + }; + }; + } else { // Internet Explorer <=8.0 + root.getCoord = function(event) { + event = event || window.event; + return { + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop + }; + }; + } + return root.getCoord(event); +}; + +/* + Get target scale and position in space. +*/ + +root.getBoundingBox = function(o) { + if (o === window || o === document) o = document.body; + /// + var bbox = {}; + var bcr = o.getBoundingClientRect(); + bbox.width = bcr.width; + bbox.height = bcr.height; + bbox.x1 = bcr.left; + bbox.y1 = bcr.top; + bbox.x2 = bbox.x1 + bbox.width; + bbox.y2 = bbox.y1 + bbox.height; + bbox.scaleX = bcr.width / o.offsetWidth || 1; + bbox.scaleY = bcr.height / o.offsetHeight || 1; + bbox.scrollLeft = 0; + bbox.scrollTop = 0; + + /// Get the scroll of container element. + var tmp = o.parentNode; + while (tmp !== null) { + if (tmp === document.body) break; + if (tmp.scrollTop === undefined) break; + var style = window.getComputedStyle(tmp); + var position = style.getPropertyValue("position"); + if (position === "absolute") { + break; + } else if (position === "fixed") { + bbox.scrollTop -= tmp.parentNode.scrollTop; + break; + } else { + bbox.scrollLeft += tmp.scrollLeft; + bbox.scrollTop += tmp.scrollTop; + } + tmp = tmp.parentNode; + }; + /// + return bbox; +}; + +/* + Keep track of metaKey, the proper ctrlKey for users platform. +*/ + +(function() { + var agent = navigator.userAgent.toLowerCase(); + var mac = agent.indexOf("macintosh") !== -1; + if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari. + var watch = { 91: true, 93: true }; + } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox. + var watch = { 224: true }; + } else { // windows, linux, or mac opera. + var watch = { 17: true }; + } + root.metaTrackerReset = function() { + root.metaKey = false; + root.ctrlKey = false; + root.shiftKey = false; + root.altKey = false; + }; + root.metaTracker = function(event) { + var check = !!watch[event.keyCode]; + if (check) root.metaKey = event.type === "keydown"; + root.ctrlKey = event.ctrlKey; + root.shiftKey = event.shiftKey; + root.altKey = event.altKey; + return check; + }; +})(); + +return root; + +})(Event.proxy); +/* + ---------------------------------------------------- + "MutationObserver" event proxy. + ---------------------------------------------------- + Author: Selvakumar Arumugam (MIT LICENSE) + http://stackoverflow.com/questions/10868104/can-you-have-a-javascript-hook-trigger-after-a-dom-elements-style-object-change + ---------------------------------------------------- +*/ +if (typeof(Event) === "undefined") var Event = {}; + +Event.MutationObserver = (function() { + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var DOMAttrModifiedSupported = (function() { + var p = document.createElement("p"); + var flag = false; + var fn = function() { flag = true }; + if (p.addEventListener) { + p.addEventListener("DOMAttrModified", fn, false); + } else if (p.attachEvent) { + p.attachEvent("onDOMAttrModified", fn); + } else { + return false; + } + /// + p.setAttribute("id", "target"); + /// + return flag; + })(); + /// + return function(container, callback) { + if (MutationObserver) { + var options = { + subtree: false, + attributes: true + }; + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(e) { + callback.call(e.target, e.attributeName); + }); + }); + observer.observe(container, options) + } else if (DOMAttrModifiedSupported) { + Event.add(container, "DOMAttrModified", function(e) { + callback.call(container, e.attrName); + }); + } else if ("onpropertychange" in document.body) { + Event.add(container, "propertychange", function(e) { + callback.call(container, window.event.propertyName); + }); + } + } +})(); +/* + "Click" event proxy. + ---------------------------------------------------- + Event.add(window, "click", function(event, self) {}); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.click = function(conf) { + conf.gesture = conf.gesture || "click"; + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var EVENT; + // Tracking the events. + conf.onPointerDown = function (event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function (event) { + EVENT = event; + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (EVENT.cancelBubble && ++ EVENT.bubble > 1) return; + var pointers = EVENT.changedTouches || root.getCoords(EVENT); + var pointer = pointers[0]; + var bbox = conf.bbox; + var newbbox = root.getBoundingBox(conf.target); + if (conf.position === "relative") { + var ax = (pointer.pageX + bbox.scrollLeft - bbox.x1); + var ay = (pointer.pageY + bbox.scrollTop - bbox.y1); + } else { + var ax = (pointer.pageX - bbox.x1); + var ay = (pointer.pageY - bbox.y1); + } + if (ax > 0 && ax < bbox.width && // Within target coordinates. + ay > 0 && ay < bbox.height && + bbox.scrollTop === newbbox.scrollTop) { + /// + for (var key in conf.tracker) break; //- should be modularized? in dblclick too + var point = conf.tracker[key]; + self.x = point.start.x; + self.y = point.start.y; + /// + conf.listener(EVENT, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "click"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.click = root.click; + +return root; + +})(Event.proxy); +/* + "Double-Click" aka "Double-Tap" event proxy. + ---------------------------------------------------- + Event.add(window, "dblclick", function(event, self) {}); + ---------------------------------------------------- + Touch an target twice for <= 700ms, with less than 25 pixel drift. +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.dbltap = +root.dblclick = function(conf) { + conf.gesture = conf.gesture || "dbltap"; + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + // Setting up local variables. + var delay = 700; // in milliseconds + var time0, time1, timeout; + var pointer0, pointer1; + // Tracking the events. + conf.onPointerDown = function (event) { + var pointers = event.changedTouches || root.getCoords(event); + if (time0 && !time1) { // Click #2 + pointer1 = pointers[0]; + time1 = (new Date()).getTime() - time0; + } else { // Click #1 + pointer0 = pointers[0]; + time0 = (new Date()).getTime(); + time1 = 0; + clearTimeout(timeout); + timeout = setTimeout(function() { + time0 = 0; + }, delay); + } + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function (event) { + if (time0 && !time1) { + var pointers = event.changedTouches || root.getCoords(event); + pointer1 = pointers[0]; + } + var bbox = conf.bbox; + if (conf.position === "relative") { + var ax = (pointer1.pageX + bbox.scrollLeft - bbox.x1); + var ay = (pointer1.pageY + bbox.scrollTop - bbox.y1); + } else { + var ax = (pointer1.pageX - bbox.x1); + var ay = (pointer1.pageY - bbox.y1); + } + if (!(ax > 0 && ax < bbox.width && // Within target coordinates.. + ay > 0 && ay < bbox.height && + Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance. + Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + if (time0 && time1) { + if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { + self.state = conf.gesture; + for (var key in conf.tracker) break; + var point = conf.tracker[key]; + self.x = point.start.x; + self.y = point.start.y; + conf.listener(event, self); + } + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + self.state = "dblclick"; + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.dbltap = root.dbltap; +Event.Gesture._gestureHandlers.dblclick = root.dblclick; + +return root; + +})(Event.proxy); +/* + "Drag" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: maxFingers, position. + ---------------------------------------------------- + Event.add(window, "drag", function(event, self) { + console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.dragElement = function(that, event) { + root.drag({ + event: event, + target: that, + position: "move", + listener: function(event, self) { + that.style.left = self.x + "px"; + that.style.top = self.y + "px"; + Event.prevent(event); + } + }); +}; + +root.drag = function(conf) { + conf.gesture = "drag"; + conf.onPointerDown = function (event) { + if (root.pointerStart(event, self, conf)) { + if (!conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Process event listener. + conf.onPointerMove(event, "down"); + }; + conf.onPointerMove = function (event, state) { + if (!conf.tracker) return conf.onPointerDown(event); + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + // Identifier defined outside of listener. + if (!pt) continue; + pt.pageX = touch.pageX; + pt.pageY = touch.pageY; + // Record data. + self.state = state || "move"; + self.identifier = identifier; + self.start = pt.start; + self.fingers = conf.fingers; + if (conf.position === "differenceFromLast") { + self.x = (pt.pageX - pt.offsetX); + self.y = (pt.pageY - pt.offsetY); + pt.offsetX = pt.pageX; + pt.offsetY = pt.pageY; + } else if (conf.position === "relative") { + self.x = (pt.pageX + bbox.scrollLeft - pt.offsetX); + self.y = (pt.pageY + bbox.scrollTop - pt.offsetY); + } else { + self.x = (pt.pageX - pt.offsetX); + self.y = (pt.pageY - pt.offsetY); + } + /// + conf.listener(event, self); + } + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { + if (!conf.monitor) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + if (conf.event) { + conf.onPointerDown(conf.event); + } else { // + Event.add(conf.target, "mousedown", conf.onPointerDown); + if (conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.drag = root.drag; + +return root; + +})(Event.proxy); +/* + "Gesture" event proxy (2+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +var RAD_DEG = Math.PI / 180; + +root.gesture = function(conf) { + conf.gesture = conf.gesture || "gesture"; + conf.minFingers = conf.minFingers || conf.fingers || 2; + // Tracking the events. + conf.onPointerDown = function (event) { + var fingers = conf.fingers; + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + // Record gesture start. + if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { + self.fingers = conf.minFingers; + self.scale = 1; + self.rotation = 0; + self.state = "start"; + var sids = ""; //- FIXME(mud): can generate duplicate IDs. + for (var key in conf.tracker) sids += key; + self.identifier = parseInt(sids); + conf.listener(event, self); + } + }; + /// + conf.onPointerMove = function (event, state) { + var bbox = conf.bbox; + var points = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + // Update tracker coordinates. + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var pt = points[sid]; + // Check whether "pt" is used by another gesture. + if (!pt) continue; + // Find the actual coordinates. + if (conf.position === "relative") { + pt.move.x = (touch.pageX + bbox.scrollLeft - bbox.x1); + pt.move.y = (touch.pageY + bbox.scrollTop - bbox.y1); + } else { + pt.move.x = (touch.pageX - bbox.x1); + pt.move.y = (touch.pageY - bbox.y1); + } + } + /// + if (conf.fingers < conf.minFingers) return; + /// + var touches = []; + var scale = 0; + var rotation = 0; + /// Calculate centroid of gesture. + var centroidx = 0; + var centroidy = 0; + var length = 0; + for (var sid in points) { + var touch = points[sid]; + if (touch.up) continue; + centroidx += touch.move.x; + centroidy += touch.move.y; + length ++; + } + centroidx /= length; + centroidy /= length; + /// + for (var sid in points) { + var touch = points[sid]; + if (touch.up) continue; + var start = touch.start; + if (!start.distance) { + var dx = start.x - centroidx; + var dy = start.y - centroidy; + start.distance = Math.sqrt(dx * dx + dy * dy); + start.angle = Math.atan2(dx, dy) / RAD_DEG; + } + // Calculate scale. + var dx = touch.move.x - centroidx; + var dy = touch.move.y - centroidy; + var distance = Math.sqrt(dx * dx + dy * dy); + scale += distance / start.distance; + // Calculate rotation. + var angle = Math.atan2(dx, dy) / RAD_DEG; + var rotate = (start.angle - angle + 360) % 360 - 180; + touch.DEG2 = touch.DEG1; // Previous degree. + touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree. + if (typeof(touch.DEG2) !== "undefined") { + if (rotate > 0) { + touch.rotation += touch.DEG1 - touch.DEG2; + } else { + touch.rotation -= touch.DEG1 - touch.DEG2; + } + rotation += touch.rotation; + } + // Attach current points to self. + touches.push(touch.move); + } + /// + self.touches = touches; + self.fingers = conf.fingers; + self.scale = scale / conf.fingers; + self.rotation = rotation / conf.fingers; + self.state = "change"; + conf.listener(event, self); + }; + conf.onPointerUp = function(event) { + // Remove tracking for touch. + var fingers = conf.fingers; + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + // Check whether fingers has dropped below minFingers. + if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { + self.fingers = conf.fingers; + self.state = "end"; + conf.listener(event, self); + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.gesture = root.gesture; + +return root; + +})(Event.proxy); +/* + "Pointer" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: minFingers, maxFingers. + ---------------------------------------------------- + Event.add(window, "gesture", function(event, self) { + console.log(self.rotation, self.scale, self.fingers, self.state); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.pointerdown = +root.pointermove = +root.pointerup = function(conf) { + conf.gesture = conf.gesture || "pointer"; + if (conf.target.isPointerEmitter) return; + // Tracking the events. + var isDown = true; + conf.onPointerDown = function (event) { + isDown = false; + self.gesture = "pointerdown"; + conf.listener(event, self); + }; + conf.onPointerMove = function (event) { + self.gesture = "pointermove"; + conf.listener(event, self, isDown); + }; + conf.onPointerUp = function (event) { + isDown = true; + self.gesture = "pointerup"; + conf.listener(event, self, true); + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + Event.add(conf.target, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Return this object. + conf.target.isPointerEmitter = true; + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; +Event.Gesture._gestureHandlers.pointermove = root.pointermove; +Event.Gesture._gestureHandlers.pointerup = root.pointerup; + +return root; + +})(Event.proxy); +/* + "Device Motion" and "Shake" event proxy. + ---------------------------------------------------- + http://developer.android.com/reference/android/hardware/SensorEvent.html#values + ---------------------------------------------------- + Event.add(window, "shake", function(event, self) {}); + Event.add(window, "devicemotion", function(event, self) { + console.log(self.acceleration, self.accelerationIncludingGravity); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.shake = function(conf) { + // Externally accessible data. + var self = { + gesture: "devicemotion", + acceleration: {}, + accelerationIncludingGravity: {}, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener('devicemotion', onDeviceMotion, false); + } + }; + // Setting up local variables. + var threshold = 4; // Gravitational threshold. + var timeout = 1000; // Timeout between shake events. + var timeframe = 200; // Time between shakes. + var shakes = 3; // Minimum shakes to trigger event. + var lastShake = (new Date()).getTime(); + var gravity = { x: 0, y: 0, z: 0 }; + var delta = { + x: { count: 0, value: 0 }, + y: { count: 0, value: 0 }, + z: { count: 0, value: 0 } + }; + // Tracking the events. + var onDeviceMotion = function(e) { + var alpha = 0.8; // Low pass filter. + var o = e.accelerationIncludingGravity; + gravity.x = alpha * gravity.x + (1 - alpha) * o.x; + gravity.y = alpha * gravity.y + (1 - alpha) * o.y; + gravity.z = alpha * gravity.z + (1 - alpha) * o.z; + self.accelerationIncludingGravity = gravity; + self.acceleration.x = o.x - gravity.x; + self.acceleration.y = o.y - gravity.y; + self.acceleration.z = o.z - gravity.z; + /// + if (conf.gesture === "devicemotion") { + conf.listener(e, self); + return; + } + var data = "xyz"; + var now = (new Date()).getTime(); + for (var n = 0, length = data.length; n < length; n ++) { + var letter = data[n]; + var ACCELERATION = self.acceleration[letter]; + var DELTA = delta[letter]; + var abs = Math.abs(ACCELERATION); + /// Check whether another shake event was recently registered. + if (now - lastShake < timeout) continue; + /// Check whether delta surpasses threshold. + if (abs > threshold) { + var idx = now * ACCELERATION / abs; + var span = Math.abs(idx + DELTA.value); + // Check whether last delta was registered within timeframe. + if (DELTA.value && span < timeframe) { + DELTA.value = idx; + DELTA.count ++; + // Check whether delta count has enough shakes. + if (DELTA.count === shakes) { + conf.listener(e, self); + // Reset tracking. + lastShake = now; + DELTA.value = 0; + DELTA.count = 0; + } + } else { + // Track first shake. + DELTA.value = idx; + DELTA.count = 1; + } + } + } + }; + // Attach events. + if (!window.addEventListener) return; + window.addEventListener('devicemotion', onDeviceMotion, false); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.shake = root.shake; + +return root; + +})(Event.proxy); +/* + "Swipe" event proxy (1+ fingers). + ---------------------------------------------------- + CONFIGURE: snap, threshold, maxFingers. + ---------------------------------------------------- + Event.add(window, "swipe", function(event, self) { + console.log(self.velocity, self.angle); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +var RAD_DEG = Math.PI / 180; + +root.swipe = function(conf) { + conf.snap = conf.snap || 90; // angle snap. + conf.threshold = conf.threshold || 1; // velocity threshold. + conf.gesture = conf.gesture || "swipe"; + // Tracking the events. + conf.onPointerDown = function (event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function (event) { + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var o = conf.tracker[sid]; + // Identifier defined outside of listener. + if (!o) continue; + o.move.x = touch.pageX; + o.move.y = touch.pageY; + o.moveTime = (new Date()).getTime(); + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + /// + var velocity1; + var velocity2 + var degree1; + var degree2; + /// Calculate centroid of gesture. + var start = { x: 0, y: 0 }; + var endx = 0; + var endy = 0; + var length = 0; + /// + for (var sid in conf.tracker) { + var touch = conf.tracker[sid]; + var xdist = touch.move.x - touch.start.x; + var ydist = touch.move.y - touch.start.y; + /// + endx += touch.move.x; + endy += touch.move.y; + start.x += touch.start.x; + start.y += touch.start.y; + length ++; + /// + var distance = Math.sqrt(xdist * xdist + ydist * ydist); + var ms = touch.moveTime - touch.startTime; + var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; + var velocity2 = ms ? distance / ms : 0; + if (typeof(degree1) === "undefined") { + degree1 = degree2; + velocity1 = velocity2; + } else if (Math.abs(degree2 - degree1) <= 20) { + degree1 = (degree1 + degree2) / 2; + velocity1 = (velocity1 + velocity2) / 2; + } else { + return; + } + } + /// + var fingers = conf.gestureFingers; + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + if (velocity1 > conf.threshold) { + start.x /= length; + start.y /= length; + self.start = start; + self.x = endx / length; + self.y = endy / length; + self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360); + self.velocity = velocity1; + self.fingers = fingers; + self.state = "swipe"; + conf.listener(event, self); + } + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.swipe = root.swipe; + +return root; + +})(Event.proxy); +/* + "Tap" and "Longpress" event proxy. + ---------------------------------------------------- + CONFIGURE: delay (longpress), timeout (tap). + ---------------------------------------------------- + Event.add(window, "tap", function(event, self) { + console.log(self.fingers); + }); + ---------------------------------------------------- + multi-finger tap // touch an target for <= 250ms. + multi-finger longpress // touch an target for >= 500ms +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.longpress = function(conf) { + conf.gesture = "longpress"; + return root.tap(conf); +}; + +root.tap = function(conf) { + conf.delay = conf.delay || 500; + conf.timeout = conf.timeout || 250; + conf.driftDeviance = conf.driftDeviance || 10; + conf.gesture = conf.gesture || "tap"; + // Setting up local variables. + var timestamp, timeout; + // Tracking the events. + conf.onPointerDown = function (event) { + if (root.pointerStart(event, self, conf)) { + timestamp = (new Date()).getTime(); + // Initialize event listeners. + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + // Make sure this is a "longpress" event. + if (conf.gesture !== "longpress") return; + timeout = setTimeout(function() { + if (event.cancelBubble && ++event.bubble > 1) return; + // Make sure no fingers have been changed. + var fingers = 0; + for (var key in conf.tracker) { + var point = conf.tracker[key]; + if (point.end === true) return; + if (conf.cancel) return; + fingers ++; + } + // Send callback. + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + self.state = "start"; + self.fingers = fingers; + self.x = point.start.x; + self.y = point.start.y; + conf.listener(event, self); + } + }, conf.delay); + } + }; + conf.onPointerMove = function (event) { + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i ++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + if (!pt) continue; + if (conf.position === "relative") { + var x = (touch.pageX + bbox.scrollLeft - bbox.x1); + var y = (touch.pageY + bbox.scrollTop - bbox.y1); + } else { + var x = (touch.pageX - bbox.x1); + var y = (touch.pageY - bbox.y1); + } + /// + var dx = x - pt.start.x; + var dy = y - pt.start.y; + var distance = Math.sqrt(dx * dx + dy * dy); + if (!(x > 0 && x < bbox.width && // Within target coordinates.. + y > 0 && y < bbox.height && + distance <= conf.driftDeviance)) { // Within drift deviance. + // Cancel out this listener. + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + conf.cancel = true; + return; + } + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + clearTimeout(timeout); + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (event.cancelBubble && ++event.bubble > 1) return; + // Callback release on longpress. + if (conf.gesture === "longpress") { + if (self.state === "start") { + self.state = "end"; + conf.listener(event, self); + } + return; + } + // Cancel event due to movement. + if (conf.cancel) return; + // Ensure delay is within margins. + if ((new Date()).getTime() - timestamp > conf.timeout) return; + // Send callback. + var fingers = conf.gestureFingers; + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + self.state = "tap"; + self.fingers = conf.gestureFingers; + conf.listener(event, self); + } + } + }; + // Generate maintenance commands, and other configurations. + var self = root.pointerSetup(conf); + // Attach events. + Event.add(conf.target, "mousedown", conf.onPointerDown); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.tap = root.tap; +Event.Gesture._gestureHandlers.longpress = root.longpress; + +return root; + +})(Event.proxy); +/* + "Mouse Wheel" event proxy. + ---------------------------------------------------- + Event.add(window, "wheel", function(event, self) { + console.log(self.state, self.wheelDelta); + }); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.wheel = function(conf) { + // Configure event listener. + var interval; + var timeout = conf.timeout || 150; + var count = 0; + // Externally accessible data. + var self = { + gesture: "wheel", + state: "start", + wheelDelta: 0, + target: conf.target, + listener: conf.listener, + preventElasticBounce: function() { + var target = this.target; + var scrollTop = target.scrollTop; + var top = scrollTop + target.offsetHeight; + var height = target.scrollHeight; + if (top === height && this.wheelDelta <= 0) Event.cancel(event); + else if (scrollTop === 0 && this.wheelDelta >= 0) Event.cancel(event); + Event.stop(event); + }, + add: function() { + conf.target[add](type, onMouseWheel, false); + }, + remove: function() { + conf.target[remove](type, onMouseWheel, false); + } + }; + // Tracking the events. + var onMouseWheel = function(event) { + event = event || window.event; + self.state = count++ ? "change" : "start"; + self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; + conf.listener(event, self); + clearTimeout(interval); + interval = setTimeout(function() { + count = 0; + self.state = "end"; + self.wheelDelta = 0; + conf.listener(event, self); + }, timeout); + }; + // Attach events. + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + var type = Event.getEventSupport("mousewheel") ? "mousewheel" : "DOMMouseScroll"; + conf.target[add](type, onMouseWheel, false); + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.wheel = root.wheel; + +return root; + +})(Event.proxy); +/* + "Orientation Change" + ---------------------------------------------------- + https://developer.apple.com/library/safari/documentation/SafariDOMAdditions/Reference/DeviceOrientationEventClassRef/DeviceOrientationEvent/DeviceOrientationEvent.html#//apple_ref/doc/uid/TP40010526 + ---------------------------------------------------- + Event.add(window, "deviceorientation", function(event, self) {}); +*/ + +if (typeof(Event) === "undefined") var Event = {}; +if (typeof(Event.proxy) === "undefined") Event.proxy = {}; + +Event.proxy = (function(root) { "use strict"; + +root.orientation = function(conf) { + // Externally accessible data. + var self = { + gesture: "orientationchange", + previous: null, /* Report the previous orientation */ + current: window.orientation, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener('orientationchange', onOrientationChange, false); + } + }; + + // Tracking the events. + var onOrientationChange = function(e) { + + self.previous = self.current; + self.current = window.orientation; + if(self.previous !== null && self.previous != self.current) { + conf.listener(e, self); + return; + } + + + }; + // Attach events. + if (window.DeviceOrientationEvent) { + window.addEventListener("orientationchange", onOrientationChange, false); + } + // Return this object. + return self; +}; + +Event.Gesture = Event.Gesture || {}; +Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; +Event.Gesture._gestureHandlers.orientation = root.orientation; + +return root; + +})(Event.proxy); + + (function(){ /** @@ -392,6 +3939,45 @@ fabric.Collection = { return new fabric.Point(rx, ry).addEquals(origin); }, + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[1] * p.y, + t[2] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[1] * p.y + t[4], + t[2] * p.x + t[3] * p.y + t[5] + ); + }, + + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + invertTransform: function(t) { + var r = t.slice(), + a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; + var o = fabric.util.transformPoint({x: t[4], y: t[5]}, r); + r[4] = -o.x; + r[5] = -o.y; + return r + }, + /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static @@ -1514,1922 +5100,8 @@ fabric.util.string = { // IE branch listeners = { }, -<<<<<<< HEAD -/* - ---------------------------------------------------- - Event.js : 1.1.1 : 2012/11/19 : MIT License - ---------------------------------------------------- - https://github.com/mudcube/Event.js - ---------------------------------------------------- - 1 : click, dblclick, dbltap - 1+ : tap, longpress, drag, swipe - 2+ : pinch, rotate - : mousewheel, devicemotion, shake - ---------------------------------------------------- - TODO - ---------------------------------------------------- - * switch configuration to 4th argument on addEventListener - * bbox calculation for elements scaled with transform. - ---------------------------------------------------- - NOTES - ---------------------------------------------------- - * When using other libraries that may have built in "Event" namespace, - i.e. Typescript, you can use "eventjs" instead of "Event" for all example calls. - ---------------------------------------------------- - REQUIREMENTS: querySelector, querySelectorAll - ---------------------------------------------------- - * There are two ways to add/remove events with this library. - ---------------------------------------------------- - // Retains "this" attribute as target, and overrides native addEventListener. - target.addEventListener(type, listener, useCapture); - target.removeEventListener(type, listener, useCapture); - - // Attempts to perform as fast as possible. - Event.add(type, listener, configure); - Event.remove(type, listener, configure); - - * You can turn prototyping on/off for individual features. - ---------------------------------------------------- - Event.modifyEventListener = true; // add custom *EventListener commands to HTMLElements. - Event.modifySelectors = true; // add bulk *EventListener commands on NodeLists from querySelectorAll and others. - - * Example of setting up a single listener with a custom configuration. - ---------------------------------------------------- - // optional configuration. - var configure = { - fingers: 2, // listen for specifically two fingers. - snap: 90 // snap to 90 degree intervals. - }; - // adding with addEventListener() - target.addEventListener("swipe", function(event) { - // additional variables can be found on the event object. - console.log(event.velocity, event.angle, event.fingers); - }, configure); - - // adding with Event.add() - Event.add("swipe", function(event, self) { - // additional variables can be found on the self object. - console.log(self.velocity, self.angle, self.fingers); - }, configure); - - * Multiple listeners glued together. - ---------------------------------------------------- - // adding with addEventListener() - target.addEventListener("click swipe", function(event) { }); - - // adding with Event.add() - Event.add(target, "click swipe", function(event, self) { }); - - * Use query selectors to create an event (querySelectorAll) - ---------------------------------------------------- - // adding events to NodeList from querySelectorAll() - document.querySelectorAll("#element a.link").addEventListener("click", callback); - - // adding with Event.add() - Event.add("#element a.link", "click", callback); - - * Listen for selector to become available (querySelector) - ---------------------------------------------------- - Event.add("body", "ready", callback); - // or... - Event.add({ - target: "body", - type: "ready", - timeout: 10000, // set a timeout to stop checking. - interval: 30, // set how often to check for element. - listener: callback - }); - - * Multiple listeners bound to one callback w/ single configuration. - ---------------------------------------------------- - var bindings = Event.add({ - target: target, - type: "click swipe", - snap: 90, // snap to 90 degree intervals. - minFingers: 2, // minimum required fingers to start event. - maxFingers: 4, // maximum fingers in one event. - listener: function(event, self) { - console.log(self.gesture); // will be click or swipe. - console.log(self.x); - console.log(self.y); - console.log(self.identifier); - console.log(self.start); - console.log(self.fingers); // somewhere between "2" and "4". - self.pause(); // disable event. - self.resume(); // enable event. - self.remove(); // remove event. - } - }); - - * Multiple listeners bound to multiple callbacks w/ single configuration. - ---------------------------------------------------- - var bindings = Event.add({ - target: target, - minFingers: 1, - maxFingers: 12, - listeners: { - click: function(event, self) { - self.remove(); // removes this click listener. - }, - swipe: function(event, self) { - binding.remove(); // removes both the click + swipe listeners. - } - } - }); - - * Multiple listeners bound to multiple callbacks w/ multiple configurations. - ---------------------------------------------------- - var binding = Event.add({ - target: target, - listeners: { - longpress: { - fingers: 1, - wait: 500, // milliseconds - listener: function(event, self) { - console.log(self.fingers); // "1" finger. - } - }, - drag: { - fingers: 3, - position: "relative", // "relative", "absolute", "difference", "move" - listener: function(event, self) { - console.log(self.fingers); // "3" fingers. - console.log(self.x); // coordinate is relative to edge of target. - } - } - } - }); - - * Capturing an event and manually forwarding it to a proxy (tiered events). - ---------------------------------------------------- - Event.add(target, "down", function(event, self) { - var x = event.pageX; // local variables that wont change. - var y = event.pageY; - Event.proxy.drag({ - event: event, - target: target, - listener: function(event, self) { - console.log(x - event.pageX); // measure movement. - console.log(y - event.pageY); - } - }); - }); - ---------------------------------------------------- - - * Event proxies. - * type, fingers, state, start, x, y, position, bbox - * rotation, scale, velocity, angle, delay, timeout - ---------------------------------------------------- - // "Click" :: fingers, minFingers, maxFingers. - Event.add(window, "click", function(event, self) { - console.log(self.gesture, self.x, self.y); - }); - // "Double-Click" :: fingers, minFingers, maxFingers. - Event.add(window, "dblclick", function(event, self) { - console.log(self.gesture, self.x, self.y); - }); - // "Drag" :: fingers, maxFingers, position - Event.add(window, "drag", function(event, self) { - console.log(self.gesture, self.fingers, self.state, self.start, self.x, self.y, self.bbox); - }); - // "Gesture" :: fingers, minFingers, maxFingers. - Event.add(window, "gesture", function(event, self) { - console.log(self.gesture, self.fingers, self.state, self.rotation, self.scale); - }); - // "Swipe" :: fingers, minFingers, maxFingers, snap, threshold. - Event.add(window, "swipe", function(event, self) { - console.log(self.gesture, self.fingers, self.velocity, self.angle, self.start, self.x, self.y); - }); - // "Tap" :: fingers, minFingers, maxFingers, timeout. - Event.add(window, "tap", function(event, self) { - console.log(self.gesture, self.fingers); - }); - // "Longpress" :: fingers, minFingers, maxFingers, delay. - Event.add(window, "longpress", function(event, self) { - console.log(self.gesture, self.fingers); - }); - // - Event.add(window, "shake", function(event, self) { - console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); - }); - // - Event.add(window, "devicemotion", function(event, self) { - console.log(self.gesture, self.acceleration, self.accelerationIncludingGravity); - }); - // - Event.add(window, "wheel", function(event, self) { - console.log(self.gesture, self.state, self.wheelDelta); - }); - - * Stop, prevent and cancel. - ---------------------------------------------------- - Event.stop(event); // stop bubble. - Event.prevent(event); // prevent default. - Event.cancel(event); // stop and prevent. - - * Track for proper command/control-key for Mac/PC. - ---------------------------------------------------- - Event.add(window, "keyup keydown", Event.proxy.metaTracker); - console.log(Event.proxy.metaKey); - - * Test for event features, in this example Drag & Drop file support. - ---------------------------------------------------- - console.log(Event.supports('dragstart') && Event.supports('drop') && !!window.FileReader); - - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(eventjs) === "undefined") - var eventjs = Event; - -Event = (function(root) { - "use strict"; - -// Add custom *EventListener commands to HTMLElements. - root.modifyEventListener = false; - -// Add bulk *EventListener commands on NodeLists from querySelectorAll and others. - root.modifySelectors = false; - -// Event maintenance. - root.add = function(target, type, listener, configure) { - return eventManager(target, type, listener, configure, "add"); - }; - - root.remove = function(target, type, listener, configure) { - return eventManager(target, type, listener, configure, "remove"); - }; - - root.stop = function(event) { - if (event.stopPropagation) - event.stopPropagation(); - event.cancelBubble = true; // <= IE8 - event.bubble = 0; - }; - - root.prevent = function(event) { - if (event.preventDefault) - event.preventDefault(); - event.returnValue = false; // <= IE8 - }; - - root.cancel = function(event) { - root.stop(event); - root.prevent(event); - }; - -// Check whether event is natively supported (via @kangax) - root.supports = function(target, type) { - if (typeof(target) === "string") { - type = target; - target = window; - } - type = "on" + type; - if (type in target) - return true; - if (!target.setAttribute) - target = document.createElement("div"); - if (target.setAttribute && target.removeAttribute) { - target.setAttribute(type, ""); - var isSupported = typeof target[type] === "function"; - if (typeof target[type] !== "undefined") - target[type] = null; - target.removeAttribute(type); - return isSupported; - } - }; - - var clone = function(obj) { - if (!obj || typeof (obj) !== 'object') - return obj; - var temp = new obj.constructor(); - for (var key in obj) { - if (!obj[key] || typeof (obj[key]) !== 'object') { - temp[key] = obj[key]; - } else { // clone sub-object - temp[key] = clone(obj[key]); - } - } - return temp; - }; - -/// Handle custom *EventListener commands. - var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { - configure = configure || {}; - // Check for element to load on interval (before onload). - if (typeof(target) === "string" && type === "ready") { - var time = (new Date()).getTime(); - var timeout = configure.timeout; - var ms = configure.interval || 1000 / 60; - var interval = window.setInterval(function() { - if ((new Date()).getTime() - time > timeout) { - window.clearInterval(interval); - } - if (document.querySelector(target)) { - window.clearInterval(interval); - listener(); - } - }, ms); - return; - } - // Get DOM element from Query Selector. - if (typeof(target) === "string") { - target = document.querySelectorAll(target); - if (target.length === 0) - return createError("Missing target on listener!"); // No results. - if (target.length === 1) { // Single target. - target = target[0]; - } - } - /// Handle multiple targets. - var event; - var events = {}; - if (target.length > 0) { - for (var n0 = 0, length0 = target.length; n0 < length0; n0++) { - event = eventManager(target[n0], type, listener, clone(configure), trigger); - if (event) - events[n0] = event; - } - return createBatchCommands(events); - } - // Check for multiple events in one string. - if (type.indexOf && type.indexOf(" ") !== -1) - type = type.split(" "); - if (type.indexOf && type.indexOf(",") !== -1) - type = type.split(","); - // Attach or remove multiple events associated with a target. - if (typeof(type) !== "string") { // Has multiple events. - if (typeof(type.length) === "number") { // Handle multiple listeners glued together. - for (var n1 = 0, length1 = type.length; n1 < length1; n1++) { // Array [type] - event = eventManager(target, type[n1], listener, clone(configure), trigger); - if (event) - events[type[n1]] = event; - } - } else { // Handle multiple listeners. - for (var key in type) { // Object {type} - if (typeof(type[key]) === "function") { // without configuration. - event = eventManager(target, key, type[key], clone(configure), trigger); - } else { // with configuration. - event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); - } - if (event) - events[key] = event; - } - } - return createBatchCommands(events); - } - // Ensure listener is a function. - if (typeof(listener) !== "function") - return createError("Listener is not a function!"); - // Generate a unique wrapper identifier. - var useCapture = configure.useCapture || false; - var id = normalize(type) + getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); - // Handle the event. - if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event. - if (trigger === "remove") { // Remove event listener. - if (!wrappers[id]) - return; // Already removed. - wrappers[id].remove(); - delete wrappers[id]; - } else if (trigger === "add") { // Attach event listener. - if (wrappers[id]) - return wrappers[id]; // Already attached. - // Retains "this" orientation. - if (configure.useCall && !root.modifyEventListener) { - var tmp = listener; - listener = function(event, self) { - for (var key in self) - event[key] = self[key]; - return tmp.call(target, event); - }; - } - // Create listener proxy. - configure.gesture = type; - configure.target = target; - configure.listener = listener; - configure.fromOverwrite = fromOverwrite; - // Record wrapper. - wrappers[id] = root.proxy[type](configure); - } - } else { // Fire native event. - type = normalize(type); - if (trigger === "remove") { // Remove event listener. - if (!wrappers[id]) - return; // Already removed. - target[remove](type, listener, useCapture); - delete wrappers[id]; - } else if (trigger === "add") { // Attach event listener. - if (wrappers[id]) - return wrappers[id]; // Already attached. - target[add](type, listener, useCapture); - // Record wrapper. - wrappers[id] = { - type: type, - target: target, - listener: listener, - remove: function() { - root.remove(target, type, listener, configure); - } - }; - } - } - return wrappers[id]; - }; - -/// Perform batch actions on multiple events. - var createBatchCommands = function(events) { - return { - remove: function() { // Remove multiple events. - for (var key in events) { - events[key].remove(); - } - }, - add: function() { // Add multiple events. - for (var key in events) { - events[key].add(); - } - } - }; - }; - -/// Display error message in console. - var createError = function(message) { - if (typeof(console) === "undefined") - return; - if (typeof(console.error) === "undefined") - return; - console.error(message); - }; - -/// Handle naming discrepancies between platforms. - var normalize = (function() { - var translate = {}; - return function(type) { - if (!root.pointerType) { - if (window.navigator.msPointerEnabled) { - root.pointerType = "mspointer"; - translate = { - "mousedown": "MSPointerDown", - "mousemove": "MSPointerMove", - "mouseup": "MSPointerUp" - }; - } else if (root.supports("touchstart")) { - root.pointerType = "touch"; - translate = { - "mousedown": "touchstart", - "mouseup": "touchend", - "mousemove": "touchmove" - }; - } else { - root.pointerType = "mouse"; - } - } - if (translate[type]) - type = translate[type]; - if (!document.addEventListener) { // IE - return "on" + type; - } else { - return type; - } - }; - })(); - -/// Event wrappers to keep track of all events placed in the window. - var wrappers = {}; - var counter = 0; - var getID = function(object) { - if (object === window) - return "#window"; - if (object === document) - return "#document"; - if (!object) - return createError("Missing target on listener!"); - if (!object.uniqueID) - object.uniqueID = "id" + counter++; - return object.uniqueID; - }; - -/// Detect platforms native *EventListener command. - var add = document.addEventListener ? "addEventListener" : "attachEvent"; - var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; - - /* - Pointer.js - ------------------------ - Modified from; https://github.com/borismus/pointer.js - */ - - root.createPointerEvent = function(event, self, preventRecord) { - var eventName = self.gesture; - var target = self.target; - var pts = event.changedTouches || root.proxy.getCoords(event); - if (pts.length) { - var pt = pts[0]; - self.pointers = preventRecord ? [] : pts; - self.pageX = pt.pageX; - self.pageY = pt.pageY; - self.x = self.pageX; - self.y = self.pageY; - } - /// - var newEvent = document.createEvent("Event"); - newEvent.initEvent(eventName, true, true); - newEvent.originalEvent = event; - for (var k in self) { - if (k === "target") - continue; - newEvent[k] = self[k]; - } - target.dispatchEvent(newEvent); - }; - -/// Allows *EventListener to use custom event proxies. - if (root.modifyEventListener && window.HTMLElement) - (function() { - var augmentEventListener = function(proto) { - var recall = function(trigger) { // overwrite native *EventListener's - var handle = trigger + "EventListener"; - var handler = proto[handle]; - proto[handle] = function(type, listener, useCapture) { - if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. - var configure = useCapture; - if (typeof(useCapture) === "object") { - configure.useCall = true; - } else { // convert to configuration object. - configure = { - useCall: true, - useCapture: useCapture - }; - } - eventManager(this, type, listener, configure, trigger, true); - handler.call(this, type, listener, useCapture); - } else { // use native function. - handler.call(this, normalize(type), listener, useCapture); - } - }; - }; - recall("add"); - recall("remove"); - }; - // NOTE: overwriting HTMLElement doesn't do anything in Firefox. - if (navigator.userAgent.match(/Firefox/)) { - // TODO: fix Firefox for the general case. - augmentEventListener(HTMLDivElement.prototype); - augmentEventListener(HTMLCanvasElement.prototype); - } else { - augmentEventListener(HTMLElement.prototype); - } - augmentEventListener(document); - augmentEventListener(window); - })(); - -/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk. - if (root.modifySelectors) - (function() { - var proto = NodeList.prototype; - proto.removeEventListener = function(type, listener, useCapture) { - for (var n = 0, length = this.length; n < length; n++) { - this[n].removeEventListener(type, listener, useCapture); - } - }; - proto.addEventListener = function(type, listener, useCapture) { - for (var n = 0, length = this.length; n < length; n++) { - this[n].addEventListener(type, listener, useCapture); - } - }; - })(); - - return root; - -})(Event); -/* - ---------------------------------------------------- - Event.proxy : 0.4.2 : 2012/07/29 : MIT License - ---------------------------------------------------- - https://github.com/mudcube/Event.js - ---------------------------------------------------- - Pointer Gestures - ---------------------------------------------------- - 1 : click, dblclick, dbltap - 1+ : tap, taphold, drag, swipe - 2+ : pinch, rotate - ---------------------------------------------------- - Gyroscope Gestures - ---------------------------------------------------- - * shake - ---------------------------------------------------- - Fixes issues with - ---------------------------------------------------- - * mousewheel-Firefox uses DOMMouseScroll and does not return wheelDelta. - * devicemotion-Fixes issue where event.acceleration is not returned. - ---------------------------------------------------- - Ideas for the future - ---------------------------------------------------- - * Keyboard, GamePad, and other input abstractions. - * Event batching - i.e. for every x fingers down a new gesture is created. - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - /* - Create a new pointer gesture instance. - */ - - root.pointerSetup = function(conf, self) { - /// Configure. - conf.doc = conf.target.ownerDocument || conf.target; // Associated document. - conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers. - conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers. - conf.position = conf.position || "relative"; // Determines what coordinate system points are returned. - delete conf.fingers; //- - /// Convenience data. - self = self || {}; - self.gesture = conf.gesture; - self.target = conf.target; - self.pointerType = Event.pointerType; - /// - if (Event.modifyEventListener && conf.fromOverwrite) - conf.listener = Event.createPointerEvent; - /// Convenience commands. - var fingers = 0; - var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; - self.listener = conf.listener; - self.proxy = function(listener) { - self.defaultListener = conf.listener; - conf.listener = listener; - listener(conf.event, self); - }; - self.remove = function() { - if (conf.onPointerDown) - Event.remove(conf.target, type + "down", conf.onPointerDown); - if (conf.onPointerMove) - Event.remove(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp) - Event.remove(conf.doc, type + "up", conf.onPointerUp); - }; - self.resume = function(opt) { - if (conf.onPointerMove && (!opt || opt.move)) - Event.add(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp && (!opt || opt.move)) - Event.add(conf.doc, type + "up", conf.onPointerUp); - conf.fingers = fingers; - }; - self.pause = function(opt) { - fingers = conf.fingers; - if (conf.onPointerMove && (!opt || opt.move)) - Event.remove(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp && (!opt || opt.up)) - Event.remove(conf.doc, type + "up", conf.onPointerUp); - conf.fingers = 0; - }; - /// - return self; - }; - - /* - Begin proxied pointer command. - */ - - root.pointerStart = function(event, self, conf) { - var addTouchStart = function(touch, sid) { - var bbox = conf.bbox; - var pt = track[sid] = {}; - /// - switch (conf.position) { - case "absolute": // Absolute from within window. - pt.offsetX = 0; - pt.offsetY = 0; - break; - case "difference": // Relative from origin. - pt.offsetX = touch.pageX; - pt.offsetY = touch.pageY; - break; - case "move": // Move target element. - pt.offsetX = touch.pageX - bbox.x1; - pt.offsetY = touch.pageY - bbox.y1; - break; - default: // Relative from within target. - pt.offsetX = bbox.x1; - pt.offsetY = bbox.y1; - break; - } - /// - if (conf.position === "relative") { - var x = (touch.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; - var y = (touch.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; - } else { - var x = (touch.pageX - pt.offsetX); - var y = (touch.pageY - pt.offsetY); - } - /// - pt.rotation = 0; - pt.scale = 1; - pt.startTime = pt.moveTime = (new Date).getTime(); - pt.move = {x: x, y: y}; - pt.start = {x: x, y: y}; - /// - conf.fingers++; - }; - /// - conf.event = event; - if (self.defaultListener) { - conf.listener = self.defaultListener; - delete self.defaultListener; - } - /// - var isTouchStart = !conf.fingers; - var track = conf.tracker; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Adding touch events to tracking. - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; // Touch ID. - // Track the current state of the touches. - if (conf.fingers) { - if (conf.fingers >= conf.maxFingers) { - var ids = []; - for (var sid in conf.tracker) - ids.push(sid); - self.identifier = ids.join(","); - return isTouchStart; - } - var fingers = 0; // Finger ID. - for (var rid in track) { - // Replace removed finger. - if (track[rid].up) { - delete track[rid]; - addTouchStart(touch, sid); - conf.cancel = true; - break; - } - fingers++; - } - // Add additional finger. - if (track[sid]) - continue; - addTouchStart(touch, sid); - } else { // Start tracking fingers. - track = conf.tracker = {}; - self.bbox = conf.bbox = root.getBoundingBox(conf.target); - conf.fingers = 0; - conf.cancel = false; - addTouchStart(touch, sid); - } - } - /// - var ids = []; - for (var sid in conf.tracker) - ids.push(sid); - self.identifier = ids.join(","); - /// - return isTouchStart; - }; - - /* - End proxied pointer command. - */ - - root.pointerEnd = function(event, self, conf, onPointerUp) { - // Record changed touches have ended (iOS changedTouches is not reliable). - var touches = event.touches || []; - var length = touches.length; - var exists = {}; - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var sid = touch.identifier; - exists[sid || Infinity] = true; - } - for (var sid in conf.tracker) { - var track = conf.tracker[sid]; - if (exists[sid] || track.up) - continue; - if (onPointerUp) { // add changedTouches to mouse. - onPointerUp({ - pageX: track.pageX, - pageY: track.pageY, - changedTouches: [{ - pageX: track.pageX, - pageY: track.pageY, - identifier: sid === "Infinity" ? Infinity : sid - }] - }, "up"); - } - track.up = true; - conf.fingers--; - } - /* // This should work but fails in Safari on iOS4 so not using it. - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Record changed touches have ended (this should work). - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var track = conf.tracker[sid]; - if (track && !track.up) { - if (onPointerUp) { // add changedTouches to mouse. - onPointerUp({ - changedTouches: [{ - pageX: track.pageX, - pageY: track.pageY, - identifier: sid === "Infinity" ? Infinity : sid - }] - }, "up"); - } - track.up = true; - conf.fingers --; - } - } */ - // Wait for all fingers to be released. - if (conf.fingers !== 0) - return false; - // Record total number of fingers gesture used. - var ids = []; - conf.gestureFingers = 0; - for (var sid in conf.tracker) { - conf.gestureFingers++; - ids.push(sid); - } - self.identifier = ids.join(","); - // Our pointer gesture has ended. - return true; - }; - - /* - Returns mouse coords in an array to match event.*Touches - ------------------------------------------------------------ - var touch = event.changedTouches || root.getCoords(event); - */ - - root.getCoords = function(event) { - if (typeof(event.pageX) !== "undefined") { // Desktop browsers. - root.getCoords = function(event) { - return Array({ - type: "mouse", - x: event.pageX, - y: event.pageY, - pageX: event.pageX, - pageY: event.pageY, - identifier: Infinity - }); - }; - } else { // Internet Explorer <= 8.0 - root.getCoords = function(event) { - event = event || window.event; - return Array({ - type: "mouse", - x: event.clientX + document.documentElement.scrollLeft, - y: event.clientY + document.documentElement.scrollTop, - pageX: event.clientX + document.documentElement.scrollLeft, - pageY: event.clientY + document.documentElement.scrollTop, - identifier: Infinity - }); - }; - } - return root.getCoords(event); - }; - - /* - Returns single coords in an object. - ------------------------------------------------------------ - var mouse = root.getCoord(event); - */ - - root.getCoord = function(event) { - if ("ontouchstart" in window) { // Mobile browsers. - var pX = 0; - var pY = 0; - root.getCoord = function(event) { - var touches = event.changedTouches; - if (touches.length) { // ontouchstart + ontouchmove - return { - x: pX = touches[0].pageX, - y: pY = touches[0].pageY - }; - } else { // ontouchend - return { - x: pX, - y: pY - }; - } - }; - } else if (typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers. - root.getCoord = function(event) { - return { - x: event.pageX, - y: event.pageY - }; - }; - } else { // Internet Explorer <=8.0 - root.getCoord = function(event) { - event = event || window.event; - return { - x: event.clientX + document.documentElement.scrollLeft, - y: event.clientY + document.documentElement.scrollTop - }; - }; - } - return root.getCoord(event); - }; - - /* - Get target scale and position in space. - */ - - root.getBoundingBox = function(o) { - if (o === window || o === document) - o = document.body; - /// - var bbox = { - x1: 0, - y1: 0, - x2: 0, - y2: 0, - scrollLeft: 0, - scrollTop: 0 - }; - /// - if (o === document.body) { - bbox.height = window.innerHeight; - bbox.width = window.innerWidth; - } else { - bbox.height = o.offsetHeight; - bbox.width = o.offsetWidth; - } - /// Get the scale of the element. - bbox.scaleX = o.width / bbox.width || 1; - bbox.scaleY = o.height / bbox.height || 1; - /// Get the offset of element. - var tmp = o; - while (tmp !== null) { - bbox.x1 += tmp.offsetLeft; - bbox.y1 += tmp.offsetTop; - tmp = tmp.offsetParent; - } - ; - /// Get the scroll of container element. - var tmp = o.parentNode; - while (tmp !== null) { - if (tmp === document.body) - break; - if (tmp.scrollTop === undefined) - break; - bbox.scrollLeft += tmp.scrollLeft; - bbox.scrollTop += tmp.scrollTop; - tmp = tmp.parentNode; - } - ; - /// Record the extent of box. - bbox.x2 = bbox.x1 + bbox.width; - bbox.y2 = bbox.y1 + bbox.height; - /// - return bbox; - }; - - /* - Keep track of metaKey, the proper ctrlKey for users platform. - */ - - (function() { - var agent = navigator.userAgent.toLowerCase(); - var mac = agent.indexOf("macintosh") !== -1; - if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari. - var watch = {91: true, 93: true}; - } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox. - var watch = {224: true}; - } else { // windows, linux, or mac opera. - var watch = {17: true}; - } - root.isMetaKey = function(event) { - return !!watch[event.keyCode]; - }; - root.metaTracker = function(event) { - if (watch[event.keyCode]) { - root.metaKey = event.type === "keydown"; - } - }; - })(); - - return root; - -})(Event.proxy); -/* - "Click" event proxy. - ---------------------------------------------------- - Event.add(window, "click", function(event, self) {}); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.click = function(conf) { - conf.maxFingers = conf.maxFingers || conf.fingers || 1; - // Setting up local variables. - var EVENT; - // Tracking the events. - conf.onPointerDown = function(event) { - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function(event) { - EVENT = event; - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - if (EVENT.cancelBubble && ++EVENT.bubble > 1) - return; - var pointers = EVENT.changedTouches || root.getCoords(EVENT); - var pointer = pointers[0]; - var bbox = conf.bbox; - var newbbox = root.getBoundingBox(conf.target); - if (conf.position === "relative") { - var ax = (pointer.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; - var ay = (pointer.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; - } else { - var ax = (pointer.pageX - bbox.x1); - var ay = (pointer.pageY - bbox.y1); - } - if (ax > 0 && ax < bbox.width && // Within target coordinates. - ay > 0 && ay < bbox.height && - bbox.scrollTop === newbbox.scrollTop) { - conf.listener(EVENT, self); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - self.state = "click"; - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.click = root.click; - - return root; - -})(Event.proxy); -/* - "Double-Click" aka "Double-Tap" event proxy. - ---------------------------------------------------- - Event.add(window, "dblclick", function(event, self) {}); - ---------------------------------------------------- - Touch an target twice for <= 700ms, with less than 25 pixel drift. - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.dbltap = - root.dblclick = function(conf) { - conf.maxFingers = conf.maxFingers || conf.fingers || 1; - // Setting up local variables. - var delay = 700; // in milliseconds - var time0, time1, timeout; - var pointer0, pointer1; - // Tracking the events. - conf.onPointerDown = function(event) { - var pointers = event.changedTouches || root.getCoords(event); - if (time0 && !time1) { // Click #2 - pointer1 = pointers[0]; - time1 = (new Date).getTime() - time0; - } else { // Click #1 - pointer0 = pointers[0]; - time0 = (new Date).getTime(); - time1 = 0; - clearTimeout(timeout); - timeout = setTimeout(function() { - time0 = 0; - }, delay); - } - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function(event) { - if (time0 && !time1) { - var pointers = event.changedTouches || root.getCoords(event); - pointer1 = pointers[0]; - } - var bbox = conf.bbox; - if (conf.position === "relative") { - var ax = (pointer1.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; - var ay = (pointer1.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; - } else { - var ax = (pointer1.pageX - bbox.x1); - var ay = (pointer1.pageY - bbox.y1); - } - if (!(ax > 0 && ax < bbox.width && // Within target coordinates.. - ay > 0 && ay < bbox.height && - Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance. - Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { - // Cancel out this listener. - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - clearTimeout(timeout); - time0 = time1 = 0; - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - if (time0 && time1) { - if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { - self.state = conf.gesture; - conf.listener(event, self); - } - clearTimeout(timeout); - time0 = time1 = 0; - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - self.state = "dblclick"; - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.dbltap = root.dbltap; - Event.Gesture._gestureHandlers.dblclick = root.dblclick; - - return root; - -})(Event.proxy); -/* - "Drag" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: maxFingers, position. - ---------------------------------------------------- - Event.add(window, "drag", function(event, self) { - console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.dragElement = function(that, event) { - root.drag({ - event: event, - target: that, - position: "move", - listener: function(event, self) { - that.style.left = self.x + "px"; - that.style.top = self.y + "px"; - Event.prevent(event); - } - }); - }; - - root.drag = function(conf) { - conf.gesture = "drag"; - conf.onPointerDown = function(event) { - if (root.pointerStart(event, self, conf)) { - if (!conf.monitor) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - } - // Process event listener. - conf.onPointerMove(event, "down"); - }; - conf.onPointerMove = function(event, state) { - if (!conf.tracker) - return conf.onPointerDown(event); - var bbox = conf.bbox; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var identifier = touch.identifier || Infinity; - var pt = conf.tracker[identifier]; - // Identifier defined outside of listener. - if (!pt) - continue; - pt.pageX = touch.pageX; - pt.pageY = touch.pageY; - // Record data. - self.state = state || "move"; - self.identifier = identifier; - self.start = pt.start; - self.fingers = conf.fingers; - if (conf.position === "relative") { - self.x = (pt.pageX + bbox.scrollLeft - pt.offsetX) * bbox.scaleX; - self.y = (pt.pageY + bbox.scrollTop - pt.offsetY) * bbox.scaleY; - } else { - self.x = (pt.pageX - pt.offsetX); - self.y = (pt.pageY - pt.offsetY); - } - /// - conf.listener(event, self); - } - }; - conf.onPointerUp = function(event) { - // Remove tracking for touch. - if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { - if (!conf.monitor) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - if (conf.event) { - conf.onPointerDown(conf.event); - } else { // - Event.add(conf.target, "mousedown", conf.onPointerDown); - if (conf.monitor) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - } - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.drag = root.drag; - - return root; - -})(Event.proxy); -/* - "Gesture" event proxy (2+ fingers). - ---------------------------------------------------- - CONFIGURE: minFingers, maxFingers. - ---------------------------------------------------- - Event.add(window, "gesture", function(event, self) { - console.log(self.rotation, self.scale, self.fingers, self.state); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - var RAD_DEG = Math.PI / 180; - - root.gesture = function(conf) { - conf.minFingers = conf.minFingers || conf.fingers || 2; - // Tracking the events. - conf.onPointerDown = function(event) { - var fingers = conf.fingers; - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - // Record gesture start. - if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { - self.fingers = conf.minFingers; - self.scale = 1; - self.rotation = 0; - self.state = "start"; - var sids = ""; //- FIXME(mud): can generate duplicate IDs. - for (var key in conf.tracker) - sids += key; - self.identifier = parseInt(sids); - conf.listener(event, self); - } - }; - /// - conf.onPointerMove = function(event, state) { - var bbox = conf.bbox; - var points = conf.tracker; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Update tracker coordinates. - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var pt = points[sid]; - // Check whether "pt" is used by another gesture. - if (!pt) - continue; - // Find the actual coordinates. - if (conf.position === "relative") { - pt.move.x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; - pt.move.y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; - } else { - pt.move.x = (touch.pageX - bbox.x1); - pt.move.y = (touch.pageY - bbox.y1); - } - } - /// - if (conf.fingers < conf.minFingers) - return; - /// - var touches = []; - var scale = 0; - var rotation = 0; - /// Calculate centroid of gesture. - var centroidx = 0; - var centroidy = 0; - var length = 0; - for (var sid in points) { - var touch = points[sid]; - if (touch.up) - continue; - centroidx += touch.move.x; - centroidy += touch.move.y; - length++; - } - centroidx /= length; - centroidy /= length; - /// - for (var sid in points) { - var touch = points[sid]; - if (touch.up) - continue; - var start = touch.start; - if (!start.distance) { - var dx = start.x - centroidx; - var dy = start.y - centroidy; - start.distance = Math.sqrt(dx * dx + dy * dy); - start.angle = Math.atan2(dx, dy) / RAD_DEG; - } - // Calculate scale. - var dx = touch.move.x - centroidx; - var dy = touch.move.y - centroidy; - var distance = Math.sqrt(dx * dx + dy * dy); - scale += distance / start.distance; - // Calculate rotation. - var angle = Math.atan2(dx, dy) / RAD_DEG; - var rotate = (start.angle - angle + 360) % 360 - 180; - touch.DEG2 = touch.DEG1; // Previous degree. - touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree. - if (typeof(touch.DEG2) !== "undefined") { - if (rotate > 0) { - touch.rotation += touch.DEG1 - touch.DEG2; - } else { - touch.rotation -= touch.DEG1 - touch.DEG2; - } - rotation += touch.rotation; - } - // Attach current points to self. - touches.push(touch.move); - } - /// - self.touches = touches; - self.fingers = conf.fingers; - self.scale = scale / conf.fingers; - self.rotation = rotation / conf.fingers; - self.state = "change"; - conf.listener(event, self); - }; - conf.onPointerUp = function(event) { - // Remove tracking for touch. - var fingers = conf.fingers; - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - // Check whether fingers has dropped below minFingers. - if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { - self.fingers = conf.fingers; - self.state = "end"; - conf.listener(event, self); - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.gesture = root.gesture; - - return root; - -})(Event.proxy); -/* - "Pointer" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: minFingers, maxFingers. - ---------------------------------------------------- - Event.add(window, "gesture", function(event, self) { - console.log(self.rotation, self.scale, self.fingers, self.state); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.pointerdown = - root.pointermove = - root.pointerup = function(conf) { - if (conf.target.isPointerEmitter) - return; - // Tracking the events. - var isDown = true; - conf.onPointerDown = function(event) { - isDown = false; - self.gesture = "pointerdown"; - conf.listener(event, self); - }; - conf.onPointerMove = function(event) { - self.gesture = "pointermove"; - conf.listener(event, self, isDown); - }; - conf.onPointerUp = function(event) { - isDown = true; - self.gesture = "pointerup"; - conf.listener(event, self, true); - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - Event.add(conf.target, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - // Return this object. - conf.target.isPointerEmitter = true; - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; - Event.Gesture._gestureHandlers.pointermove = root.pointermove; - Event.Gesture._gestureHandlers.pointerup = root.pointerup; - - return root; - -})(Event.proxy); -/* - "Device Motion" and "Shake" event proxy. - ---------------------------------------------------- - http://developer.android.com/reference/android/hardware/SensorEvent.html#values - ---------------------------------------------------- - Event.add(window, "shake", function(event, self) {}); - Event.add(window, "devicemotion", function(event, self) { - console.log(self.acceleration, self.accelerationIncludingGravity); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.shake = function(conf) { - // Externally accessible data. - var self = { - gesture: "devicemotion", - acceleration: {}, - accelerationIncludingGravity: {}, - target: conf.target, - listener: conf.listener, - remove: function() { - window.removeEventListener('devicemotion', onDeviceMotion, false); - } - }; - // Setting up local variables. - var threshold = 4; // Gravitational threshold. - var timeout = 1000; // Timeout between shake events. - var timeframe = 200; // Time between shakes. - var shakes = 3; // Minimum shakes to trigger event. - var lastShake = (new Date).getTime(); - var gravity = {x: 0, y: 0, z: 0}; - var delta = { - x: {count: 0, value: 0}, - y: {count: 0, value: 0}, - z: {count: 0, value: 0} - }; - // Tracking the events. - var onDeviceMotion = function(e) { - var alpha = 0.8; // Low pass filter. - var o = e.accelerationIncludingGravity; - gravity.x = alpha * gravity.x + (1 - alpha) * o.x; - gravity.y = alpha * gravity.y + (1 - alpha) * o.y; - gravity.z = alpha * gravity.z + (1 - alpha) * o.z; - self.accelerationIncludingGravity = gravity; - self.acceleration.x = o.x - gravity.x; - self.acceleration.y = o.y - gravity.y; - self.acceleration.z = o.z - gravity.z; - /// - if (conf.gesture === "devicemotion") { - conf.listener(e, self); - return; - } - var data = "xyz"; - var now = (new Date).getTime(); - for (var n = 0, length = data.length; n < length; n++) { - var letter = data[n]; - var ACCELERATION = self.acceleration[letter]; - var DELTA = delta[letter]; - var abs = Math.abs(ACCELERATION); - /// Check whether another shake event was recently registered. - if (now - lastShake < timeout) - continue; - /// Check whether delta surpasses threshold. - if (abs > threshold) { - var idx = now * ACCELERATION / abs; - var span = Math.abs(idx + DELTA.value); - // Check whether last delta was registered within timeframe. - if (DELTA.value && span < timeframe) { - DELTA.value = idx; - DELTA.count++; - // Check whether delta count has enough shakes. - if (DELTA.count === shakes) { - conf.listener(e, self); - // Reset tracking. - lastShake = now; - DELTA.value = 0; - DELTA.count = 0; - } - } else { - // Track first shake. - DELTA.value = idx; - DELTA.count = 1; - } - } - } - }; - // Attach events. - if (!window.addEventListener) - return; - window.addEventListener('devicemotion', onDeviceMotion, false); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.shake = root.shake; - - return root; - -})(Event.proxy); -/* - "Swipe" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: snap, threshold, maxFingers. - ---------------------------------------------------- - Event.add(window, "swipe", function(event, self) { - console.log(self.velocity, self.angle); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - var RAD_DEG = Math.PI / 180; - - root.swipe = function(conf) { - conf.snap = conf.snap || 90; // angle snap. - conf.threshold = conf.threshold || 1; // velocity threshold. - // Tracking the events. - conf.onPointerDown = function(event) { - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function(event) { - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var o = conf.tracker[sid]; - // Identifier defined outside of listener. - if (!o) - continue; - o.move.x = touch.pageX; - o.move.y = touch.pageY; - o.moveTime = (new Date).getTime(); - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - /// - var velocity1; - var velocity2 - var degree1; - var degree2; - /// Calculate centroid of gesture. - var start = {x: 0, y: 0}; - var endx = 0; - var endy = 0; - var length = 0; - /// - for (var sid in conf.tracker) { - var touch = conf.tracker[sid]; - var xdist = touch.move.x - touch.start.x; - var ydist = touch.move.y - touch.start.y; - - endx += touch.move.x; - endy += touch.move.y; - start.x += touch.start.x; - start.y += touch.start.y; - length++; - - - var distance = Math.sqrt(xdist * xdist + ydist * ydist); - var ms = touch.moveTime - touch.startTime; - var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; - var velocity2 = ms ? distance / ms : 0; - if (typeof(degree1) === "undefined") { - degree1 = degree2; - velocity1 = velocity2; - } else if (Math.abs(degree2 - degree1) <= 20) { - degree1 = (degree1 + degree2) / 2; - velocity1 = (velocity1 + velocity2) / 2; - } else { - return; - } - } - /// - if (velocity1 > conf.threshold) { - start.x /= length; - start.y /= length; - self.start = start; - self.x = endx / length; - self.y = endy / length; - self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360); - self.velocity = velocity1; - self.fingers = conf.gestureFingers; - self.state = "swipe"; - conf.listener(event, self); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.swipe = root.swipe; - - return root; - -})(Event.proxy); -/* - "Tap" and "Longpress" event proxy. - ---------------------------------------------------- - CONFIGURE: delay (longpress), timeout (tap). - ---------------------------------------------------- - Event.add(window, "tap", function(event, self) { - console.log(self.fingers); - }); - ---------------------------------------------------- - multi-finger tap // touch an target for <= 250ms. - multi-finger longpress // touch an target for >= 500ms - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.tap = - root.longpress = function(conf) { - conf.delay = conf.delay || 500; - conf.timeout = conf.timeout || 250; - // Setting up local variables. - var timestamp, timeout; - // Tracking the events. - conf.onPointerDown = function(event) { - if (root.pointerStart(event, self, conf)) { - timestamp = (new Date).getTime(); - // Initialize event listeners. - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - // Make sure this is a "longpress" event. - if (conf.gesture !== "longpress") - return; - timeout = setTimeout(function() { - if (event.cancelBubble && ++event.bubble > 1) - return; - // Make sure no fingers have been changed. - var fingers = 0; - for (var key in conf.tracker) { - if (conf.tracker[key].end === true) - return; - if (conf.cancel) - return; - fingers++; - } - // Send callback. - self.state = "start"; - self.fingers = fingers; - conf.listener(event, self); - }, conf.delay); - } - }; - conf.onPointerMove = function(event) { - var bbox = conf.bbox; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i++) { - var touch = touches[i]; - var identifier = touch.identifier || Infinity; - var pt = conf.tracker[identifier]; - if (!pt) - continue; - if (conf.position === "relative") { - var x = (touch.pageX + bbox.scrollLeft - bbox.x1) * bbox.scaleX; - var y = (touch.pageY + bbox.scrollTop - bbox.y1) * bbox.scaleY; - } else { - var x = (touch.pageX - bbox.x1); - var y = (touch.pageY - bbox.y1); - } - if (!(x > 0 && x < bbox.width && // Within target coordinates.. - y > 0 && y < bbox.height && - Math.abs(x - pt.start.x) <= 25 && // Within drift deviance. - Math.abs(y - pt.start.y) <= 25)) { - // Cancel out this listener. - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - conf.cancel = true; - return; - } - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - clearTimeout(timeout); - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - if (event.cancelBubble && ++event.bubble > 1) - return; - // Callback release on longpress. - if (conf.gesture === "longpress") { - if (self.state === "start") { - self.state = "end"; - conf.listener(event, self); - } - return; - } - // Cancel event due to movement. - if (conf.cancel) - return; - // Ensure delay is within margins. - if ((new Date).getTime() - timestamp > conf.timeout) - return; - // Send callback. - self.state = "tap"; - self.fingers = conf.gestureFingers; - conf.listener(event, self); - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.tap = root.tap; - Event.Gesture._gestureHandlers.longpress = root.longpress; - - return root; - -})(Event.proxy); -/* - "Mouse Wheel" event proxy. - ---------------------------------------------------- - Event.add(window, "wheel", function(event, self) { - console.log(self.state, self.wheelDelta); - }); - */ - -if (typeof(Event) === "undefined") - var Event = {}; -if (typeof(Event.proxy) === "undefined") - Event.proxy = {}; - -Event.proxy = (function(root) { - "use strict"; - - root.wheel = function(conf) { - // Configure event listener. - var interval; - var timeout = conf.timeout || 150; - var count = 0; - // Externally accessible data. - var self = { - gesture: "wheel", - state: "start", - wheelDelta: 0, - target: conf.target, - listener: conf.listener, - remove: function() { - conf.target[remove](type, onMouseWheel, false); - } - }; - // Tracking the events. - var onMouseWheel = function(event) { - event = event || window.event; - self.state = count++ ? "change" : "start"; - self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; - conf.listener(event, self); - clearTimeout(interval); - interval = setTimeout(function() { - count = 0; - self.state = "end"; - self.wheelDelta = 0; - conf.listener(event, self); - }, timeout); - }; - // Attach events. - var add = document.addEventListener ? "addEventListener" : "attachEvent"; - var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; - var type = Event.supports("mousewheel") ? "mousewheel" : "DOMMouseScroll"; - conf.target[add](type, onMouseWheel, false); - // Return this object. - return self; - }; - - Event.Gesture = Event.Gesture || {}; - Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; - Event.Gesture._gestureHandlers.wheel = root.wheel; - - return root; - -})(Event.proxy); - - -/** - * Wrapper around `console.log` (when available) - * @param {Any} values Values to log - */ -fabric.log = function() { }; -======= // DOM L0 branch handlers = { }, ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f addListener, removeListener; @@ -3727,53 +5399,9 @@ fabric.log = function() { }; element.className += (element.className ? ' ' : '') + className; } } - - /** - * Apply transform t to point p - * @static - * @memberOf fabric.util - * @param {fabric.Point} p The point to transform - * @param {Array} t The transform - * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied - * @return {fabric.Point} The transformed point - */ - function transformPoint(p, t, ignoreOffset) { - if (ignoreOffset) { - return new fabric.Point( - t[0] * p.x + t[1] * p.y, - t[2] * p.x + t[3] * p.y - ); - } - return new fabric.Point( - t[0] * p.x + t[1] * p.y + t[4], - t[2] * p.x + t[3] * p.y + t[5] - ); - } /** -<<<<<<< HEAD - * Invert transformation t - * @static - * @memberOf fabric.util - * @param {Array} t The transform - * @return {Array} The inverted transform - */ - function invertTransform(t) { - var r = t.slice(), - a = 1 / (t[0] * t[3] - t[1] * t[2]); - r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; - var o = transformPoint({x: t[4], y: t[5]}, r); - r[4] = -o.x; - r[5] = -o.y; - return r - } - - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static -======= * Wraps element with another element ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f * @memberOf fabric.util * @param {HTMLElement} element Element to wrap * @param {HTMLElement|String} wrapper Element to wrap with @@ -4074,37 +5702,11 @@ fabric.log = function() { }; */ fabric.log = function() { }; -<<<<<<< HEAD - fabric.util.removeFromArray = removeFromArray; - fabric.util.degreesToRadians = degreesToRadians; - fabric.util.radiansToDegrees = radiansToDegrees; - fabric.util.rotatePoint = rotatePoint; - fabric.util.transformPoint = transformPoint; - fabric.util.invertTransform = invertTransform; - fabric.util.toFixed = toFixed; - fabric.util.getRandomInt = getRandomInt; - fabric.util.falseFunction = falseFunction; - fabric.util.getKlass = getKlass; - fabric.util.resolveNamespace = resolveNamespace; - fabric.util.loadImage = loadImage; - fabric.util.enlivenObjects = enlivenObjects; - fabric.util.groupSVGElements = groupSVGElements; - fabric.util.populateWithProperties = populateWithProperties; - fabric.util.drawDashedLine = drawDashedLine; - fabric.util.createCanvasElement = createCanvasElement; - fabric.util.createImage = createImage; - fabric.util.createAccessors = createAccessors; - fabric.util.clipContext = clipContext; - fabric.util.multiplyTransformMatrices = multiplyTransformMatrices; - fabric.util.getFunctionBody = getFunctionBody; - fabric.util.drawArc = drawArc; -======= /** * Wrapper around `console.warn` (when available) * @param {Any} Values to log as a warning */ fabric.warn = function() { }; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f if (typeof console !== 'undefined') { ['log', 'warn'].forEach(function(methodName) { @@ -5746,167 +7348,167 @@ fabric.ElementsParser = { })(typeof exports !== 'undefined' ? exports : this); -(function(global) { - - "use strict"; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (u_b !== 0) { - var ua = ua_t / u_b, - ub = ub_t / u_b; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection("Intersection"); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (ua_t === 0 || ub_t === 0) { - result = new Intersection("Coincident"); - } - else { - result = new Intersection("Parallel"); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i+1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i+1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Intersection) { + fabric.warn('fabric.Intersection is already defined'); + return; + } + + /** + * Intersection class + * @class fabric.Intersection + * @memberOf fabric + * @constructor + */ + function Intersection(status) { + this.status = status; + this.points = []; + } + + fabric.Intersection = Intersection; + + fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { + + /** + * Appends a point to intersection + * @param {fabric.Point} point + */ + appendPoint: function (point) { + this.points.push(point); + }, + + /** + * Appends points to intersection + * @param {Array} points + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + } + }; + + /** + * Checks if one line intersects another + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {fabric.Point} b1 + * @param {fabric.Point} b2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (u_b !== 0) { + var ua = ua_t / u_b, + ub = ub_t / u_b; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection("Intersection"); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection(); + } + } + else { + if (ua_t === 0 || ub_t === 0) { + result = new Intersection("Coincident"); + } + else { + result = new Intersection("Parallel"); + } + } + return result; + }; + + /** + * Checks if line intersects polygon + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {Array} points + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ + var result = new Intersection(), + length = points.length; + + for (var i = 0; i < length; i++) { + var b1 = points[i], + b2 = points[(i+1) % length], + inter = Intersection.intersectLineLine(a1, a2, b1, b2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * Checks if polygon intersects another polygon + * @static + * @param {Array} points1 + * @param {Array} points2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection(), + length = points1.length; + + for (var i = 0; i < length; i++) { + var a1 = points1[i], + a2 = points1[(i+1) % length], + inter = Intersection.intersectLinePolygon(a1, a2, points2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * Checks if polygon intersects rectangle + * @static + * @param {Array} points + * @param {Number} r1 + * @param {Number} r2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection(); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + +})(typeof exports !== 'undefined' ? exports : this); (function(global) { @@ -6600,169 +8202,6 @@ fabric.ElementsParser = { this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2); } -<<<<<<< HEAD -(function(global) { - - "use strict"; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (u_b !== 0) { - var ua = ua_t / u_b, - ub = ub_t / u_b; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection("Intersection"); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (ua_t === 0 || ub_t === 0) { - result = new Intersection("Coincident"); - } - else { - result = new Intersection("Parallel"); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i+1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i+1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = "Intersection"; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); -======= for (var i = 0, len = this.colorStops.length; i < len; i++) { var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, @@ -7063,7 +8502,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return ctx.createPattern(source, this.repeat); } }); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f (function(global) { @@ -7367,6 +8805,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ allowTouchScrolling: false, + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: [1, 0, 0, 1, 0, 0], + /** * Callback; invoked right before object is about to be scaled/rotated * @param {fabric.Object} target Object that's about to be scaled/rotated @@ -7742,6 +9187,91 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this; }, + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + + /** + * Returns point at center of viewport + * @return {fabric.Point} the top left corner of the viewport + */ + getViewportCenter: function () { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ), + x = this.viewportTransform[4], + y = this.viewportTransform[5]; + + return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + }, + + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + this.viewportTransform = vpt + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + // TODO: just change the scale, preserve other transformations + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Centers viewport of this canvas instance on given point + * @param {Numer} x value for center of viewport + * @param {Numer} y value for center of viewport + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportCenter: function (x, y) { + var wh = fabric.util.transformPoint( + new fabric.Point(this.getWidth(), this.getHeight()), + this.viewportTransform + ); + this.viewportTransform[4] = x - wh.x/2; + this.viewportTransform[5] = y - wh.y/2; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Centers viewport of this canvas instance + * @return {fabric.Canvas} instance + * @chainable true + */ + centerViewport: function () { + return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + }, + /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} @@ -7793,8 +9323,16 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); - obj.setCoords(); obj.canvas = this; + if (obj._objects) { + obj._calcBounds(); + for (var i = 0, len = obj._objects.length; i < len; i++) { + obj._objects[i].canvas = this; + this._onObjectAdded(obj._objects[i]); + } + obj._updateObjectsCoords() + } + obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); }, @@ -8362,23 +9900,11 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ }, /** -<<<<<<< HEAD - * The transformation (in the format of Canvas transform) which focuses the viewport - * @type Array - * @default - */ - viewportTransform: [1, 0, 0, 1, 0, 0], - - /** - * Callback; invoked right before object is about to be scaled/rotated - * @param {fabric.Object} target Object that's about to be scaled/rotated -======= * Moves an object down in stack of drawn objects * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Canvas} thisArg * @chainable ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f */ sendBackwards: function (object, intersecting) { var idx = this._objects.indexOf(object); @@ -8607,82 +10133,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ width: 1, -<<<<<<< HEAD - /** - * Returns canvas zoom level - * @return {Number} - */ - getZoom: function () { - return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); - }, - - /** - * Returns point at center of viewport - * @return {fabric.Point} the top left corner of the viewport - */ - getViewportCenter: function () { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ), - x = this.viewportTransform[4], - y = this.viewportTransform[5]; - - return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); - }, - - /** - * Sets zoom level of this canvas instance - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - setZoom: function (value) { - // TODO: just change the scale, preserve other transformations - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Centers viewport of this canvas instance on given point - * @param {Numer} x value for center of viewport - * @param {Numer} y value for center of viewport - * @return {fabric.Canvas} instance - * @chainable true - */ - setViewportCenter: function (x, y) { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ); - this.viewportTransform[4] = x - wh.x/2; - this.viewportTransform[5] = y - wh.y/2; - this.renderAll(); - return this; - }, - - /** - * Centers viewport of this canvas instance - * @return {fabric.Canvas} instance - * @chainable true - */ - centerViewport: function () { - return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); - }, - - /** - * Returns <canvas> element corresponding to this instance - * @return {HTMLCanvasElement} - */ - getElement: function () { - return this.lowerCanvasEl; - }, -======= /** * Shadow object representing shadow of this shape. * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), @@ -8691,7 +10141,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @default */ shadow: null, ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f /** * Line endings style of a brush (one of "butt", "round", "square") @@ -8797,27 +10246,12 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * Inovoked on mouse move * @param {Object} pointer */ -<<<<<<< HEAD - _onObjectAdded: function(obj) { - this.stateful && obj.setupState(); - obj.canvas = this; - obj.setCoords(); - if (obj._objects) { - for (var i = 0, len = obj._objects.length; i < len; i++) { - obj._objects[i].canvas = this; - obj._objects[i].setCoords(); - } - } - this.fire('object:added', { target: obj }); - obj.fire('added'); -======= onMouseMove: function(pointer) { this._captureDrawingPath(pointer); // redraw curve // clear top canvas this.canvas.clearContext(this.canvas.contextTop); this._render(); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f }, /** @@ -8880,6 +10314,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _render: function() { var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); var p1 = this._points[0]; @@ -8909,6 +10346,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); + ctx.restore(); }, /** @@ -9083,11 +10521,17 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var point = this.addPoint(pointer); var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); + + ctx.restore(); }, /** @@ -9120,12 +10564,12 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric for (var i = 0, len = this.points.length; i < len; i++) { var point = this.points[i]; var circle = new fabric.Circle({ - radius: point.radius, + radius: this.points[i].radius, left: point.x, top: point.y, originX: 'center', originY: 'center', - fill: point.fill + fill: this.points[i].fill }); this.shadow && circle.setShadow(this.shadow); @@ -9133,6 +10577,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric circles.push(circle); } var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -9281,6 +10726,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; + this.canvas.add(group); this.canvas.fire('path:created', { path: group }); @@ -9315,7 +10762,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric render: function() { var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; + + var v = this.canvas.viewportTransform; ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; @@ -9349,32 +10799,16 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric else { width = this.dotWidth; } - - var point = { x: x, y: y, width: width }; + + var point = new fabric.Point(x, y); + point.width = width if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; } -<<<<<<< HEAD - /** - * Return an SVG path based on our captured points and their bounding box - * - * @private - */ - _getSVGPathData: function() { - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); - for (var i = 0, len = this._points.length; i < len; i++) { - this._points[i] = fabric.util.transformPoint(this._points[i], ivt); - } - this.box = this.getPathBoundingBox(this._points); - return this.convertPointsToSVGPath( - this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); - }, -======= this.sprayChunkPoints.push(point); } ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f this.sprayChunks.push(this.sprayChunkPoints); } @@ -9438,14 +10872,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab }); -<<<<<<< HEAD - circles.push(circle); - } - var group = new fabric.Group(circles); - group.canvas = this.canvas; -======= (function() { ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, @@ -9638,16 +11065,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); -<<<<<<< HEAD - var group = new fabric.Group(rects); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); -======= this.calcOffset(); }, ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f /** * Resets the current transform to its original values and chooses the type of resizing based on the event @@ -9690,53 +11109,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab t.originY = 'center'; } } -<<<<<<< HEAD - } - var uniqueRectsArray = [ ]; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - - /** - * Renders brush - */ - render: function() { - var ctx = this.canvas.contextTop; - ctx.fillStyle = this.color; - ctx.save(); - var ivt = fabric.util.invertTransform(this.canvas.viewportTransform); - - for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { - var point = this.sprayChunkPoints[i]; - var tpoint = fabric.util.transformPoint({x: point.x, y: point.y}, ivt); - point.x = tpoint.x; - point.y = tpoint.y; - if (typeof point.opacity !== 'undefined') { - ctx.globalAlpha = point.opacity; - } - ctx.fillRect(point.x, point.y, point.width, point.width); - } - ctx.restore(); - }, - - /** - * @param {Object} pointer - */ - addSprayChunk: function(pointer) { - this.sprayChunkPoints = [ ]; - - var x, y, width, radius = this.width / 2; - var vpt = this.canvas.viewportTransform; -======= else { t.originX = t.original.originX; t.originY = t.original.originY; } }, ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f /** * Checks if point is contained within an area of given object @@ -9745,7 +11122,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target) { - var pointer = this.getPointer(e), + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html @@ -9753,39 +11130,26 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset)); }, -<<<<<<< HEAD - if (this.dotWidthVariance) { - width = fabric.util.getRandomInt( - // bottom clamp width to 1 - Math.max(1, this.dotWidth - this.dotWidthVariance), - this.dotWidth + this.dotWidthVariance); - } - else { - width = this.dotWidth; - } - - var point = new fabric.Point(x, y); - point = fabric.util.transformPoint(point, vpt); - point.width = width -======= /** * @private */ _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), x = pointer.x, - y = pointer.y; + y = pointer.y, + lt; var isObjectInGroup = ( activeGroup && object.type !== 'group' && activeGroup.contains(object) ); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f if (isObjectInGroup) { - x -= activeGroup.left; - y -= activeGroup.top; + lt = new fabric.Point(activeGroup.left, activeGroup.top); + lt = fabric.util.transformPoint(lt, this.viewportTransform, true); + x -= lt.x; + y -= lt.y; } return { x: x, y: y }; }, @@ -9916,7 +11280,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (!target) return; var corner = target._findTargetCorner(e, this._offset), - pointer = getPointer(e, target.canvas.upperCanvasEl), + pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); @@ -10032,13 +11399,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab /** * @private */ -<<<<<<< HEAD - containsPoint: function (e, target) { - var pointer = this.getPointer(e, true), - xy = this._normalizePointer(target, pointer); -======= _scaleObjectEqually: function(localMouse, target, transform) { ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f var dist = localMouse.y + localMouse.x; @@ -10057,25 +11418,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab /** * @private */ -<<<<<<< HEAD - _normalizePointer: function (object, pointer) { - var activeGroup = this.getActiveGroup(), - x = pointer.x, - y = pointer.y, - lt; - - var isObjectInGroup = ( - activeGroup && - object.type !== 'group' && - activeGroup.contains(object) - ); - - if (isObjectInGroup) { - lt = new fabric.Point(activeGroup.left, activeGroup.top); - lt = fabric.util.transformPoint(lt, this.viewportTransform, true); - x -= lt.x; - y -= lt.y; -======= _flipObject: function(transform) { if (transform.newScaleX < 0) { if (transform.originX === 'left') { @@ -10093,7 +11435,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab else if (transform.originY === 'bottom') { transform.originY = 'top'; } ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f } }, @@ -10159,329 +11500,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _rotateObject: function (x, y) { -<<<<<<< HEAD - return ( - !target || ( - target && - activeGroup && - !activeGroup.contains(target) && - activeGroup !== target && - !e.shiftKey) || ( - target && - !target.evented) - ); - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _shouldCenterTransform: function (e, target) { - if (!target) return; - - var t = this._currentTransform, - centerTransform; - - if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { - centerTransform = this.centeredScaling || target.centeredScaling; - } - else if (t.action === 'rotate') { - centerTransform = this.centeredRotation || target.centeredRotation; - } - - return centerTransform ? !e.altKey : e.altKey; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _setupCurrentTransform: function (e, target) { - if (!target) return; - - var action = 'drag', - corner, - pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ); - - corner = target._findTargetCorner(e, this._offset); - if (corner) { - action = (corner === 'ml' || corner === 'mr') - ? 'scaleX' - : (corner === 'mt' || corner === 'mb') - ? 'scaleY' - : corner === 'mtr' - ? 'rotate' - : 'scale'; - } - - var originX = target.originX, - originY = target.originY; - - if (corner === 'ml' || corner === 'tl' || corner === 'bl') { - originX = "right"; - } - else if (corner === 'mr' || corner === 'tr' || corner === 'br') { - originX = "left"; - } - - if (corner === 'tl' || corner === 'mt' || corner === 'tr') { - originY = "bottom"; - } - else if (corner === 'bl' || corner === 'mb' || corner === 'br') { - originY = "top"; - } - - this._currentTransform = { - target: target, - action: action, - scaleX: target.scaleX, - scaleY: target.scaleY, - offsetX: pointer.x - target.left, - offsetY: pointer.y - target.top, - originX: originX, - originY: originY, - ex: pointer.x, - ey: pointer.y, - left: target.left, - top: target.top, - theta: degreesToRadians(target.angle), - width: target.width * target.scaleX, - mouseXSign: 1, - mouseYSign: 1 - }; - - this._currentTransform.original = { - left: target.left, - top: target.top, - scaleX: target.scaleX, - scaleY: target.scaleY, - originX: originX, - originY: originY - }; - - this._resetCurrentTransform(e); - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - * @return {Boolean} - */ - _shouldHandleGroupLogic: function(e, target) { - var activeObject = this.getActiveObject(); - return e.shiftKey && - (this.getActiveGroup() || (activeObject && activeObject !== target)) - && this.selection; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _handleGroupLogic: function (e, target) { - if (target === this.getActiveGroup()) { - // if it's a group, find target again, this time skipping group - target = this.findTarget(e, true); - // if even object is not found, bail out - if (!target || target.isType('group')) { - return; - } - } - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - if (activeGroup.contains(target)) { - activeGroup.removeWithUpdate(target); - this._resetObjectTransform(activeGroup); - target.set('active', false); - if (activeGroup.size() === 1) { - // remove group alltogether if after removal it only contains 1 object - this.discardActiveGroup(); - } - } - else { - activeGroup.addWithUpdate(target); - this._resetObjectTransform(activeGroup); - } - this.fire('selection:created', { target: activeGroup, e: e }); - activeGroup.set('active', true); - } - else { - // group does not exist - if (this._activeObject) { - // only if there's an active object - if (target !== this._activeObject) { - // and that object is not the actual target - var objects = this.getObjects(); - var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); - var group = new fabric.Group( - isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]); - group.canvas = this; - - this.setActiveGroup(group); - this._activeObject = null; - activeGroup = this.getActiveGroup(); - this.fire('selection:created', { target: activeGroup, e: e }); - } - } - // activate target object in any case - target.set('active', true); - } - - if (activeGroup) { - activeGroup.saveCoords(); - } - }, - - /** - * Translates object by "setting" its left/top - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - */ - _translateObject: function (x, y) { - var target = this._currentTransform.target; - - if (!target.get('lockMovementX')) { - target.set('left', x - this._currentTransform.offsetX); - } - if (!target.get('lockMovementY')) { - target.set('top', y - this._currentTransform.offsetY); - } - }, - - /** - * Scales object by invoking its scaleX/scaleY methods - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. - * When not provided, an object is scaled by both dimensions equally - */ - _scaleObject: function (x, y, by) { - var t = this._currentTransform, - offset = this._offset, - target = t.target; - - var lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); - - if (lockScalingX && lockScalingY) return; - - // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); - var localMouse = target.toLocalPoint(new fabric.Point(x - offset.left, y - offset.top), t.originX, t.originY); - - if (t.originX === 'right') { - localMouse.x *= -1; - } - else if (t.originX === 'center') { - localMouse.x *= t.mouseXSign * 2; - - if (localMouse.x < 0) { - t.mouseXSign = -t.mouseXSign; - } - } - - if (t.originY === 'bottom') { - localMouse.y *= -1; - } - else if (t.originY === 'center') { - localMouse.y *= t.mouseYSign * 2; - - if (localMouse.y < 0) { - t.mouseYSign = -t.mouseYSign; - } - } - - // adjust the mouse coordinates when dealing with padding - if (abs(localMouse.x) > target.padding) { - if (localMouse.x < 0 ) { - localMouse.x += target.padding; - } else { - localMouse.x -= target.padding; - } - } else { // mouse is within the padding, set to 0 - localMouse.x = 0; - } - - if (abs(localMouse.y) > target.padding) { - if (localMouse.y < 0 ) { - localMouse.y += target.padding; - } else { - localMouse.y -= target.padding; - } - } else { - localMouse.y = 0; - } - - // Actually scale the object - var newScaleX = target.scaleX, newScaleY = target.scaleY; - if (by === 'equally' && !lockScalingX && !lockScalingY) { - var dist = localMouse.y + localMouse.x; - var lastDist = (target.height + (target.strokeWidth)) * t.original.scaleY + - (target.width + (target.strokeWidth)) * t.original.scaleX; - - // We use t.scaleX/Y instead of target.scaleX/Y because the object may have a min scale and we'll loose the proportions - newScaleX = t.original.scaleX * dist/lastDist; - newScaleY = t.original.scaleY * dist/lastDist; - - target.set('scaleX', newScaleX); - target.set('scaleY', newScaleY); - } - else if (!by) { - newScaleX = localMouse.x/(target.width+target.strokeWidth); - newScaleY = localMouse.y/(target.height+target.strokeWidth); - - lockScalingX || target.set('scaleX', newScaleX); - lockScalingY || target.set('scaleY', newScaleY); - } - else if (by === 'x' && !target.get('lockUniScaling')) { - newScaleX = localMouse.x/(target.width + target.strokeWidth); - lockScalingX || target.set('scaleX', newScaleX); - } - else if (by === 'y' && !target.get('lockUniScaling')) { - newScaleY = localMouse.y/(target.height + target.strokeWidth); - lockScalingY || target.set('scaleY', newScaleY); - } - - // Check if we flipped - if (newScaleX < 0) - { - if (t.originX === 'left') - t.originX = 'right'; - else if (t.originX === 'right') - t.originX = 'left'; - } - - if (newScaleY < 0) - { - if (t.originY === 'top') - t.originY = 'bottom'; - else if (t.originY === 'bottom') - t.originY = 'top'; - } - - // Make sure the constraints apply - target.setPositionByOrigin(constraintPosition, t.originX, t.originY); - }, - - /** - * Rotates object by invoking its rotate method - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - */ - _rotateObject: function (x, y) { - -======= ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f var t = this._currentTransform, o = this._offset; @@ -10567,51 +11585,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab /** * @private */ -<<<<<<< HEAD - _findSelectedObjects: function (e) { - var group = [ ], - x1 = this._groupSelector.ex, - y1 = this._groupSelector.ey, - x2 = x1 + this._groupSelector.left, - y2 = y1 + this._groupSelector.top, - currentObject, - selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), - selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), - isClick = x1 === x2 && y1 === y2; - - for (var i = this._objects.length; i--; ) { - currentObject = this._objects[i]; - - if (!currentObject) continue; - - if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || - currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || - currentObject.containsPoint(selectionX1Y1) || - currentObject.containsPoint(selectionX2Y2)) { - - if (this.selection && currentObject.selectable) { - currentObject.set('active', true); - group.push(currentObject); - - // only add one object if it's a click - if (isClick) break; - } - } - } - - // do not create group for 1 element only - if (group.length === 1) { - this.setActiveObject(group[0], e); - } - else if (group.length > 1) { - group = new fabric.Group(group.reverse()); - group.canvas = this; - this.setActiveGroup(group); - group.saveCoords(); - this.fire('selection:created', { target: group }); - this.renderAll(); - } -======= _isLastRenderedObject: function(e) { return ( this.controlsAboveOverlay && @@ -10619,7 +11592,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f }, /** @@ -10630,21 +11602,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab findTarget: function (e, skipGroup) { if (this.skipTargetFind) return; -<<<<<<< HEAD - var target, - pointer = this.getPointer(e, true); - - if (this.controlsAboveOverlay && - this.lastRenderedObjectWithControlsAboveOverlay && - this.lastRenderedObjectWithControlsAboveOverlay.visible && - this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && - this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) { - target = this.lastRenderedObjectWithControlsAboveOverlay; - return target; -======= if (this._isLastRenderedObject(e)) { return this.lastRenderedObjectWithControlsAboveOverlay; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f } // first check current group (if one exists) @@ -10664,7 +11623,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // Cache all targets where their bounding box contains point. var possibleTargets = [], target, - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); for (var i = this._objects.length; i--; ) { if (this._objects[i] && @@ -10875,6 +11834,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this._activeGroup = group; if (group) { group.canvas = this; + group._calcBounds(); + group._updateObjectsCoords(); + group.setCoords(); group.set('active', true); } }, @@ -11253,18 +12215,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab target = this._currentTransform.target; } else { -<<<<<<< HEAD - pointer = this.getPointer(e, true); - } - - render = this._shouldRender(target, pointer); - - if (this.selection && this._groupSelector) { - // group selection was completed, determine its bounds - this._findSelectedObjects(e); -======= target = this.findTarget(e, true); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f } var shouldRender = this._shouldRender(target, this.getPointer(e)); @@ -11349,7 +12300,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } - this.freeDrawingBrush.onMouseDown(this.getPointer(e, true)); + var ivt = fabric.util.invertTransform(this.viewportTransform); + var pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseDown(pointer); this.fire('mouse:down', { e: e }); }, @@ -11359,7 +12312,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { - var pointer = this.getPointer(e); + var ivt = fabric.util.invertTransform(this.viewportTransform); + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; @@ -11402,13 +12356,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (this._currentTransform) return; var target = this.findTarget(e), -<<<<<<< HEAD - pointer = this.getPointer(e, true), - corner, - render; -======= - pointer = this.getPointer(e); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f + pointer = this.getPointer(e, true); // save pointer for check in __onMouseUp event this._previousPointer = pointer; @@ -11527,15 +12475,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var target, pointer; if (this.isDrawingMode) { -<<<<<<< HEAD - if (this._isCurrentlyDrawing) { - this.freeDrawingBrush.onMouseMove(this.getPointer(e, true)); - } - this.upperCanvasEl.style.cursor = this.freeDrawingCursor; - this.fire('mouse:move', { e: e }); -======= this._onMouseMoveInDrawingMode(e); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f return; } @@ -11545,14 +12485,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (groupSelector) { pointer = this.getPointer(e, true); -<<<<<<< HEAD groupSelector.left = pointer.x - groupSelector.ex; groupSelector.top = pointer.y - groupSelector.ey; -======= - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f this.renderTop(); } else if (!this._currentTransform) { @@ -11567,57 +12502,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab } } else { -<<<<<<< HEAD - // object is being transformed (scaled/rotated/moved/etc.) - pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ); - - var x = pointer.x, - y = pointer.y, - reset = false, - centerTransform, - transform = this._currentTransform; - - target = transform.target; - target.isMoving = true; - - if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { - centerTransform = this._shouldCenterTransform(e, target); - - // Switch from a normal resize to center-based - if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || - // Switch from center-based resize to normal one - (!centerTransform && transform.originX === 'center' && transform.originY === 'center') - ) { - this._resetCurrentTransform(e); - reset = true; - } - } - - if (transform.action === 'rotate') { - this._rotateObject(x, y); - - this.fire('object:rotating', { target: target, e: e }); - target.fire('rotating', { e: e }); - } - else if (transform.action === 'scale') { - // rotate object only if shift key is not pressed - // and if it is not a group we are transforming - if ((e.shiftKey || this.uniScaleTransform) && !target.get('lockUniScaling')) { - transform.currentAction = 'scale'; - this._scaleObject(x, y); - } - else { - // Switch from a normal resize to proportional - if (!reset && transform.currentAction === 'scale') { - this._resetCurrentTransform(e, target); - } -======= this._transformObject(e); } ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f this.fire('mouse:move', { target: target, e: e }); target && target.fire('mousemove', { e: e }); @@ -11629,7 +12515,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _transformObject: function(e) { - var pointer = getPointer(e, this.upperCanvasEl), + var pointer = fabric.util.transformPoint( + getPointer(e, this.upperCanvasEl), + fabric.util.invertTransform(this.viewportTransform) + ), transform = this._currentTransform; transform.reset = false, @@ -12449,6 +13338,36 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.fire('touch:gesture', {target: target, e: e, self: self}); }, + /** + * Method that defines actions when an Event.js drag is detected. + * + * @param e Event object by Event.js + * @param self Event proxy object by Event.js + */ + __onDrag: function(e, self) { + this.fire('touch:drag', {e: e, self: self}); + }, + + /** + * Method that defines actions when an Event.js orientation event is detected. + * + * @param e Event object by Event.js + * @param self Event proxy object by Event.js + */ + __onOrientationChange: function(e, self) { + this.fire('touch:orientation', {e: e, self: self}); + }, + + /** + * Method that defines actions when an Event.js shake event is detected. + * + * @param e Event object by Event.js + * @param self Event proxy object by Event.js + */ + __onShake: function(e, self) { + this.fire('touch:shake', {e: e, self: self}); + }, + /** * Scales an object by a factor * @param s {Number} The scale factor to apply to the current scale level @@ -13439,23 +14358,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); - - if (this.active && !noTransform) { - this.drawBorders(ctx); - this.drawControls(ctx); - } ctx.restore(); + + this._renderControls(ctx, noTransform); }, _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -13485,19 +14395,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ? this.fill.toLive(ctx) : this.fill; } -<<<<<<< HEAD + }, - if (m && this.group) { - ctx.translate(-this.group.width/2, -this.group.height/2); - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx, noTransform); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + var v = this.canvas.viewportTransform; ctx.save(); if (this.active && !noTransform) { @@ -13518,8 +14424,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.drawControls(ctx); } ctx.restore(); -======= ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f }, /** @@ -14450,12 +15354,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati padding = this.padding, theta = degreesToRadians(this.angle), vpt; - if (this.canvas) { - vpt = this.canvas.viewportTransform; - } - if (!vpt) { // TODO - vpt = [1, 0, 0, 1, 0, 0]; - }; + // TODO: ideally we should never setCoords an object which lacks a canvas + vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -15055,8 +15955,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot sx= sxy.x, sy= sxy.y; if (this.group) { - w = w * this.group.scaleX; - h = h * this.group.scaleY; + w = w * this.group.scaleX; + h = h * this.group.scaleY; } ctx.strokeRect( @@ -15099,7 +15999,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down -<<<<<<< HEAD wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), width = wh.x, height = wh.y, @@ -15110,22 +16009,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, - methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', - transparent = this.transparentCorners, - isVML = typeof G_vmlCanvasManager !== 'undefined'; -======= - left = -(this.width / 2), - top = -(this.height / 2), - paddingX = this.padding / this.scaleX, - paddingY = this.padding / this.scaleY, - scaleOffsetY = size2 / this.scaleY, - scaleOffsetX = size2 / this.scaleX, - scaleOffsetSizeX = (size2 - size) / this.scaleX, - scaleOffsetSizeY = (size2 - size) / this.scaleY, - height = this.height, - width = this.width, methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f ctx.save(); @@ -15135,124 +16019,55 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.strokeStyle = ctx.fillStyle = this.cornerColor; // top-left -<<<<<<< HEAD - _left = left - scaleOffset - strokeWidth2 - padding; - _top = top - scaleOffset - strokeWidth2 - padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // top-right - _left = left + width - scaleOffset + strokeWidth2 + padding; - _top = top - scaleOffset - strokeWidth2 - padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // bottom-left - _left = left - scaleOffset - strokeWidth2 - padding; - _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // bottom-right - _left = left + width + scaleOffsetSize + strokeWidth2 + padding; - _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - if (!this.get('lockUniScaling')) { - // middle-top - _left = left + width/2 - scaleOffset; - _top = top - scaleOffset - strokeWidth2 - padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // middle-bottom - _left = left + width/2 - scaleOffset; - _top = top + height + scaleOffsetSize + strokeWidth2 + padding; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // middle-right - _left = left + width + scaleOffsetSize + strokeWidth2 + padding; - _top = top + height/2 - scaleOffset; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); - - // middle-left - _left = left - scaleOffset - strokeWidth2 - padding; - _top = top + height/2 - scaleOffset; - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); -======= this._drawControl('tl', ctx, methodName, - left - scaleOffsetX - strokeWidth2 - paddingX, - top - scaleOffsetY - strokeWidth2 - paddingY); + left - scaleOffset - strokeWidth2 - padding, + top - scaleOffset - strokeWidth2 - padding); // top-right this._drawControl('tr', ctx, methodName, - left + width - scaleOffsetX + strokeWidth2 + paddingX, - top - scaleOffsetY - strokeWidth2 - paddingY); + left + width - scaleOffset + strokeWidth2 + padding, + top - scaleOffset - strokeWidth2 - padding); // bottom-left this._drawControl('tr', ctx, methodName, - left - scaleOffsetX - strokeWidth2 - paddingX, - top + height + scaleOffsetSizeY + strokeWidth2 + paddingY); + left - scaleOffset - strokeWidth2 - padding, + top + height + scaleOffsetSize + strokeWidth2 + padding); // bottom-right this._drawControl('br', ctx, methodName, - left + width + scaleOffsetSizeX + strokeWidth2 + paddingX, - top + height + scaleOffsetSizeY + strokeWidth2 + paddingY); + left + width + scaleOffsetSize + strokeWidth2 + padding, + top + height + scaleOffsetSize + strokeWidth2 + padding); if (!this.get('lockUniScaling')) { // middle-top this._drawControl('mt', ctx, methodName, - left + width/2 - scaleOffsetX, - top - scaleOffsetY - strokeWidth2 - paddingY); + left + width/2 - scaleOffset, + top - scaleOffset - strokeWidth2 - padding); // middle-bottom this._drawControl('mb', ctx, methodName, - left + width/2 - scaleOffsetX, - top + height + scaleOffsetSizeY + strokeWidth2 + paddingY); + left + width/2 - scaleOffset, + top + height + scaleOffsetSize + strokeWidth2 + padding); // middle-right this._drawControl('mb', ctx, methodName, - left + width + scaleOffsetSizeX + strokeWidth2 + paddingX, - top + height/2 - scaleOffsetY); + left + width + scaleOffsetSize + strokeWidth2 + padding, + top + height/2 - scaleOffset); // middle-left this._drawControl('ml', ctx, methodName, - left - scaleOffsetX - strokeWidth2 - paddingX, - top + height/2 - scaleOffsetY); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f + left - scaleOffset - strokeWidth2 - padding, + top + height/2 - scaleOffset); } // middle-top-rotate if (this.hasRotatingPoint) { -<<<<<<< HEAD - - _left = left + width/2 - scaleOffset; - _top = this.flipY ? - (top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding) - : (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding); - - isVML || transparent || ctx.clearRect(_left, _top, size, size); - ctx[methodName](_left, _top, size, size); -======= this._drawControl('mtr', ctx, methodName, - left + width/2 - scaleOffsetX, + left + width/2 - scaleOffset, this.flipY - ? (top + height + (this.rotatingPointOffset / this.scaleY) - this.cornerSize/this.scaleX/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - this.cornerSize/this.scaleY/2 - strokeWidth2 - paddingY)); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f + ? (top + height + this.rotatingPointOffset - this.cornerSize/2 + strokeWidth2 + padding) + : (top - this.rotatingPointOffset - this.cornerSize/2 - strokeWidth2 - padding)); } ctx.restore(); @@ -15264,12 +16079,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _drawControl: function(control, ctx, methodName, left, top) { - var sizeX = this.cornerSize / this.scaleX, - sizeY = this.cornerSize / this.scaleY; + var size = this.cornerSize; if (this.isControlVisible(control)) { - isVML || this.transparentCorners || ctx.clearRect(left, top, sizeX, sizeY); - ctx[methodName](left, top, sizeX, sizeY); + isVML || this.transparentCorners || ctx.clearRect(left, top, size, size); + ctx[methodName](left, top, size, size); } }, @@ -17415,6 +18229,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v = this.canvas.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -17432,12 +18250,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); this._removeShadow(ctx); - - if (!noTransform && this.active) { - this.drawBorders(ctx); - this.drawControls(ctx); - } ctx.restore(); + + this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -17756,7 +18571,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.setOptions(options); - this.setCoords(); if (options.sourcePath) { this.setSourcePath(options.sourcePath); @@ -17774,6 +18588,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + + var v = this.canvas.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -17787,12 +18605,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } this.clipTo && ctx.restore(); this._removeShadow(ctx); - - if (this.active) { - this.drawBorders(ctx); - this.drawControls(ctx); - } ctx.restore(); + + this.callSuper('_renderControls', ctx); }, /** @@ -17951,7 +18766,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke; + invoke = fabric.util.array.invoke, + degreesToRadians = fabric.util.degreesToRadians; if (fabric.Group) { return; @@ -18003,16 +18819,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.originalState = { }; this.callSuper('initialize'); - this._calcBounds(); - this._updateObjectsCoords(); - if (options) { extend(this, options); } this._setOpacityIfSame(); - - this.setCoords(true); - this.saveCoords(); }, /** @@ -18165,8 +18975,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!this.visible) return; ctx.save(); - this.transform(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); // the array is now sorted in order of highest first, so start from end @@ -18176,10 +18984,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); - if (!noTransform && this.active) { - this.drawBorders(ctx); - this.drawControls(ctx); - } + this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, @@ -18187,20 +18992,23 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _renderObject: function(object, ctx) { - - var originalScaleFactor = object.borderScaleFactor, - originalHasRotatingPoint = object.hasRotatingPoint, - groupScaleFactor = Math.max(this.scaleX, this.scaleY); + var v = this.canvas.viewportTransform, + sxy = fabric.util.transformPoint( + new fabric.Point(this.scaleX, this.scaleY), + v, + true + ); + + var originalHasRotatingPoint = object.hasRotatingPoint, + groupScaleFactor = Math.max(sxy.x, sxy.y); // do not render if object is not visible if (!object.visible) return; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; }, @@ -18396,18 +19204,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _getBounds: function(aX, aY) { - var minX = min(aX), - maxX = max(aX), - minY = min(aY), - maxY = max(aY), - width = (maxX - minX) || 0, - height = (maxY - minY) || 0; + var ivt; + if (this.canvas) { + ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + } + var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); return { - width: width, - height: height, - left: (minX + width / 2) || 0, - top: (minY + height / 2) || 0 + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0, + left: (minXY.x + maxXY.x) / 2 || 0, + top: (minXY.y + maxXY.y) / 2 || 0, }; }, @@ -18614,8 +19422,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; + var v; + v = this.canvas.viewportTransform; + var isInPathGroup = this.group && this.group.type === 'path-group'; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); @@ -18637,12 +19450,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); - - if (this.active && !noTransform) { - this.drawBorders(ctx); - this.drawControls(ctx); - } ctx.restore(); + + this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -20081,32 +20891,10 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag data = imageData.data, iLen = data.length, i, r, g, b; -<<<<<<< HEAD - ctx.save(); - var m = this.transformMatrix; - - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - // ctx.globalCompositeOperation = this.fillRule; -======= for (i = 0; i < iLen; i+=4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; @@ -20126,26 +20914,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag return new fabric.Image.filters.Sepia2(); }; -<<<<<<< HEAD - this._render(ctx); - this._renderFill(ctx); - this._renderStroke(ctx); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); - }, -======= })(typeof exports !== 'undefined' ? exports : this); @@ -20179,7 +20947,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f /** * Filter type @@ -20585,7 +21352,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag this.setOptions(options); this.__skipDimension = false; this._initDimensions(); - this.setCoords(); }, /** @@ -20613,50 +21379,19 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag */ _render: function(ctx) { -<<<<<<< HEAD - var m = this.transformMatrix; - - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); -======= var isInPathGroup = this.group && this.group.type === 'path-group'; if (isInPathGroup && !this.transformMatrix) { ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f } else if (isInPathGroup && this.transformMatrix) { ctx.translate(-this.group.width/2, -this.group.height/2); } -<<<<<<< HEAD - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - - ctx.save(); - if (this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); -======= if (typeof Cufon === 'undefined' || this.useNative === true) { this._renderViaNative(ctx); } else { this._renderViaCufon(ctx); ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f } }, @@ -20803,20 +21538,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag // lift the line by quarter of fontSize top -= this.fontSize / 4; -<<<<<<< HEAD - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - invoke = fabric.util.array.invoke, - degreesToRadians = fabric.util.degreesToRadians; -======= // short-circuit if (this.textAlign !== 'justify') { this._renderChars(method, ctx, line, left, top, lineIndex); return; } ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f var lineWidth = ctx.measureText(line).width; var totalWidth = this.width; @@ -20859,25 +21585,13 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag return -this.height / 2; }, -<<<<<<< HEAD - this._objects = objects || []; - for (var i = this._objects.length; i--; ) { - this._objects[i].group = this; - this._objects[i].setCoords(); - } -======= /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextFill: function(ctx, textLines) { -<<<<<<< HEAD - if (!this.fill && !this.skipFillStrokeCheck) return; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f -======= if (!this.fill && !this._skipFillStrokeCheck) return; ->>>>>>> 8ca6806f3287a8351433961c32de79e3eeafcefb this._boundaries = [ ]; var lineHeights = 0; @@ -21085,59 +21799,12 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); -<<<<<<< HEAD var v = this.canvas.viewportTransform; - - var sxy = fabric.util.transformPoint( - new fabric.Point(this.scaleX, this.scaleY), - v, true), - groupScaleFactor = Math.max(sxy.x, sxy.y); - - this.clipTo && fabric.util.clipContext(this, ctx); - - //The array is now sorted in order of highest first, so start from end. - for (var i = 0, len = this._objects.length; i < len; i++) { - - var object = this._objects[i], - originalScaleFactor = object.borderScaleFactor, - originalHasRotatingPoint = object.hasRotatingPoint; - - // do not render if object is not visible - if (!object.visible) continue; - - object.hasRotatingPoint = false; - object.render(ctx); - - object.hasRotatingPoint = originalHasRotatingPoint; - } - this.clipTo && ctx.restore(); - - if (this.active && !noTransform) { - var center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); -======= + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); - if (!noTransform && this.active) { ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f - this.drawBorders(ctx); - this.drawControls(ctx); - } ctx.restore(); -<<<<<<< HEAD - }, - /** - * Retores original state of each of group objects (original state is that which was before group was created). - * @private - * @return {fabric.Group} thisArg - * @chainable - */ - _restoreObjectsState: function() { - this._objects.forEach(this._restoreObjectState, this); - return this; -======= ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f + this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -21277,19 +21944,10 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @param {Array} textLines Array of all text lines * @return {Object} */ -<<<<<<< HEAD - _calcBounds: function() { - var aX = [], - aY = [], - minX, minY, maxX, maxY, o, width, height, minXY, maxXY, - i = 0, - len = this._objects.length; -======= _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { var textSpans = [ ], textBgRects = [ ], lineTopOffsetMultiplier = 1; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f // bounding-box background this._setSVGBg(textBgRects); @@ -21305,23 +21963,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag // prevents empty tspans lineTopOffsetMultiplier++; } -<<<<<<< HEAD - } - - var ivt = fabric.util.invertTransform(canvas.viewportTransform) || [1, 0, 0, 1, 0, 0]; - - minXY = new fabric.Point(min(aX), min(aY)); - maxXY = new fabric.Point(max(aX), max(aY)); - - minXY = fabric.util.transformPoint(minXY, ivt); - maxXY = fabric.util.transformPoint(maxXY, ivt); - - this.width = (maxXY.x - minXY.x) || 0; - this.height = (maxXY.y - minXY.y) || 0; - - this.left = (minXY.x + maxXY.x) / 2 || 0; - this.top = (minXY.y + maxXY.y) / 2 || 0; -======= if (!this.textBackgroundColor || !this._boundaries) continue; @@ -21385,7 +22026,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag toFixed(this.height, 2), '">'); } ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f }, /** @@ -21492,6 +22132,85 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag })(typeof exports !== 'undefined' ? exports : this); +/** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ +fabric.util.object.extend(fabric.Text.prototype, { + _renderViaCufon: function(ctx) { + + var o = Cufon.textOptions || (Cufon.textOptions = { }); + + // export options to be used by cufon.js + o.left = this.left; + o.top = this.top; + o.context = ctx; + o.color = this.fill; + + var el = this._initDummyElementForCufon(); + + // set "cursor" to top/left corner + this.transform(ctx); + + // draw text + Cufon.replaceElement(el, { + engine: 'canvas', + separate: 'none', + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, + textDecoration: this.textDecoration, + textShadow: this.shadow && this.shadow.toString(), + textAlign: this.textAlign, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + stroke: this.stroke, + strokeWidth: this.strokeWidth, + backgroundColor: this.backgroundColor, + textBackgroundColor: this.textBackgroundColor + }); + + // update width, height + this.width = o.width; + this.height = o.height; + + this._totalLineHeight = o.totalLineHeight; + this._fontAscent = o.fontAscent; + this._boundaries = o.boundaries; + + el = null; + + // need to set coords _after_ the width/height was retreived from Cufon + this.setCoords(); + }, + + /** + * @private + */ + _initDummyElementForCufon: function() { + var el = fabric.document.createElement('pre'), + container = fabric.document.createElement('div'); + + // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent + container.appendChild(el); + + if (typeof G_vmlCanvasManager === 'undefined') { + el.innerHTML = this.text; + } + else { + // IE 7 & 8 drop newlines and white space on text nodes + // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html + // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp + el.innerText = this.text.replace(/\r?\n/gi, '\r'); + } + + el.style.fontSize = this.fontSize + 'px'; + el.style.letterSpacing = 'normal'; + + return el; + } +}); + + (function() { var clone = fabric.util.object.clone; @@ -21603,64 +22322,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag */ cursorColor: '#333', -<<<<<<< HEAD - ctx.save(); - var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } - var isInPathGroup = this.group && this.group.type !== 'group'; - - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - // this._resetWidthHeight(); - if (isInPathGroup) { - ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); - } - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - - ctx.save(); - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx); - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - this._renderStroke(ctx); - this.clipTo && ctx.restore(); - ctx.restore(); - ctx.restore(); - - ctx.save(); - if (this.active && !noTransform) { - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); - } - center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); - }, -======= /** * Delay between cursor blink (in ms) * @type Number @@ -21683,10 +22344,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag */ styles: null, -<<<<<<< HEAD - skipFillStrokeCheck: true, ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f -======= /** * Indicates whether internal text char widths can be cached * @type Boolean @@ -21700,7 +22357,6 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @default */ _skipFillStrokeCheck: true, ->>>>>>> 8ca6806f3287a8351433961c32de79e3eeafcefb /** * @private @@ -23905,30 +24561,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this._selectionDirection = 'left'; }, -<<<<<<< HEAD - ctx.save(); - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - this._render(ctx); - ctx.restore(); - ctx.save(); - if (!noTransform && this.active) { - var center; - center = fabric.util.transformPoint(this.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(fabric.util.degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); - }, -======= /** * Moves cursor up without shift * @param {Number} offset @@ -23941,7 +24573,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.selectionStart = 0; } this.selectionEnd = this.selectionStart; ->>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f this._selectionDirection = 'left'; }, diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index f53e8145..b97842d2 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -500,8 +500,8 @@ if (groupSelector) { pointer = this.getPointer(e, true); - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 937cce35..03460f5f 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -308,12 +308,8 @@ padding = this.padding, theta = degreesToRadians(this.angle), vpt; - if (this.canvas) { - vpt = this.canvas.viewportTransform; - } - if (!vpt) { // TODO - vpt = [1, 0, 0, 1, 0, 0]; - }; + // TODO: ideally we should never setCoords an object which lacks a canvas + vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; var f = function (p) { return fabric.util.transformPoint(p, vpt); diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 0c21a0c6..aa2e5786 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -51,26 +51,18 @@ initialize: function(objects, options) { options = options || { }; - // NOTE: all the coords calculations need to have a canvas before they make sense this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; - //this._objects[i].setCoords(); } this.originalState = { }; this.callSuper('initialize'); - //this._calcBounds(); - //this._updateObjectsCoords(); - if (options) { extend(this, options); } this._setOpacityIfSame(); - - //this.setCoords(true); - //this.saveCoords(); }, /** @@ -247,19 +239,16 @@ true ); - var originalScaleFactor = object.borderScaleFactor, - originalHasRotatingPoint = object.hasRotatingPoint, + var originalHasRotatingPoint = object.hasRotatingPoint, groupScaleFactor = Math.max(sxy.x, sxy.y); // do not render if object is not visible if (!object.visible) return; - object.borderScaleFactor = groupScaleFactor; object.hasRotatingPoint = false; object.render(ctx); - object.borderScaleFactor = originalScaleFactor; object.hasRotatingPoint = originalHasRotatingPoint; }, @@ -459,10 +448,6 @@ if (this.canvas) { ivt = fabric.util.invertTransform(this.canvas.viewportTransform); } - else { // BUG: this always happens when new groups are created - ivt = [1, 0, 0, 1, 0, 0]; - console.log('no canvas'); - } var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index 66cecfe4..65c41f1c 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -124,12 +124,8 @@ ctx.save(); var m = this.transformMatrix; var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + v = this.canvas.viewportTransform; + var isInPathGroup = this.group && this.group.type === 'path-group'; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 7ec90fc8..989f5e7c 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -948,13 +948,7 @@ _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -992,13 +986,8 @@ * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; + ctx.save(); if (this.active && !noTransform) { var center; diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index b65f8ae2..fc9b6207 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -444,13 +444,7 @@ ctx.save(); var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { diff --git a/src/shapes/path_group.class.js b/src/shapes/path_group.class.js index 953aa09b..a7ea505f 100644 --- a/src/shapes/path_group.class.js +++ b/src/shapes/path_group.class.js @@ -51,7 +51,6 @@ } this.setOptions(options); - this.setCoords(); if (options.sourcePath) { this.setSourcePath(options.sourcePath); @@ -70,13 +69,7 @@ var m = this.transformMatrix; - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 5891fcf0..7f193d3e 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -319,7 +319,6 @@ this.setOptions(options); this.__skipDimension = false; this._initDimensions(); - this.setCoords(); }, /** @@ -767,13 +766,7 @@ if (!this.visible) return; ctx.save(); - var v; - if (this.canvas) { - v = this.canvas.viewportTransform; - } - else { - v = [1, 0, 0, 1, 0, 0]; // TODO: this isn't a solution - } + var v = this.canvas.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._render(ctx); ctx.restore(); From 116a630defbfe926513c9f9d5afd0ba3b0ac0b8e Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 31 May 2014 17:59:31 +0100 Subject: [PATCH 05/27] JShint --- dist/fabric.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 03e32a6a..7c8473b7 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -11849,7 +11849,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // Cache all targets where their bounding box contains point. var target, - pointer = this.getPointer(e); + pointer = this.getPointer(e, true); var i = this._objects.length; @@ -15720,9 +15720,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, theta = degreesToRadians(this.angle), - vpt; - // TODO: ideally we should never setCoords an object which lacks a canvas - vpt = this.getViewportTransform(); + vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -18306,6 +18304,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot q: 4, t: 2, a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' }; if (fabric.Path) { @@ -18875,12 +18877,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - var command = coordsParsed[0].toLowerCase(), - commandLength = commandLengths[command]; + var command = coordsParsed[0], + commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; if (coordsParsed.length - 1 > commandLength) { for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([ coordsParsed[0] ].concat(coordsParsed.slice(k, k + commandLength))); + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; } } else { @@ -24730,6 +24734,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; this.__lastIsEditing = this.isEditing; + this.__lastSelected = this.selected; }, isDoubleClick: function(newPointer) { @@ -24841,7 +24846,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__isMousedown = false; if (this._isObjectMoved(options.e)) return; - if (this.selected) { + if (this.__lastSelected) { this.enterEditing(); this.initDelayedCursor(true); } From 009c5389bf7f0306f9648cd6159d687e738654f5 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 31 May 2014 18:08:33 +0100 Subject: [PATCH 06/27] JSHint, restoring .gitignore --- .gitignore | 5 ++++ src/brushes/spray_brush.class.js | 2 +- src/mixins/canvas_events.mixin.js | 10 +++----- src/mixins/object_geometry.mixin.js | 31 ++++++++++++------------ src/mixins/object_interactivity.mixin.js | 2 -- src/static_canvas.class.js | 8 +++--- src/util/misc.js | 2 +- 7 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..06b77a9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/node_modules/ +/npm-debug.log +before_commit +/coverage/ diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index ad75c16d..afd9ce26 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -187,7 +187,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var point = new fabric.Point(x, y); - point.width = width + point.width = width; if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index 0d008997..a469bc4c 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -346,8 +346,8 @@ */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { - var ivt = fabric.util.invertTransform(this.viewportTransform); - pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + var ivt = fabric.util.invertTransform(this.viewportTransform), + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; @@ -548,11 +548,7 @@ * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - fabric.util.getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), - pointer = this.getPointer(e), + var pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index d393b29c..30e18c1f 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -304,13 +304,12 @@ setCoords: function() { var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - padding = this.padding, theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); - } + }; this.currentWidth = (this.width + strokeWidth) * this.scaleX; this.currentHeight = (this.height + strokeWidth) * this.scaleY; @@ -332,20 +331,20 @@ sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), - wh = new fabric.Point(this.currentWidth, this.currentHeight); - var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); - var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); - var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); - var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); - var tl = f(_tl); - var tr = f(_tr); - var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); - var bl = f(_bl); - var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); - var mt = f(_mt); - var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); - var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); - var mtr = f(new fabric.Point(_mt.x, _mt.y)); + wh = new fabric.Point(this.currentWidth, this.currentHeight), + _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), + _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), + _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), + _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), + tl = f(_tl), + tr = f(_tr), + br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), + bl = f(_bl), + ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), + mt = f(_mt), + mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), + mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), + mtr = f(new fabric.Point(_mt.x, _mt.y)); // padding var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index de15029d..30e5824a 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -333,8 +333,6 @@ height = wh.y, left = -(width / 2), top = -(height / 2), - _left, - _top, padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index ca998686..85c5c44b 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -538,7 +538,7 @@ * @return {Number} */ getZoom: function () { - return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, /** @@ -553,7 +553,7 @@ x = this.viewportTransform[4], y = this.viewportTransform[5]; - return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + return new fabric.Point(wh.x + x, wh.y + y); }, /** @@ -563,7 +563,7 @@ * @chainable true */ setViewportTransform: function (vpt) { - this.viewportTransform = vpt + this.viewportTransform = vpt; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); @@ -676,7 +676,7 @@ obj._objects[i].canvas = this; this._onObjectAdded(obj._objects[i]); } - obj._updateObjectsCoords() + obj._updateObjectsCoords(); } obj.setCoords(); this.fire('object:added', { target: obj }); diff --git a/src/util/misc.js b/src/util/misc.js index 0bd8e2ca..60a07ee5 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -117,7 +117,7 @@ var o = fabric.util.transformPoint({x: t[4], y: t[5]}, r); r[4] = -o.x; r[5] = -o.y; - return r + return r; }, /** From ef01083cabaad2a02cd6ee87d035850928907916 Mon Sep 17 00:00:00 2001 From: Tom French Date: Tue, 3 Jun 2014 08:36:00 +0100 Subject: [PATCH 07/27] Stray console.log removed. --- dist/fabric.js | 59 +++++++++++++--------------- dist/fabric.require.js | 87 +++++++++++++++++++++--------------------- src/canvas.class.js | 4 +- 3 files changed, 72 insertions(+), 78 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 7c8473b7..59ba3840 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -3981,7 +3981,7 @@ fabric.Collection = { var o = fabric.util.transformPoint({x: t[4], y: t[5]}, r); r[4] = -o.x; r[5] = -o.y; - return r + return r; }, /** @@ -9363,7 +9363,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {Number} */ getZoom: function () { - return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, /** @@ -9378,7 +9378,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ x = this.viewportTransform[4], y = this.viewportTransform[5]; - return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + return new fabric.Point(wh.x + x, wh.y + y); }, /** @@ -9388,7 +9388,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable true */ setViewportTransform: function (vpt) { - this.viewportTransform = vpt + this.viewportTransform = vpt; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); @@ -9501,7 +9501,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ obj._objects[i].canvas = this; this._onObjectAdded(obj._objects[i]); } - obj._updateObjectsCoords() + obj._updateObjectsCoords(); } obj.setCoords(); this.fire('object:added', { target: obj }); @@ -10988,7 +10988,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var point = new fabric.Point(x, y); - point.width = width + point.width = width; if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; @@ -11790,7 +11790,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { - console.log('AG', activeGroup); return activeGroup; } @@ -12179,7 +12178,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _drawControls: function(ctx, object, klass) { ctx.save(); fabric[klass].prototype.transform.call(object, ctx); - object.drawBorders(ctx).drawControls(ctx); + //object.drawBorders(ctx).drawControls(ctx); + object._renderControls(ctx); ctx.restore(); } }); @@ -12555,8 +12555,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { - var ivt = fabric.util.invertTransform(this.viewportTransform); - pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + var ivt = fabric.util.invertTransform(this.viewportTransform), + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; @@ -12757,11 +12757,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - fabric.util.getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), - pointer = this.getPointer(e), + var pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, @@ -15718,13 +15714,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati setCoords: function() { var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - padding = this.padding, theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); - } + }; this.currentWidth = (this.width + strokeWidth) * this.scaleX; this.currentHeight = (this.height + strokeWidth) * this.scaleY; @@ -15746,20 +15741,20 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), - wh = new fabric.Point(this.currentWidth, this.currentHeight); - var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); - var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); - var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); - var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); - var tl = f(_tl); - var tr = f(_tr); - var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); - var bl = f(_bl); - var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); - var mt = f(_mt); - var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); - var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); - var mtr = f(new fabric.Point(_mt.x, _mt.y)); + wh = new fabric.Point(this.currentWidth, this.currentHeight), + _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), + _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), + _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), + _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), + tl = f(_tl), + tr = f(_tr), + br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), + bl = f(_bl), + ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), + mt = f(_mt), + mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), + mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), + mtr = f(new fabric.Point(_mt.x, _mt.y)); // padding var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), @@ -16370,8 +16365,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot height = wh.y, left = -(width / 2), top = -(height / 2), - _left, - _top, padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, diff --git a/dist/fabric.require.js b/dist/fabric.require.js index 320db6ac..c3bdfc1e 100644 --- a/dist/fabric.require.js +++ b/dist/fabric.require.js @@ -3981,7 +3981,7 @@ fabric.Collection = { var o = fabric.util.transformPoint({x: t[4], y: t[5]}, r); r[4] = -o.x; r[5] = -o.y; - return r + return r; }, /** @@ -9363,7 +9363,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {Number} */ getZoom: function () { - return sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, /** @@ -9378,7 +9378,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ x = this.viewportTransform[4], y = this.viewportTransform[5]; - return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y); + return new fabric.Point(wh.x + x, wh.y + y); }, /** @@ -9388,7 +9388,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable true */ setViewportTransform: function (vpt) { - this.viewportTransform = vpt + this.viewportTransform = vpt; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); @@ -9501,7 +9501,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ obj._objects[i].canvas = this; this._onObjectAdded(obj._objects[i]); } - obj._updateObjectsCoords() + obj._updateObjectsCoords(); } obj.setCoords(); this.fire('object:added', { target: obj }); @@ -10501,7 +10501,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _render: function() { var ctx = this.canvas.contextTop; - var v = this.getViewportTransform(); + var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); @@ -10708,7 +10708,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var point = this.addPoint(pointer), ctx = this.canvas.contextTop; - var v = this.getViewportTransform(); + var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10950,7 +10950,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; - var v = this.getViewportTransform(); + var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10988,7 +10988,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } var point = new fabric.Point(x, y); - point.width = width + point.width = width; if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; @@ -11790,11 +11790,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + console.log('AG', activeGroup); return activeGroup; } var target = this._searchPossibleTargets(e); this._fireOverOutEvents(target); + return target; }, @@ -12177,7 +12179,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _drawControls: function(ctx, object, klass) { ctx.save(); fabric[klass].prototype.transform.call(object, ctx); - object.drawBorders(ctx).drawControls(ctx); + //object.drawBorders(ctx).drawControls(ctx); + object._renderControls(ctx); ctx.restore(); } }); @@ -12553,8 +12556,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { - var ivt = fabric.util.invertTransform(this.viewportTransform); - pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + var ivt = fabric.util.invertTransform(this.viewportTransform), + pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseMove(pointer); } this.upperCanvasEl.style.cursor = this.freeDrawingCursor; @@ -12755,11 +12758,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - fabric.util.getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), - pointer = this.getPointer(e), + var pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, @@ -14593,7 +14592,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {Boolean} flipY value // TODO */ getViewportTransform: function() { - if (this.canvas && this.getViewportTransform()) + if (this.canvas && this.canvas.viewportTransform) return this.canvas.viewportTransform; return [1, 0, 0, 1, 0, 0]; }, @@ -15716,15 +15715,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati setCoords: function() { var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - padding = this.padding, theta = degreesToRadians(this.angle), - vpt; - // TODO: ideally we should never setCoords an object which lacks a canvas - vpt = this.getViewportTransform(); + vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); - } + }; this.currentWidth = (this.width + strokeWidth) * this.scaleX; this.currentHeight = (this.height + strokeWidth) * this.scaleY; @@ -15746,20 +15742,20 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), - wh = new fabric.Point(this.currentWidth, this.currentHeight); - var _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY); - var _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)); - var _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)); - var _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)); - var tl = f(_tl); - var tr = f(_tr); - var br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))); - var bl = f(_bl); - var ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))); - var mt = f(_mt); - var mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))); - var mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))); - var mtr = f(new fabric.Point(_mt.x, _mt.y)); + wh = new fabric.Point(this.currentWidth, this.currentHeight), + _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), + _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), + _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), + _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), + tl = f(_tl), + tr = f(_tr), + br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), + bl = f(_bl), + ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), + mt = f(_mt), + mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), + mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), + mtr = f(new fabric.Point(_mt.x, _mt.y)); // padding var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), @@ -16370,8 +16366,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot height = wh.y, left = -(width / 2), top = -(height / 2), - _left, - _top, padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, @@ -18304,6 +18298,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot q: 4, t: 2, a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' }; if (fabric.Path) { @@ -18873,12 +18871,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - var command = coordsParsed[0].toLowerCase(), - commandLength = commandLengths[command]; + var command = coordsParsed[0], + commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; if (coordsParsed.length - 1 > commandLength) { for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([ coordsParsed[0] ].concat(coordsParsed.slice(k, k + commandLength))); + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; } } else { @@ -24728,6 +24728,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; this.__lastIsEditing = this.isEditing; + this.__lastSelected = this.selected; }, isDoubleClick: function(newPointer) { @@ -24839,7 +24840,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__isMousedown = false; if (this._isObjectMoved(options.e)) return; - if (this.selected) { + if (this.__lastSelected) { this.enterEditing(); this.initDelayedCursor(true); } diff --git a/src/canvas.class.js b/src/canvas.class.js index bd7326e2..bde7725f 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -729,7 +729,6 @@ // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { - console.log('AG', activeGroup); return activeGroup; } @@ -1118,7 +1117,8 @@ _drawControls: function(ctx, object, klass) { ctx.save(); fabric[klass].prototype.transform.call(object, ctx); - object.drawBorders(ctx).drawControls(ctx); + //object.drawBorders(ctx).drawControls(ctx); + object._renderControls(ctx); ctx.restore(); } }); From 8c826a32047d7c6cb4799b33c12839af32fe7fd7 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 6 Jun 2014 17:36:17 +0100 Subject: [PATCH 08/27] Fixes for controlsAboveOverlay --- dist/fabric.js | 85 ++++++++----------------------- dist/fabric.require.js | 62 ++++------------------ src/brushes/circle_brush.class.js | 7 ++- src/canvas.class.js | 15 +----- src/shapes/group.class.js | 23 +++++---- src/shapes/image.class.js | 7 --- src/shapes/object.class.js | 5 -- src/shapes/path.class.js | 5 -- src/shapes/path_group.class.js | 6 --- src/shapes/text.class.js | 5 -- src/static_canvas.class.js | 19 +++---- 11 files changed, 57 insertions(+), 182 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 59ba3840..b9b42cdd 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -9475,17 +9475,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ _draw: function (ctx, object) { if (!object) return; - - if (this.controlsAboveOverlay) { - var hasBorders = object.hasBorders, hasControls = object.hasControls; - object.hasBorders = object.hasControls = false; - object.render(ctx); - object.hasBorders = hasBorders; - object.hasControls = hasControls; - } - else { - object.render(ctx); - } + + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + object.render(ctx); + ctx.restore(); + if (!this.controlsAboveOverlay) object._renderControls(ctx); }, /** @@ -9572,7 +9568,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable */ renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], activeGroup = this.getActiveGroup(); @@ -12158,7 +12153,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @private */ _drawGroupControls: function(ctx, activeGroup) { - this._drawControls(ctx, activeGroup, 'Group'); + activeGroup._renderControls(ctx); }, /** @@ -12167,20 +12162,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _drawObjectsControls: function(ctx) { for (var i = 0, len = this._objects.length; i < len; ++i) { if (!this._objects[i] || !this._objects[i].active) continue; - this._drawControls(ctx, this._objects[i], 'Object'); + this._objects[i]._renderControls(ctx); this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; } - }, - - /** - * @private - */ - _drawControls: function(ctx, object, klass) { - ctx.save(); - fabric[klass].prototype.transform.call(object, ctx); - //object.drawBorders(ctx).drawControls(ctx); - object._renderControls(ctx); - ctx.restore(); } }); @@ -14628,15 +14612,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._restoreFillRule(ctx); ctx.restore(); - - this._renderControls(ctx, noTransform); }, _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.getViewportTransform(); - - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); @@ -18734,9 +18713,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -18755,8 +18731,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -19109,13 +19083,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } - this.transform(ctx); this._setShadow(ctx); @@ -19126,8 +19096,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx); }, /** @@ -19504,23 +19472,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); - this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + this.callSuper('_renderControls', ctx, noTransform) + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + /** * @private */ _renderObject: function(object, ctx) { - var v = this.getViewportTransform(), - sxy = fabric.util.transformPoint( - new fabric.Point(this.scaleX, this.scaleY), - v, - true - ); - - var originalHasRotatingPoint = object.hasRotatingPoint, - groupScaleFactor = Math.max(sxy.x, sxy.y); + var originalHasRotatingPoint = object.hasRotatingPoint; // do not render if object is not visible if (!object.visible) return; @@ -19944,11 +19915,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix, - v = this.getViewportTransform(), isInPathGroup = this.group && this.group.type === 'path-group'; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2, -this.group.height/2); @@ -19963,7 +19931,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.translate(this.width/2, this.height/2); } - ctx.save(); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx); @@ -19973,9 +19940,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); - ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -22429,17 +22393,12 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - var m = this.transformMatrix; if (m && (!this.group || this.group.type === 'path-group')) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } this._render(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/dist/fabric.require.js b/dist/fabric.require.js index c3bdfc1e..b7596fb3 100644 --- a/dist/fabric.require.js +++ b/dist/fabric.require.js @@ -9475,17 +9475,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ */ _draw: function (ctx, object) { if (!object) return; - - if (this.controlsAboveOverlay) { - var hasBorders = object.hasBorders, hasControls = object.hasControls; - object.hasBorders = object.hasControls = false; - object.render(ctx); - object.hasBorders = hasBorders; - object.hasControls = hasControls; - } - else { - object.render(ctx); - } + + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + object.render(ctx); + ctx.restore(); + if (!this.controlsAboveOverlay) object._renderControls(ctx); }, /** @@ -9572,7 +9568,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable */ renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], activeGroup = this.getActiveGroup(); @@ -11790,7 +11785,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { - console.log('AG', activeGroup); return activeGroup; } @@ -12178,7 +12172,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _drawControls: function(ctx, object, klass) { ctx.save(); - fabric[klass].prototype.transform.call(object, ctx); + //fabric[klass].prototype.transform.call(object, ctx); //object.drawBorders(ctx).drawControls(ctx); object._renderControls(ctx); ctx.restore(); @@ -14629,15 +14623,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._restoreFillRule(ctx); ctx.restore(); - - this._renderControls(ctx, noTransform); }, _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.getViewportTransform(); - - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); @@ -18735,9 +18724,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -18756,8 +18742,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -19110,13 +19094,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } - this.transform(ctx); this._setShadow(ctx); @@ -19127,8 +19107,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx); }, /** @@ -19505,7 +19483,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.clipTo && ctx.restore(); - this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, @@ -19513,15 +19490,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _renderObject: function(object, ctx) { - var v = this.getViewportTransform(), - sxy = fabric.util.transformPoint( - new fabric.Point(this.scaleX, this.scaleY), - v, - true - ); - - var originalHasRotatingPoint = object.hasRotatingPoint, - groupScaleFactor = Math.max(sxy.x, sxy.y); + var originalHasRotatingPoint = object.hasRotatingPoint; // do not render if object is not visible if (!object.visible) return; @@ -19529,6 +19498,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot object.hasRotatingPoint = false; object.render(ctx); + object._renderControls(ctx); object.hasRotatingPoint = originalHasRotatingPoint; }, @@ -19945,11 +19915,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix, - v = this.getViewportTransform(), isInPathGroup = this.group && this.group.type === 'path-group'; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2, -this.group.height/2); @@ -19964,7 +19931,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.translate(this.width/2, this.height/2); } - ctx.save(); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx); @@ -19974,9 +19940,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); - ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** @@ -22430,17 +22393,12 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - var m = this.transformMatrix; if (m && (!this.group || this.group.type === 'path-group')) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } this._render(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index af24e237..16f19f82 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -26,9 +26,8 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric */ drawDot: function(pointer) { var point = this.addPoint(pointer), - ctx = this.canvas.contextTop; - - var v = this.canvas.viewportTransform; + ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -37,7 +36,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); - + ctx.restore(); }, diff --git a/src/canvas.class.js b/src/canvas.class.js index bde7725f..d28273cc 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -1097,7 +1097,7 @@ * @private */ _drawGroupControls: function(ctx, activeGroup) { - this._drawControls(ctx, activeGroup, 'Group'); + activeGroup._renderControls(ctx); }, /** @@ -1106,20 +1106,9 @@ _drawObjectsControls: function(ctx) { for (var i = 0, len = this._objects.length; i < len; ++i) { if (!this._objects[i] || !this._objects[i].active) continue; - this._drawControls(ctx, this._objects[i], 'Object'); + this._objects[i]._renderControls(ctx); this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; } - }, - - /** - * @private - */ - _drawControls: function(ctx, object, klass) { - ctx.save(); - fabric[klass].prototype.transform.call(object, ctx); - //object.drawBorders(ctx).drawControls(ctx); - object._renderControls(ctx); - ctx.restore(); } }); diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 6449911d..4b1554b8 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -224,23 +224,26 @@ this.clipTo && ctx.restore(); - this.callSuper('_renderControls', ctx, noTransform); ctx.restore(); }, + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + this.callSuper('_renderControls', ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + /** * @private */ _renderObject: function(object, ctx) { - var v = this.getViewportTransform(), - sxy = fabric.util.transformPoint( - new fabric.Point(this.scaleX, this.scaleY), - v, - true - ); - - var originalHasRotatingPoint = object.hasRotatingPoint, - groupScaleFactor = Math.max(sxy.x, sxy.y); + var originalHasRotatingPoint = object.hasRotatingPoint; // do not render if object is not visible if (!object.visible) return; diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index c485042a..1f91357b 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -123,11 +123,8 @@ ctx.save(); var m = this.transformMatrix, - v = this.getViewportTransform(), isInPathGroup = this.group && this.group.type === 'path-group'; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2, -this.group.height/2); @@ -142,7 +139,6 @@ ctx.translate(this.width/2, this.height/2); } - ctx.save(); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx); @@ -152,9 +148,6 @@ this._renderStroke(ctx); this.clipTo && ctx.restore(); ctx.restore(); - ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 407c4975..e175dfb7 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -967,15 +967,10 @@ this._restoreFillRule(ctx); ctx.restore(); - - this._renderControls(ctx, noTransform); }, _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.getViewportTransform(); - - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m && !this.group) { ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index bccdf6d7..bf21bf7b 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -455,9 +455,6 @@ ctx.save(); var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } @@ -476,8 +473,6 @@ this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/shapes/path_group.class.js b/src/shapes/path_group.class.js index 7c1acbab..da89a2c4 100644 --- a/src/shapes/path_group.class.js +++ b/src/shapes/path_group.class.js @@ -78,13 +78,9 @@ var m = this.transformMatrix; - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } - this.transform(ctx); this._setShadow(ctx); @@ -95,8 +91,6 @@ this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx); }, /** diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index a942ad00..6f30c625 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -766,17 +766,12 @@ if (!this.visible) return; ctx.save(); - var v = this.getViewportTransform(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - var m = this.transformMatrix; if (m && (!this.group || this.group.type === 'path-group')) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } this._render(ctx); ctx.restore(); - - this.callSuper('_renderControls', ctx, noTransform); }, /** diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 85c5c44b..23f816ab 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -650,17 +650,13 @@ */ _draw: function (ctx, object) { if (!object) return; - - if (this.controlsAboveOverlay) { - var hasBorders = object.hasBorders, hasControls = object.hasControls; - object.hasBorders = object.hasControls = false; - object.render(ctx); - object.hasBorders = hasBorders; - object.hasControls = hasControls; - } - else { - object.render(ctx); - } + + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + object.render(ctx); + ctx.restore(); + if (!this.controlsAboveOverlay) object._renderControls(ctx); }, /** @@ -747,7 +743,6 @@ * @chainable */ renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], activeGroup = this.getActiveGroup(); From 6924df7154e9b2a8ca7197641c92e7f229db398f Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 6 Jun 2014 20:46:34 +0100 Subject: [PATCH 09/27] Replaced viewport-moving functions. --- dist/fabric.js | 66 ++++++++++++++++++++++-------------- src/static_canvas.class.js | 68 ++++++++++++++++++++------------------ 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index b9b42cdd..332a41b6 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -9368,7 +9368,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ /** * Returns point at center of viewport - * @return {fabric.Point} the top left corner of the viewport + * @return {fabric.Point} the center of the viewport */ getViewportCenter: function () { var wh = fabric.util.transformPoint( @@ -9378,7 +9378,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ x = this.viewportTransform[4], y = this.viewportTransform[5]; - return new fabric.Point(wh.x + x, wh.y + y); + return new fabric.Point(wh.x/2 + x, wh.y/2 + y); }, /** @@ -9396,6 +9396,28 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this; }, + /** + * Sets zoom level of this canvas instance, zoom centered around point + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out @@ -9403,44 +9425,41 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable true */ setZoom: function (value) { - // TODO: just change the scale, preserve other transformations - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } + this.zoomToPoint(new fabric.Point(0, 0), value); return this; }, /** - * Centers viewport of this canvas instance on given point - * @param {Numer} x value for center of viewport - * @param {Numer} y value for center of viewport + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to * @return {fabric.Canvas} instance * @chainable true */ - setViewportCenter: function (x, y) { + absolutePan: function (point) { var wh = fabric.util.transformPoint( new fabric.Point(this.getWidth(), this.getHeight()), this.viewportTransform ); - this.viewportTransform[4] = x - wh.x/2; - this.viewportTransform[5] = y - wh.y/2; + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } - return this; + return this }, /** - * Centers viewport of this canvas instance + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by * @return {fabric.Canvas} instance * @chainable true */ - centerViewport: function () { - return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); }, /** @@ -10701,9 +10720,8 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric */ drawDot: function(pointer) { var point = this.addPoint(pointer), - ctx = this.canvas.contextTop; - - var v = this.canvas.viewportTransform; + ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10712,7 +10730,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); - + ctx.restore(); }, @@ -19481,7 +19499,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - this.callSuper('_renderControls', ctx, noTransform) + this.callSuper('_renderControls', ctx, noTransform); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i]._renderControls(ctx); } diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 23f816ab..8b63b0b4 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -541,21 +541,6 @@ return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, - /** - * Returns point at center of viewport - * @return {fabric.Point} the top left corner of the viewport - */ - getViewportCenter: function () { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ), - x = this.viewportTransform[4], - y = this.viewportTransform[5]; - - return new fabric.Point(wh.x + x, wh.y + y); - }, - /** * Sets viewport transform of this canvas instance * @param {Array} vpt the transform in the form of context.transform @@ -571,6 +556,28 @@ return this; }, + /** + * Sets zoom level of this canvas instance, zoom centered around point + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out @@ -578,44 +585,41 @@ * @chainable true */ setZoom: function (value) { - // TODO: just change the scale, preserve other transformations - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } + this.zoomToPoint(new fabric.Point(0, 0), value); return this; }, /** - * Centers viewport of this canvas instance on given point - * @param {Numer} x value for center of viewport - * @param {Numer} y value for center of viewport + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to * @return {fabric.Canvas} instance * @chainable true */ - setViewportCenter: function (x, y) { + absolutePan: function (point) { var wh = fabric.util.transformPoint( new fabric.Point(this.getWidth(), this.getHeight()), this.viewportTransform ); - this.viewportTransform[4] = x - wh.x/2; - this.viewportTransform[5] = y - wh.y/2; + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } - return this; + return this }, /** - * Centers viewport of this canvas instance + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by * @return {fabric.Canvas} instance * @chainable true */ - centerViewport: function () { - return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); }, /** From cd4d8b2c174002844898ef620e94d78afe9e932d Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 12 Jun 2014 10:13:08 +0100 Subject: [PATCH 10/27] Fix group.hasMoved --- dist/fabric.js | 16 +----- dist/fabric.require.js | 104 ++++++++++++++++++++------------------ src/shapes/group.class.js | 1 + 3 files changed, 56 insertions(+), 65 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 332a41b6..e2fd4c7a 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -9366,21 +9366,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, - /** - * Returns point at center of viewport - * @return {fabric.Point} the center of the viewport - */ - getViewportCenter: function () { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ), - x = this.viewportTransform[4], - y = this.viewportTransform[5]; - - return new fabric.Point(wh.x/2 + x, wh.y/2 + y); - }, - /** * Sets viewport transform of this canvas instance * @param {Array} vpt the transform in the form of context.transform @@ -19329,6 +19314,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend(this, options); } this._setOpacityIfSame(); + this.saveCoords(); }, /** diff --git a/dist/fabric.require.js b/dist/fabric.require.js index b7596fb3..a3f4c596 100644 --- a/dist/fabric.require.js +++ b/dist/fabric.require.js @@ -9366,21 +9366,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, - /** - * Returns point at center of viewport - * @return {fabric.Point} the top left corner of the viewport - */ - getViewportCenter: function () { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ), - x = this.viewportTransform[4], - y = this.viewportTransform[5]; - - return new fabric.Point(wh.x + x, wh.y + y); - }, - /** * Sets viewport transform of this canvas instance * @param {Array} vpt the transform in the form of context.transform @@ -9396,6 +9381,28 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this; }, + /** + * Sets zoom level of this canvas instance, zoom centered around point + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out @@ -9403,44 +9410,41 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @chainable true */ setZoom: function (value) { - // TODO: just change the scale, preserve other transformations - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } + this.zoomToPoint(new fabric.Point(0, 0), value); return this; }, /** - * Centers viewport of this canvas instance on given point - * @param {Numer} x value for center of viewport - * @param {Numer} y value for center of viewport + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to * @return {fabric.Canvas} instance * @chainable true */ - setViewportCenter: function (x, y) { + absolutePan: function (point) { var wh = fabric.util.transformPoint( new fabric.Point(this.getWidth(), this.getHeight()), this.viewportTransform ); - this.viewportTransform[4] = x - wh.x/2; - this.viewportTransform[5] = y - wh.y/2; + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } - return this; + return this }, /** - * Centers viewport of this canvas instance + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by * @return {fabric.Canvas} instance * @chainable true */ - centerViewport: function () { - return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2); + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); }, /** @@ -10701,9 +10705,8 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric */ drawDot: function(pointer) { var point = this.addPoint(pointer), - ctx = this.canvas.contextTop; - - var v = this.canvas.viewportTransform; + ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10712,7 +10715,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); - + ctx.restore(); }, @@ -12153,7 +12156,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @private */ _drawGroupControls: function(ctx, activeGroup) { - this._drawControls(ctx, activeGroup, 'Group'); + activeGroup._renderControls(ctx); }, /** @@ -12162,20 +12165,9 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _drawObjectsControls: function(ctx) { for (var i = 0, len = this._objects.length; i < len; ++i) { if (!this._objects[i] || !this._objects[i].active) continue; - this._drawControls(ctx, this._objects[i], 'Object'); + this._objects[i]._renderControls(ctx); this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; } - }, - - /** - * @private - */ - _drawControls: function(ctx, object, klass) { - ctx.save(); - //fabric[klass].prototype.transform.call(object, ctx); - //object.drawBorders(ctx).drawControls(ctx); - object._renderControls(ctx); - ctx.restore(); } }); @@ -19322,6 +19314,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend(this, options); } this._setOpacityIfSame(); + this.saveCoords(); }, /** @@ -19486,6 +19479,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.restore(); }, + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + this.callSuper('_renderControls', ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + /** * @private */ @@ -19498,7 +19503,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot object.hasRotatingPoint = false; object.render(ctx); - object._renderControls(ctx); object.hasRotatingPoint = originalHasRotatingPoint; }, diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 4b1554b8..2fcb5b39 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -63,6 +63,7 @@ extend(this, options); } this._setOpacityIfSame(); + this.saveCoords(); }, /** From b13e5763b6b0f9d5e3e631093bd77b0dce8ce0c7 Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 12 Jun 2014 12:37:20 +0100 Subject: [PATCH 11/27] Calculate group coordinates when initializing --- src/shapes/group.class.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 2fcb5b39..b7e0c570 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -63,6 +63,7 @@ extend(this, options); } this._setOpacityIfSame(); + //this.setCoords(); this.saveCoords(); }, From 3262a66e9904e481b33771dc9b32075e4ab8bb33 Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 12 Jun 2014 12:37:59 +0100 Subject: [PATCH 12/27] Build and minify --- dist/fabric.js | 1 + dist/fabric.min.js | 19 +- dist/fabric.min.js.gz | Bin 54856 -> 79867 bytes dist/fabric.require.js | 37718 +++++++++++++-------------------------- 4 files changed, 12354 insertions(+), 25384 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index e2fd4c7a..9fd9c6d7 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -19314,6 +19314,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend(this, options); } this._setOpacityIfSame(); + //this.setCoords(); this.saveCoords(); }, diff --git a/dist/fabric.min.js b/dist/fabric.min.js index 2d03ca19..4dd89d34 100644 --- a/dist/fabric.min.js +++ b/dist/fabric.min.js @@ -1,7 +1,12 @@ -/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.4.6"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined",fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],function(){function e(e,t){if(!this.__eventListeners[e])return;t?fabric.util.removeFromArray(this.__eventListeners[e],t):this.__eventListeners[e].length=0}function t(e,t){this.__eventListeners||(this.__eventListeners={});if(arguments.length===1)for(var n in e)this.on(n,e[n]);else this.__eventListeners[e]||(this.__eventListeners[e]=[]),this.__eventListeners[e].push(t);return this}function n(t,n){if(!this.__eventListeners)return;if(arguments.length===0)this.__eventListeners={};else if(arguments.length===1&&typeof arguments[0]=="object")for(var r in t)e.call(this,r,t[r]);else e.call(this,t,n);return this}function r(e,t){if(!this.__eventListeners)return;var n=this.__eventListeners[e];if(!n)return;for(var r=0,i=n.length;r-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)}},function(e){var t=Math.sqrt,n=Math.atan2,r=Math.PI/180;fabric.util={removeFromArray:function(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e},getRandomInt:function(e,t){return Math.floor(Math.random()*(t-e+1))+e},degreesToRadians:function(e){return e*r},radiansToDegrees:function(e){return e/r},rotatePoint:function(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)},toFixed:function(e,t){return parseFloat(Number(e).toFixed(t))},falseFunction:function(){return!1},getKlass:function(e,t){return e=fabric.util.string.camelize(e.charAt(0).toUpperCase()+e.slice(1)),fabric.util.resolveNamespace(t)[e]},resolveNamespace:function(t){if(!t)return fabric;var n=t.split("."),r=n.length,i=e||fabric.window;for(var s=0;s1?r=new fabric.PathGroup(e,t):r=e[0],typeof n!="undefined"&&r.setSourcePath(n),r},populateWithProperties:function(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;rr)r+=u[p++%h],r>l&&(r=l),e[d?"lineTo":"moveTo"](r,0),d=!d;e.restore()},createCanvasElement:function(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}},clipContext:function(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()},multiplyTransformMatrices:function(e,t){var n=[[e[0],e[2],e[4]],[e[1],e[3],e[5]],[0,0,1]],r=[[t[0],t[2],t[4]],[t[1],t[3],t[5]],[0,0,1]],i=[];for(var s=0;s<3;s++){i[s]=[];for(var o=0;o<3;o++){var u=0;for(var a=0;a<3;a++)u+=n[s][a]*r[a][o];i[s][o]=u}}return[i[0][0],i[1][0],i[0][1],i[1][1],i[0][2],i[1][2]]},getFunctionBody:function(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},normalizePoints:function(e,t){var n=fabric.util.array.min(e,"x"),r=fabric.util.array.min(e,"y");n=n<0?n:0,r=n<0?r:0;for(var i=0,s=e.length;i0&&(t>r?t-=r:t=0,n>r?n-=r:n=0);var i=!0,s=e.getImageData(t,n,r*2||1,r*2||1);for(var o=3,u=s.data.length;o0&&f===0&&(E-=2*Math.PI);var S=Math.ceil(Math.abs(E/(Math.PI*.5+.001))),x=[];for(var T=0;T1&&(h=Math.sqrt(h),t*=h,n*=h);var p=f/t,d=a/t,v=-a/n,m=f/n;return{x0:p*r+d*i,y0:v*r+m*i,x1:p*s+d*o,y1:v*s+m*o,sinTh:a,cosTh:f}}function o(e,i,s,o,u,a,f,l){r=n.call(arguments);if(t[r])return t[r];var c=l*u,h=-f*a,p=f*u,d=l*a,v=.5*(o-s),m=8/3*Math.sin(v*.5)*Math.sin(v*.5)/Math.sin(v),g=e+Math.cos(s)-m*Math.sin(s),y=i+Math.sin(s)+m*Math.cos(s),b=e+Math.cos(o),w=i+Math.sin(o),E=b+m*Math.sin(o),S=w-m*Math.cos(o);return t[r]=[c*g+h*y,p*g+d*y,c*E+h*S,p*E+d*S,c*b+h*w,p*b+d*w],t[r]}var e={},t={},n=Array.prototype.join,r;fabric.util.drawArc=function(e,t,n,r){var s=r[0],u=r[1],a=r[2],f=r[3],l=r[4],c=r[5],h=r[6],p=i(c,h,s,u,f,l,a,t,n);for(var d=0;d=t})}function r(e,t){return i(e,t,function(e,t){return e>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r))));if(r>=n)return-1;var i=r>=0?r:Math.max(n-Math.abs(r),0);for(;i>>0;n>>0;r>>0;n>>0;n>>0;i>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:e,capitalize:t,escapeXml:n}}(),function(){var e=Array.prototype.slice,t=Function.prototype.apply,n=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var i=this,s=e.call(arguments,1),o;return s.length?o=function(){return t.call(i,this instanceof n?this:r,s.concat(e.call(arguments)))}:o=function(){return t.call(i,this instanceof n?this:r,arguments)},n.prototype=this.prototype,o.prototype=new n,o})}(),function(){function i(){}function s(t){var n=this.constructor.superclass.prototype[t];return arguments.length>1?n.apply(this,e.call(arguments,1)):n.call(this)}function o(){function u(){this.initialize.apply(this,arguments)}var n=null,o=e.call(arguments,0);typeof o[0]=="function"&&(n=o.shift()),u.superclass=n,u.subclasses=[],n&&(i.prototype=n.prototype,u.prototype=new i,n.subclasses.push(u));for(var a=0,f=o.length;a-1?e.prototype[i]=function(e){return function(){var n=this.constructor.superclass;this.constructor.superclass=r;var i=t[e].apply(this,arguments);this.constructor.superclass=n;if(e!=="initialize")return i}}(i):e.prototype[i]=t[i],n&&(t.toString!==Object.prototype.toString&&(e.prototype.toString=t.toString),t.valueOf!==Object.prototype.valueOf&&(e.prototype.valueOf=t.valueOf))};fabric.util.createClass=o}(),function(){function t(e){var t=Array.prototype.slice.call(arguments,1),n,r,i=t.length;for(r=0;r-1?s(e,t.match(/opacity:\s*(\d?\.?\d*)/)[1]):e;for(var r in t)if(r==="opacity")s(e,t[r]);else{var i=r==="float"||r==="cssFloat"?typeof n.styleFloat=="undefined"?"cssFloat":"styleFloat":r;n[i]=t[r]}return e}var t=fabric.document.createElement("div"),n=typeof t.style.opacity=="string",r=typeof t.style.filter=="string",i=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(e){return e};n?s=function(e,t){return e.style.opacity=t,e}:r&&(s=function(e,t){var n=e.style;return e.currentStyle&&!e.currentStyle.hasLayout&&(n.zoom=1),i.test(n.filter)?(t=t>=.9999?"":"alpha(opacity="+t*100+")",n.filter=n.filter.replace(i,t)):n.filter+=" alpha(opacity="+t*100+")",e}),fabric.util.setStyle=e}(),function(){function t(e){return typeof e=="string"?fabric.document.getElementById(e):e}function s(e,t){var n=fabric.document.createElement(e);for(var r in t)r==="class"?n.className=t[r]:r==="for"?n.htmlFor=t[r]:n.setAttribute(r,t[r]);return n}function o(e,t){e&&(" "+e.className+" ").indexOf(" "+t+" ")===-1&&(e.className+=(e.className?" ":"")+t)}function u(e,t,n){return typeof t=="string"&&(t=s(t,n)),e.parentNode&&e.parentNode.replaceChild(t,e),t.appendChild(e),t}function a(e,t){var n,r,i=0,s=0,o=fabric.document.documentElement,u=fabric.document.body||{scrollLeft:0,scrollTop:0};r=e;while(e&&e.parentNode&&!n)e=e.parentNode,e!==fabric.document&&fabric.util.getElementStyle(e,"position")==="fixed"&&(n=e),e!==fabric.document&&r!==t&&fabric.util.getElementStyle(e,"position")==="absolute"?(i=0,s=0):e===fabric.document?(i=u.scrollLeft||o.scrollLeft||0,s=u.scrollTop||o.scrollTop||0):(i+=e.scrollLeft||0,s+=e.scrollTop||0);return{left:i,top:s}}function f(e){var t,n=e&&e.ownerDocument,r={left:0,top:0},i={left:0,top:0},s,o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!n)return{left:0,top:0};for(var u in o)i[o[u]]+=parseInt(l(e,u),10)||0;return t=n.documentElement,typeof e.getBoundingClientRect!="undefined"&&(r=e.getBoundingClientRect()),s=fabric.util.getScrollLeftTop(e,null),{left:r.left+s.left-(t.clientLeft||0)+i.left,top:r.top+s.top-(t.clientTop||0)+i.top}}var e=Array.prototype.slice,n,r=function(t){return e.call(t,0)};try{n=r(fabric.document.childNodes)instanceof Array}catch(i){}n||(r=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var l;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?l=function(e,t){return fabric.document.defaultView.getComputedStyle(e,null)[t]}:l=function(e,t){var n=e.style[t];return!n&&e.currentStyle&&(n=e.currentStyle[t]),n},function(){function n(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=fabric.util.falseFunction),t?e.style[t]="none":typeof e.unselectable=="string"&&(e.unselectable="on"),e}function r(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=null),t?e.style[t]="":typeof e.unselectable=="string"&&(e.unselectable=""),e}var e=fabric.document.documentElement.style,t="userSelect"in e?"userSelect":"MozUserSelect"in e?"MozUserSelect":"WebkitUserSelect"in e?"WebkitUserSelect":"KhtmlUserSelect"in e?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=n,fabric.util.makeElementSelectable=r}(),function(){function e(e,t){var n=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),i=!0;r.onload=r.onreadystatechange=function(e){if(i){if(typeof this.readyState=="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;i=!1,t(e||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=e,n.appendChild(r)}fabric.util.getScript=e}(),fabric.util.getById=t,fabric.util.toArray=r,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getScrollLeftTop=a,fabric.util.getElementOffset=f,fabric.util.getElementStyle=l}(),function(){function e(e,t){return e+(/\?/.test(e)?"&":"?")+t}function n(){}function r(r,i){i||(i={});var s=i.method?i.method.toUpperCase():"GET",o=i.onComplete||function(){},u=t(),a;return u.onreadystatechange=function(){u.readyState===4&&(o(u),u.onreadystatechange=n)},s==="GET"&&(a=null,typeof i.parameters=="string"&&(r=e(r,i.parameters))),u.open(s,r,!0),(s==="POST"||s==="PUT")&&u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(a),u}var t=function(){var e=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var t=e.length;t--;)try{var n=e[t]();if(n)return e[t]}catch(r){}}();fabric.util.request=r}(),fabric.log=function(){},fabric.warn=function(){},typeof console!="undefined"&&["log","warn"].forEach(function(e){typeof console[e]!="undefined"&&console[e].apply&&(fabric[e]=function(){return console[e].apply(console,arguments)})}),function(){function e(e){n(function(t){e||(e={});var r=t||+(new Date),i=e.duration||500,s=r+i,o,u=e.onChange||function(){},a=e.abort||function(){return!1},f=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},l="startValue"in e?e.startValue:0,c="endValue"in e?e.endValue:100,h=e.byValue||c-l;e.onStart&&e.onStart(),function p(t){o=t||+(new Date);var c=o>s?i:o-r;if(a()){e.onComplete&&e.onComplete();return}u(f(c,l,h,i));if(o>s){e.onComplete&&e.onComplete();return}n(p)}(r)})}function n(){return t.apply(fabric.window,arguments)}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)};fabric.util.animate=e,fabric.util.requestAnimFrame=n}(),function(){function e(e,t,n,r){return e','')}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s=t.util.toFixed,o=t.util.multiplyTransformMatrices,u={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},a={stroke:"strokeOpacity",fill:"fillOpacity"};t.parseTransformAttribute=function(){function e(e,t){var n=t[0];e[0]=Math.cos(n),e[1]=Math.sin(n),e[2]=-Math.sin(n),e[3]=Math.cos(n)}function n(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function r(e,t){e[2]=t[0]}function i(e,t){e[1]=t[0]}function s(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var o=[1,0,0,1,0,0],u="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",a="(?:\\s+,?\\s*|,\\s*)",f="(?:(skewX)\\s*\\(\\s*("+u+")\\s*\\))",l="(?:(skewY)\\s*\\(\\s*("+u+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+")"+a+"("+u+"))?\\s*\\))",h="(?:(scale)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",p="(?:(translate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",d="(?:(matrix)\\s*\\(\\s*("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+"\\s*\\))",v="(?:"+d+"|"+p+"|"+h+"|"+c+"|"+f+"|"+l+")",m="(?:"+v+"(?:"+a+v+")*"+")",g="^\\s*(?:"+m+"?)\\s*$",y=new RegExp(g),b=new RegExp(v,"g");return function(u){var a=o.concat(),f=[];if(!u||u&&!y.test(u))return a;u.replace(b,function(u){var l=(new RegExp(v)).exec(u).filter(function(e){return e!==""&&e!=null}),c=l[1],h=l.slice(2).map(parseFloat);switch(c){case"translate":s(a,h);break;case"rotate":h[0]=t.util.degreesToRadians(h[0]),e(a,h);break;case"scale":n(a,h);break;case"skewX":r(a,h);break;case"skewY":i(a,h);break;case"matrix":a=h}f.push(a.concat()),a=o.concat()});var l=f[0];while(f.length>1)f.shift(),l=t.util.multiplyTransformMatrices(l,f[0]);return l}}(),t.parseSVGDocument=function(){function s(e,t){while(e&&(e=e.parentNode))if(t.test(e.nodeName))return!0;return!1}var e=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,n="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",r=new RegExp("^\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*"+"$");return function(n,o,u){if(!n)return;var a=new Date,f=t.util.toArray(n.getElementsByTagName("*"));if(f.length===0&&t.isLikelyNode){f=n.selectNodes('//*[name(.)!="svg"]');var l=[];for(var c=0,h=f.length;c-1;e=e.split(/\s+/);var n=[],r,i;if(t){r=0,i=e.length;for(;r/i,"")));if(!s||!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){m.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),m.has(e,function(r){r?m.get(e,function(e){var t=g(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})},loadSVGFromString:function(e,n,r){e=e.trim();var i;if(typeof DOMParser!="undefined"){var s=new DOMParser;s&&s.parseFromString&&(i=s.parseFromString(e,"text/xml"))}else t.window.ActiveXObject&&(i=new ActiveXObject("Microsoft.XMLDOM"),i.async="false",i.loadXML(e.replace(//i,"")));t.parseSVGDocument(i.documentElement,function(e,t){n(e,t)},r)},createSVGFontFacesMarkup:function(e){var t="";for(var n=0,r=e.length;n',"",""].join("")),t},createSVGRefElementsMarkup:function(e){var t=[];return y(t,e,"backgroundColor"),y(t,e,"overlayColor"),t.join("")}})}(typeof exports!="undefined"?exports:this),fabric.ElementsParser=function(e,t,n,r){this.elements=e,this.callback=t,this.options=n,this.reviver=r},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var e=0,t=this.elements.length;ee.x&&this.y>e.y},gte:function(e){return this.x>=e.x&&this.y>=e.y},lerp:function(e,t){return new n(this.x+(e.x-this.x)*t,this.y+(e.y-this.y)*t)},distanceFrom:function(e){var t=this.x-e.x,n=this.y-e.y;return Math.sqrt(t*t+n*n)},midPointFrom:function(e){return new n(this.x+(e.x-this.x)/2,this.y+(e.y-this.y)/2)},min:function(e){return new n(Math.min(this.x,e.x),Math.min(this.y,e.y))},max:function(e){return new n(Math.max(this.x,e.x),Math.max(this.y,e.y))},toString:function(){return this.x+","+this.y},setXY:function(e,t){this.x=e,this.y=t},setFromPoint:function(e){this.x=e.x,this.y=e.y},swap:function(e){var t=this.x,n=this.y;this.x=e.x,this.y=e.y,e.x=t,e.y=n}}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){this.status=e,this.points=[]}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={appendPoint:function(e){this.points.push(e)},appendPoints:function(e){this.points=this.points.concat(e)}},t.Intersection.intersectLineLine=function(e,r,i,s){var o,u=(s.x-i.x)*(e.y-i.y)-(s.y-i.y)*(e.x-i.x),a=(r.x-e.x)*(e.y-i.y)-(r.y-e.y)*(e.x-i.x),f=(s.y-i.y)*(r.x-e.x)-(s.x-i.x)*(r.y-e.y);if(f!==0){var l=u/f,c=a/f;0<=l&&l<=1&&0<=c&&c<=1?(o=new n("Intersection"),o.points.push(new t.Point(e.x+l*(r.x-e.x),e.y+l*(r.y-e.y)))):o=new n}else u===0||a===0?o=new n("Coincident"):o=new n("Parallel");return o},t.Intersection.intersectLinePolygon=function(e,t,r){var i=new n,s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n,i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n;return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}function r(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]);if(e==="transparent"){this.setSource([255,255,255,0]);return}t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t||(t=n.sourceFromHsl(e)),t&&this.setSource(t)},_rgbToHsl:function(e,n,r){e/=255,n/=255,r/=255;var i,s,o,u=t.util.array.max([e,n,r]),a=t.util.array.min([e,n,r]);o=(u+a)/2;if(u===a)i=s=0;else{var f=u-a;s=o>.5?f/(2-u-a):f/(u+a);switch(u){case e:i=(n-r)/f+(n']:this.type==="radial"&&(r=["']);for(var i=0;i');return r.push(this.type==="linear"?"":""),r.join("")},toLive:function(e){var t;if(!this.type)return;this.type==="linear"?t=e.createLinearGradient(this.coords.x1,this.coords.y1,this.coords.x2,this.coords.y2):this.type==="radial"&&(t=e.createRadialGradient(this.coords.x1,this.coords.y1,this.coords.r1,this.coords.x2,this.coords.y2,this.coords.r2));for(var n=0,r=this.colorStops.length;n'+''+""},toLive:function(e){var t=typeof this.source=="function"?this.source():this.source;if(!t)return"";if(typeof t.src!="undefined"){if(!t.complete)return"";if(t.naturalWidth===0||t.naturalHeight===0)return""}return e.createPattern(t,this.repeat)}}),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Shadow){t.warn("fabric.Shadow is already defined.");return}t.Shadow=t.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(e){typeof e=="string"&&(e=this._parseShadow(e));for(var n in e)this[n]=e[n];this.id=t.Object.__uid++},_parseShadow:function(e){var n=e.trim(),r=t.Shadow.reOffsetsAndBlur.exec(n)||[],i=n.replace(t.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:i.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(e){var t="SourceAlpha";return e&&(e.fill===this.color||e.stroke===this.color)&&(t="SourceGraphic"),''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var e={},n=t.Shadow.prototype;return this.color!==n.color&&(e.color=this.color),this.blur!==n.blur&&(e.blur=this.blur),this.offsetX!==n.offsetX&&(e.offsetX=this.offsetX),this.offsetY!==n.offsetY&&(e.offsetY=this.offsetY),e}}),t.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/}(typeof exports!="undefined"?exports:this),function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var e=fabric.util.object.extend,t=fabric.util.getElementOffset,n=fabric.util.removeFromArray,r=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(e,t){t||(t={}),this._initStatic(e,t),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,onBeforeScaleRotate:function(){},_initStatic:function(e,t){this._objects=[],this._createLowerCanvas(e),this._initOptions(t),this._setImageSmoothing(),t.overlayImage&&this.setOverlayImage(t.overlayImage,this.renderAll.bind(this)),t.backgroundImage&&this.setBackgroundImage(t.backgroundImage,this.renderAll.bind(this)),t.backgroundColor&&this.setBackgroundColor(t.backgroundColor,this.renderAll.bind(this)),t.overlayColor&&this.setOverlayColor(t.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(e,t,n){return this.__setBgOverlayImage("overlayImage",e,t,n)},setBackgroundImage:function(e,t,n){return this.__setBgOverlayImage("backgroundImage",e,t,n)},setOverlayColor:function(e,t){return this.__setBgOverlayColor("overlayColor",e,t)},setBackgroundColor:function(e,t){return this.__setBgOverlayColor("backgroundColor",e,t)},_setImageSmoothing:function(){var e=this.getContext();e.imageSmoothingEnabled=this.imageSmoothingEnabled,e.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,e.mozImageSmoothingEnabled=this.imageSmoothingEnabled,e.msImageSmoothingEnabled=this.imageSmoothingEnabled,e.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(e,t,n,r){return typeof t=="string"?fabric.util.loadImage(t,function(t){this[e]=new fabric.Image(t,r),n&&n()},this):(this[e]=t,n&&n()),this},__setBgOverlayColor:function(e,t,n){if(t.source){var r=this;fabric.util.loadImage(t.source,function(i){r[e]=new fabric.Pattern({source:i,repeat:t.repeat,offsetX:t.offsetX,offsetY:t.offsetY}),n&&n()})}else this[e]=t,n&&n();return this},_createCanvasElement:function(){var e=fabric.document.createElement("canvas");e.style||(e.style={});if(!e)throw r;return this._initCanvasElement(e),e},_initCanvasElement:function(e){fabric.util.createCanvasElement(e);if(typeof e.getContext=="undefined")throw r},_initOptions:function(e){for(var t in e)this[t]=e[t];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(e){this.lowerCanvasEl=fabric.util.getById(e)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(e){return this._setDimension("width",e)},setHeight:function(e){return this._setDimension("height",e)},setDimensions:function(e){for(var t in e)this._setDimension(t,e[t]);return this},_setDimension:function(e,t){return this.lowerCanvasEl[e]=t,this.lowerCanvasEl.style[e]=t+"px",this.upperCanvasEl&&(this.upperCanvasEl[e]=t,this.upperCanvasEl.style[e]=t+"px"),this.cacheCanvasEl&&(this.cacheCanvasEl[e]=t),this.wrapperEl&&(this.wrapperEl.style[e]=t+"px"),this[e]=t,this.calcOffset(),this.renderAll(),this},getElement:function(){return this.lowerCanvasEl},getActiveObject:function(){return null},getActiveGroup:function(){return null},_draw:function(e,t){if(!t)return;if(this.controlsAboveOverlay){var n=t.hasBorders,r=t.hasControls;t.hasBorders=t.hasControls=!1,t.render(e),t.hasBorders=n,t.hasControls=r}else t.render(e)},_onObjectAdded:function(e){this.stateful&&e.setupState(),e.setCoords(),e.canvas=this,this.fire("object:added",{target:e}),e.fire("added")},_onObjectRemoved:function(e){this.getActiveObject()===e&&(this.fire("before:selection:cleared",{target:e}),this._discardActiveObject(),this.fire("selection:cleared")),this.fire("object:removed",{target:e}),e.fire("removed")},clearContext:function(e){return e.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.discardActiveGroup&&this.discardActiveGroup(),this.discardActiveObject&&this.discardActiveObject(),this.clearContext(this.contextContainer),this.contextTop&&this.clearContext(this.contextTop),this.fire("canvas:cleared"),this.renderAll(),this},renderAll:function(e){var t=this[e===!0&&this.interactive?"contextTop":"contextContainer"],n=this.getActiveGroup();return this.contextTop&&this.selection&&!this._groupSelector&&this.clearContext(this.contextTop),e||this.clearContext(t),this.fire("before:render"),this.clipTo&&fabric.util.clipContext(this,t),this._renderBackground(t),this._renderObjects(t,n),this._renderActiveGroup(t,n),this.clipTo&&t.restore(),this._renderOverlay(t),this.controlsAboveOverlay&&this.interactive&&this.drawControls(t),this.fire("after:render"),this},_renderObjects:function(e,t){var n,r;if(!t)for(n=0,r=this._objects.length;n"),n.join("")},_setSVGPreamble:function(e,t){t.suppressPreamble||e.push('','\n')},_setSVGHeader:function(e,t){e.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(e,t){var n=this.getActiveGroup();n&&this.discardActiveGroup();for(var r=0,i=this.getObjects(),s=i.length;r"):this[t]&&t==="overlayColor"&&e.push('")},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll&&this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll&&this.renderAll()},sendBackwards:function(e,t){var r=this._objects.indexOf(e);if(r!==0){var i=this._findNewLowerIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(e,t,n){var r;if(n){r=t;for(var i=t-1;i>=0;--i){var s=e.intersectsWithObject(this._objects[i])||e.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(e);if(s){r=i;break}}}else r=t-1;return r},bringForward:function(e,t){var r=this._objects.indexOf(e);if(r!==this._objects.length-1){var i=this._findNewUpperIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(e,t,n){var r;if(n){r=t;for(var i=t+1;i"}}),e(fabric.StaticCanvas.prototype,fabric.Observable),e(fabric.StaticCanvas.prototype,fabric.Collection),e(fabric.StaticCanvas.prototype,fabric.DataURLExporter),e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(e){var t=fabric.util.createCanvasElement();if(!t||!t.getContext)return null;var n=t.getContext("2d");if(!n)return null;switch(e){case"getImageData":return typeof n.getImageData!="undefined";case"setLineDash":return typeof n.setLineDash!="undefined";case"toDataURL":return typeof t.toDataURL!="undefined";case"toDataURLWithQuality":try{return t.toDataURL("image/jpeg",0),!0}catch(r){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(e){return this.shadow=new fabric.Shadow(e),this},_setBrushStyles:function(){var e=this.canvas.contextTop;e.strokeStyle=this.color,e.lineWidth=this.width,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var e=this.canvas.contextTop;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var e=this.canvas.contextTop;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0}}),function(){var e=fabric.util.array.min,t=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(e){this.canvas=e,this._points=[]},onMouseDown:function(e){this._prepareForDrawing(e),this._captureDrawingPath(e),this._render()},onMouseMove:function(e){this._captureDrawingPath(e),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(e){var t=new fabric.Point(e.x,e.y);this._reset(),this._addPoint(t),this.canvas.contextTop.moveTo(t.x,t.y)},_addPoint:function(e){this._points.push(e)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(e){var t=new fabric.Point(e.x,e.y);this._addPoint(t)},_render:function(){var e=this.canvas.contextTop;e.beginPath();var t=this._points[0],n=this._points[1];this._points.length===2&&t.x===n.x&&t.y===n.y&&(t.x-=.5,n.x+=.5),e.moveTo(t.x,t.y);for(var r=1,i=this._points.length;rn.padding?e.x<0?e.x+=n.padding:e.x-=n.padding:e.x=0,i(e.y)>n.padding?e.y<0?e.y+=n.padding:e.y-=n.padding:e.y=0},_rotateObject:function(e,t){var i=this._currentTransform;if(i.target.get("lockRotation"))return;var s=r(i.ey-i.top,i.ex-i.left),o=r(t-i.top,e-i.left),u=n(o-s+i.theta);u<0&&(u=360+u),i.target.angle=u},_setCursor:function(e){this.upperCanvasEl.style.cursor=e},_resetObjectTransform:function(e){e.scaleX=1,e.scaleY=1,e.setAngle(0)},_drawSelection:function(){var e=this.contextTop,t=this._groupSelector,n=t.left,r=t.top,o=i(n),u=i(r);e.fillStyle=this.selectionColor,e.fillRect(t.ex-(n>0?0:-n),t.ey-(r>0?0:-r),o,u),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var a=t.ex+s-(n>0?0:o),f=t.ey+s-(r>0?0:u);e.beginPath(),fabric.util.drawDashedLine(e,a,f,a+o,f,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f+u-1,a+o,f+u-1,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f,a,f+u,this.selectionDashArray),fabric.util.drawDashedLine(e,a+o-1,f,a+o-1,f+u,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+s-(n>0?0:o),t.ey+s-(r>0?0:u),o,u)},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e))},findTarget:function(e,t){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e))return this.lastRenderedObjectWithControlsAboveOverlay;var n=this.getActiveGroup();if(n&&!t&&this.containsPoint(e,n))return n;var r=this._searchPossibleTargets(e);return this._fireOverOutEvents(r),r},_fireOverOutEvents:function(e){e?this._hoveredTarget!==e&&(this.fire("mouse:over",{target:e}),e.fire("mouseover"),this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout")),this._hoveredTarget=e):this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null)},_checkTarget:function(e,t,n){if(t&&t.visible&&t.evented&&this.containsPoint(e,t)){if(!this.perPixelTargetFind&&!t.perPixelTargetFind||!!t.isEditing)return!0;var r=this.isTargetTransparent(t,n.x,n.y);if(!r)return!0}},_searchPossibleTargets:function(e){var t,n=this.getPointer(e),r=this._objects.length;while(r--)if(this._checkTarget(e,this._objects[r],n)){this.relatedTarget=this._objects[r],t=this._objects[r];break}return t},getPointer:function(t){var n=e(t,this.upperCanvasEl),r=this.upperCanvasEl.getBoundingClientRect(),i;return r.width===0||r.height===0?i={width:1,height:1}:i={width:this.upperCanvasEl.width/r.width,height:this.upperCanvasEl.height/r.height},{x:(n.x-this._offset.left)*i.width,y:(n.y-this._offset.top)*i.height}},_createUpperCanvas:function(){var e=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+e),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(e){var t=this.getWidth()||e.width,n=this.getHeight()||e.height;fabric.util.setStyle(e,{position:"absolute",width:t+"px",height:n+"px",left:0,top:0}),e.width=t,e.height=n,fabric.util.makeElementUnselectable(e)},_copyCanvasStyle:function(e,t){t.style.cssText=e.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(e){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=e,e.set("active",!0)},setActiveObject:function(e,t){return this._setActiveObject(e),this.renderAll(),this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t}),this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(e){return this._discardActiveObject(),this.renderAll(),this.fire("selection:cleared",{e:e}),this},_setActiveGroup:function(e){this._activeGroup=e,e&&(e.canvas=this,e.set("active",!0))},setActiveGroup:function(e,t){return this._setActiveGroup(e),e&&(this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var e=this.getActiveGroup();e&&e.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(e){return this._discardActiveGroup(),this.fire("selection:cleared",{e:e}),this},deactivateAll:function(){var e=this.getObjects(),t=0,n=e.length;for(;t1&&(t=new fabric.Group(t.reverse(),{originX:"center",originY:"center"}),this.setActiveGroup(t,e),t.saveCoords(),this.fire("selection:created",{target:t}),this.renderAll())},_collectObjects:function(){var n=[],r,i=this._groupSelector.ex,s=this._groupSelector.ey,o=i+this._groupSelector.left,u=s+this._groupSelector.top,a=new fabric.Point(e(i,o),e(s,u)),f=new fabric.Point(t(i,o),t(s,u)),l=i===o&&s===u;for(var c=this._objects.length;c--;){r=this._objects[c];if(!r||!r.selectable||!r.visible)continue;if(r.intersectsWithRect(a,f)||r.isContainedWithinRect(a,f)||r.containsPoint(a)||r.containsPoint(f)){r.set("active",!0),n.push(r);if(l)break}}return n},_maybeGroupObjects:function(e){this.selection&&this._groupSelector&&this._groupSelectedObjects(e);var t=this.getActiveGroup();t&&(t.setObjectsCoords().setCoords(),t.isMoving=!1,this._setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(e){e||(e={});var t=e.format||"png",n=e.quality||1,r=e.multiplier||1,i={left:e.left,top:e.top,width:e.width,height:e.height};return r!==1?this.__toDataURLWithMultiplier(t,n,i,r):this.__toDataURL(t,n,i)},__toDataURL:function(e,t,n){this.renderAll(!0);var r=this.upperCanvasEl||this.lowerCanvasEl,i=this.__getCroppedCanvas(r,n);e==="jpg"&&(e="jpeg");var s=fabric.StaticCanvas.supports("toDataURLWithQuality")?(i||r).toDataURL("image/"+e,t):(i||r).toDataURL("image/"+e);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),i&&(i=null),s},__getCroppedCanvas:function(e,t){var n,r,i="left"in t||"top"in t||"width"in t||"height"in t;return i&&(n=fabric.util.createCanvasElement(),r=n.getContext("2d"),n.width=t.width||this.width,n.height=t.height||this.height,r.drawImage(e,-t.left||0,-t.top||0)),n},__toDataURLWithMultiplier:function(e,t,n,r){var i=this.getWidth(),s=this.getHeight(),o=i*r,u=s*r,a=this.getActiveObject(),f=this.getActiveGroup(),l=this.contextTop||this.contextContainer;r>1&&this.setWidth(o).setHeight(u),l.scale(r,r),n.left&&(n.left*=r),n.top&&(n.top*=r),n.width?n.width*=r:r<1&&(n.width=o),n.height?n.height*=r:r<1&&(n.height=u),f?this._tempRemoveBordersControlsFromGroup(f):a&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var c=this.__toDataURL(e,t,n);return this.width=i,this.height=s,l.scale(1/r,1/r),this.setWidth(i).setHeight(s),f?this._restoreBordersControlsOnGroup(f):a&&this.setActiveObject&&this.setActiveObject(a),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),c},toDataURLWithMultiplier:function(e,t,n){return this.toDataURL({format:e,multiplier:t,quality:n})},_tempRemoveBordersControlsFromGroup:function(e){e.origHasControls=e.hasControls,e.origBorderColor=e.borderColor,e.hasControls=!0,e.borderColor="rgba(0,0,0,0)",e.forEachObject(function(e){e.origBorderColor=e.borderColor,e.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(e){e.hideControls=e.origHideControls,e.borderColor=e.origBorderColor,e.forEachObject(function(e){e.borderColor=e.origBorderColor,delete e.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(e,t,n){return this.loadFromJSON(e,t,n)},loadFromJSON:function(e,t,n){if(!e)return;var r=typeof e=="string"?JSON.parse(e):e;this.clear();var i=this;return this._enlivenObjects(r.objects,function(){i._setBgOverlay(r,t)},n),this},_setBgOverlay:function(e,t){var n=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!e.backgroundImage&&!e.overlayImage&&!e.background&&!e.overlay){t&&t();return}var i=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(n.renderAll(),t&&t())};this.__setBgOverlay("backgroundImage",e.backgroundImage,r,i),this.__setBgOverlay("overlayImage",e.overlayImage,r,i),this.__setBgOverlay("backgroundColor",e.background,r,i),this.__setBgOverlay("overlayColor",e.overlay,r,i),i()},__setBgOverlay:function(e,t,n,r){var i=this;if(!t){n[e]=!0;return}e==="backgroundImage"||e==="overlayImage"?fabric.Image.fromObject(t,function(t){i[e]=t,n[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){n[e]=!0,r&&r()})},_enlivenObjects:function(e,t,n){var r=this;if(!e||e.length===0){t&&t();return}var i=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(e,function(e){e.forEach(function(e,t){r.insertAt(e,t,!0)}),r.renderOnAddRemove=i,t&&t()},null,n)},_toDataURL:function(e,t){this.clone(function(n){t(n.toDataURL(e))})},_toDataURLWithMultiplier:function(e,t,n){this.clone(function(r){n(r.toDataURLWithMultiplier(e,t))})},clone:function(e,t){var n=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(n,function(){e&&e(t)})})},cloneWithoutData:function(e){var t=fabric.document.createElement("canvas");t.width=this.getWidth(),t.height=this.getHeight();var n=new fabric.Canvas(t);n.clipTo=this.clipTo,this.backgroundImage?(n.setBackgroundImage(this.backgroundImage.src,function(){n.renderAll(),e&&e(n)}),n.backgroundImageOpacity=this.backgroundImageOpacity,n.backgroundImageStretch=this.backgroundImageStretch):e&&e(n)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.toFixed,i=t.util.string.capitalize,s=t.util.degreesToRadians,o=t.StaticCanvas.supports("setLineDash");if(t.Object)return;t.Object=t.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule shadow clipTo visible backgroundColor".split(" "),initialize:function(e){e&&this.setOptions(e)},_initGradient:function(e){e.fill&&e.fill.colorStops&&!(e.fill instanceof t.Gradient)&&this.set("fill",new t.Gradient(e.fill))},_initPattern:function(e){e.fill&&e.fill.source&&!(e.fill instanceof t.Pattern)&&this.set("fill",new t.Pattern(e.fill)),e.stroke&&e.stroke.source&&!(e.stroke instanceof t.Pattern)&&this.set("stroke",new t.Pattern(e.stroke))},_initClipping:function(e){if(!e.clipTo||typeof e.clipTo!="string")return;var n=t.util.getFunctionBody(e.clipTo);typeof n!="undefined"&&(this.clipTo=new Function("ctx",n))},setOptions:function(e){for(var t in e)this.set(t,e[t]);this._initGradient(e),this -._initPattern(e),this._initClipping(e)},transform:function(e,t){e.globalAlpha=this.opacity;var n=t?this._getLeftTopCoords():this.getCenterPoint();e.translate(n.x,n.y),e.rotate(s(this.angle)),e.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(e){var n=t.Object.NUM_FRACTION_DIGITS,i={type:this.type,originX:this.originX,originY:this.originY,left:r(this.left,n),top:r(this.top,n),width:r(this.width,n),height:r(this.height,n),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,n),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,n),scaleX:r(this.scaleX,n),scaleY:r(this.scaleY,n),angle:r(this.getAngle(),n),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,n),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};return this.includeDefaultValues||(i=this._removeDefaultValues(i)),t.util.populateWithProperties(this,i,e),i},toDatalessObject:function(e){return this.toObject(e)},_removeDefaultValues:function(e){var n=t.util.getKlass(e.type).prototype,r=n.stateProperties;return r.forEach(function(t){e[t]===n[t]&&delete e[t]}),e},toString:function(){return"#"},get:function(e){return this[e]},_setObject:function(e){for(var t in e)this._set(t,e[t])},set:function(e,t){return typeof e=="object"?this._setObject(e):typeof t=="function"&&e!=="clipTo"?this._set(e,t(this.get(e))):this._set(e,t),this},_set:function(e,n){var i=e==="scaleX"||e==="scaleY";return i&&(n=this._constrainScale(n)),e==="scaleX"&&n<0?(this.flipX=!this.flipX,n*=-1):e==="scaleY"&&n<0?(this.flipY=!this.flipY,n*=-1):e==="width"||e==="height"?this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2):e==="shadow"&&n&&!(n instanceof t.Shadow)&&(n=new t.Shadow(n)),this[e]=n,this},toggle:function(e){var t=this.get(e);return typeof t=="boolean"&&this.set(e,!t),this},setSourcePath:function(e){return this.sourcePath=e,this},render:function(e,n){if(this.width===0||this.height===0||!this.visible)return;e.save(),this._setupFillRule(e),this._transform(e,n),this._setStrokeStyles(e),this._setFillStyles(e);var r=this.transformMatrix;r&&this.group&&(e.translate(-this.group.width/2,-this.group.height/2),e.transform(r[0],r[1],r[2],r[3],r[4],r[5])),this._setShadow(e),this.clipTo&&t.util.clipContext(this,e),this._render(e,n),this.clipTo&&e.restore(),this._removeShadow(e),this._restoreFillRule(e),this.active&&!n&&(this.drawBorders(e),this.drawControls(e)),e.restore()},_transform:function(e,t){var n=this.transformMatrix;n&&!this.group&&e.setTransform(n[0],n[1],n[2],n[3],n[4],n[5]),t||this.transform(e)},_setStrokeStyles:function(e){this.stroke&&(e.lineWidth=this.strokeWidth,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,e.miterLimit=this.strokeMiterLimit,e.strokeStyle=this.stroke.toLive?this.stroke.toLive(e):this.stroke)},_setFillStyles:function(e){this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e):this.fill)},_setShadow:function(e){if(!this.shadow)return;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(e){if(!this.shadow)return;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},_renderFill:function(e){if(!this.fill)return;this.fill.toLive&&(e.save(),e.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)),this.fillRule==="destination-over"?e.fill("evenodd"):e.fill(),this.fill.toLive&&e.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(e)},_renderStroke:function(e){if(!this.stroke)return;e.save(),this.strokeDashArray?(1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),o?(e.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(e)):this._renderDashedStroke&&this._renderDashedStroke(e),e.stroke()):this._stroke?this._stroke(e):e.stroke(),this._removeShadow(e),e.restore()},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){var n=this.toDataURL();return t.util.loadImage(n,function(n){e&&e(new t.Image(n))}),this},toDataURL:function(e){e||(e={});var n=t.util.createCanvasElement(),r=this.getBoundingRect();n.width=r.width,n.height=r.height,t.util.wrapElement(n,"div");var i=new t.Canvas(n);e.format==="jpg"&&(e.format="jpeg"),e.format==="jpeg"&&(i.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new t.Point(n.width/2,n.height/2),"center","center");var o=this.canvas;i.add(this);var u=i.toDataURL(e);return this.set(s).setCoords(),this.canvas=o,i.dispose(),i=null,u},isType:function(e){return this.type===e},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradient:function(e,n){n||(n={});var r={colorStops:[]};r.type=n.type||(n.r1||n.r2?"radial":"linear"),r.coords={x1:n.x1,y1:n.y1,x2:n.x2,y2:n.y2};if(n.r1||n.r2)r.coords.r1=n.r1,r.coords.r2=n.r2;for(var i in n.colorStops){var s=new t.Color(n.colorStops[i]);r.colorStops.push({offset:i,color:s.toRgb(),opacity:s.getAlpha()})}return this.set(e,t.Gradient.forObject(this,r))},setPatternFill:function(e){return this.set("fill",new t.Pattern(e))},setShadow:function(e){return this.set("shadow",new t.Shadow(e))},setColor:function(e){return this.set("fill",e),this},setAngle:function(e){var t=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;return t&&this._setOriginToCenter(),this.set("angle",e),t&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(e,t){t=t||this.canvas.getPointer(e);var n=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:t.x-n.x,y:t.y-n.y}},_setupFillRule:function(e){this.fillRule&&(this._prevFillRule=e.globalCompositeOperation,e.globalCompositeOperation=this.fillRule)},_restoreFillRule:function(e){this.fillRule&&this._prevFillRule&&(e.globalCompositeOperation=this._prevFillRule)}}),t.util.createAccessors(t.Object),t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2,t.Object.__uid=0}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x+(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x-(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y+(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y-(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},translateToOriginPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x-(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x+(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y-(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y+(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){var e=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(e,this.originX,this.originY)},getPointByOrigin:function(e,t){var n=this.getCenterPoint();return this.translateToOriginPoint(n,e,t)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s=this.stroke?this.strokeWidth:0,o,u;return n&&r?(n==="left"?o=i.x-(this.getWidth()+s*this.scaleX)/2:n==="right"?o=i.x+(this.getWidth()+s*this.scaleX)/2:o=i.x,r==="top"?u=i.y-(this.getHeight()+s*this.scaleY)/2:r==="bottom"?u=i.y+(this.getHeight()+s*this.scaleY)/2:u=i.y):(o=this.left,u=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(o,u))},setPositionByOrigin:function(e,t,n){var r=this.translateToCenterPoint(e,t,n),i=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",i.x),this.set("top",i.y)},adjustPosition:function(t){var n=e(this.angle),r=this.getWidth()/2,i=Math.cos(n)*r,s=Math.sin(n)*r,o=this.getWidth(),u=Math.cos(n)*o,a=Math.sin(n)*o;this.originX==="center"&&t==="left"||this.originX==="right"&&t==="center"?(this.left-=i,this.top-=s):this.originX==="left"&&t==="center"||this.originX==="center"&&t==="right"?(this.left+=i,this.top+=s):this.originX==="left"&&t==="right"?(this.left+=u,this.top+=a):this.originX==="right"&&t==="left"&&(this.left-=u,this.top-=a),this.setCoords(),this.originX=t},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var e=this.getCenterPoint();this.originX="center",this.originY="center",this.left=e.x,this.top=e.y},_resetOrigin:function(){var e=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=e.x,this.top=e.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(e,t){var n=this.oCoords,r=new fabric.Point(n.tl.x,n.tl.y),i=new fabric.Point(n.tr.x,n.tr.y),s=new fabric.Point(n.bl.x,n.bl.y),o=new fabric.Point(n.br.x,n.br.y),u=fabric.Intersection.intersectPolygonRectangle([r,i,o,s],e,t);return u.status==="Intersection"},intersectsWithObject:function(e){function t(e){return{tl:new fabric.Point(e.tl.x,e.tl.y),tr:new fabric.Point(e.tr.x,e.tr.y),bl:new fabric.Point(e.bl.x,e.bl.y),br:new fabric.Point(e.br.x,e.br.y)}}var n=t(this.oCoords),r=t(e.oCoords),i=fabric.Intersection.intersectPolygonPolygon([n.tl,n.tr,n.br,n.bl],[r.tl,r.tr,r.br,r.bl]);return i.status==="Intersection"},isContainedWithinObject:function(e){var t=e.getBoundingRect(),n=new fabric.Point(t.left,t.top),r=new fabric.Point(t.left+t.width,t.top+t.height);return this.isContainedWithinRect(n,r)},isContainedWithinRect:function(e,t){var n=this.getBoundingRect();return n.left>=e.x&&n.left+n.width<=t.x&&n.top>=e.y&&n.top+n.height<=t.y},containsPoint:function(e){var t=this._getImageLines(this.oCoords),n=this._findCrossPoints(e,t);return n!==0&&n%2===1},_getImageLines:function(e){return{topline:{o:e.tl,d:e.tr},rightline:{o:e.tr,d:e.br},bottomline:{o:e.br,d:e.bl},leftline:{o:e.bl,d:e.tl}}},_findCrossPoints:function(e,t){var n,r,i,s,o,u,a=0,f;for(var l in t){f=t[l];if(f.o.y=e.y&&f.d.y>=e.y)continue;f.o.x===f.d.x&&f.o.x>=e.x?(o=f.o.x,u=e.y):(n=0,r=(f.d.y-f.o.y)/(f.d.x-f.o.x),i=e.y-n*e.x,s=f.o.y-r*f.o.x,o=-(i-s)/(n-r),u=i+n*o),o>=e.x&&(a+=1);if(a===2)break}return a},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],t=fabric.util.array.min(e),n=fabric.util.array.max(e),r=Math.abs(t-n),i=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],s=fabric.util.array.min(i),o=fabric.util.array.max(i),u=Math.abs(s-o);return{left:t,top:s,width:r,height:u}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(e){return Math.abs(e)1?this.strokeWidth:0,n=this.padding,r=e(this.angle);this.currentWidth=(this.width+t)*this.scaleX+n*2,this.currentHeight=(this.height+t)*this.scaleY+n*2,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var i=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),s=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(s+r)*i,u=Math.sin(s+r)*i,a=Math.sin(r),f=Math.cos(r),l=this.getCenterPoint(),c={x:l.x-o,y:l.y-u},h={x:c.x+this.currentWidth*f,y:c.y+this.currentWidth*a},p={x:h.x-this.currentHeight*a,y:h.y+this.currentHeight*f},d={x:c.x-this.currentHeight*a,y:c.y+this.currentHeight*f},v={x:c.x-this.currentHeight/2*a,y:c.y+this.currentHeight/2*f},m={x:c.x+this.currentWidth/2*f,y:c.y+this.currentWidth/2*a},g={x:h.x-this.currentHeight/2*a,y:h.y+this.currentHeight/2*f},y={x:d.x+this.currentWidth/2*f,y:d.y+this.currentWidth/2*a},b={x:m.x,y:m.y};return this.oCoords={tl:c,tr:h,br:p,bl:d,ml:v,mt:m,mr:g,mb:y,mtr:b},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(e){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,e):this.canvas.sendBackwards(this,e),this},bringForward:function(e){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,e):this.canvas.bringForward(this,e),this},moveTo:function(e){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,e):this.canvas.moveTo(this,e),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var e=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",t=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",n=this.strokeWidth?this.strokeWidth:"0",r=this.strokeDashArray?this.strokeDashArray.join(" "):"",i=this.strokeLineCap?this.strokeLineCap:"butt",s=this.strokeLineJoin?this.strokeLineJoin:"miter",o=this.strokeMiterLimit?this.strokeMiterLimit:"4",u=typeof this.opacity!="undefined"?this.opacity:"1",a=this.visible?"":" visibility: hidden;",f=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",t,"; ","stroke-width: ",n,"; ","stroke-dasharray: ",r,"; ","stroke-linecap: ",i,"; ","stroke-linejoin: ",s,"; ","stroke-miterlimit: ",o,"; ","fill: ",e,"; ","opacity: ",u,";",f,a].join("")},getSvgTransform:function(){var e=fabric.util.toFixed,t=this.getAngle(),n=this.getCenterPoint(),r=fabric.Object.NUM_FRACTION_DIGITS,i="translate("+e(n.x,r)+" "+e(n.y,r)+")",s=t!==0?" rotate("+e(t,r)+")":"",o=this.scaleX===1&&this.scaleY===1?"":" scale("+e(this.scaleX,r)+" "+e(this.scaleY,r)+")",u=this.flipX?"matrix(-1 0 0 1 0 0) ":"",a=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[i,s,o,u,a].join("")},_createBaseSVGMarkup:function(){var e=[];return this.fill&&this.fill.toLive&&e.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&e.push(this.stroke.toSVG(this,!1)),this.shadow&&e.push(this.shadow.toSVG(this)),e}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(e){return this.get(e)!==this.originalState[e]},this)},saveState:function(e){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),e&&e.stateProperties&&e.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(){var e=fabric.util.degreesToRadians,t=typeof G_vmlCanvasManager!="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(e){if(!this.hasControls||!this.active)return!1;var t=e.x,n=e.y,r,i;for(var s in this.oCoords){if(!this.isControlVisible(s))continue;if(s==="mtr"&&!this.hasRotatingPoint)continue;if(!(!this.get("lockUniScaling")||s!=="mt"&&s!=="mr"&&s!=="mb"&&s!=="ml"))continue;i=this._getImageLines(this.oCoords[s].corner),r=this._findCrossPoints({x:t,y:n},i);if(r!==0&&r%2===1)return this.__corner=s,s}return!1},_setCornerCoords:function(){var t=this.oCoords,n=e(this.angle),r=e(45-this.angle),i=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,s=i*Math.cos(r),o=i*Math.sin(r),u=Math.sin(n),a=Math.cos(n);t.tl.corner={tl:{x:t.tl.x-o,y:t.tl.y-s},tr:{x:t.tl.x+s,y:t.tl.y-o},bl:{x:t.tl.x-s,y:t.tl.y+o},br:{x:t.tl.x+o,y:t.tl.y+s}},t.tr.corner={tl:{x:t.tr.x-o,y:t.tr.y-s},tr:{x:t.tr.x+s,y:t.tr.y-o},br:{x:t.tr.x+o,y:t.tr.y+s},bl:{x:t.tr.x-s,y:t.tr.y+o}},t.bl.corner={tl:{x:t.bl.x-o,y:t.bl.y-s},bl:{x:t.bl.x-s,y:t.bl.y+o},br:{x:t.bl.x+o,y:t.bl.y+s},tr:{x:t.bl.x+s,y:t.bl.y-o}},t.br.corner={tr:{x:t.br.x+s,y:t.br.y-o},bl:{x:t.br.x-s,y:t.br.y+o},br:{x:t.br.x+o,y:t.br.y+s},tl:{x:t.br.x-o,y:t.br.y-s}},t.ml.corner={tl:{x:t.ml.x-o,y:t.ml.y-s},tr:{x:t.ml.x+s,y:t.ml.y-o},bl:{x:t.ml.x-s,y:t.ml.y+o},br:{x:t.ml.x+o,y:t.ml.y+s}},t.mt.corner={tl:{x:t.mt.x-o,y:t.mt.y-s},tr:{x:t.mt.x+s,y:t.mt.y-o},bl:{x:t.mt.x-s,y:t.mt.y+o},br:{x:t.mt.x+o,y:t.mt.y+s}},t.mr.corner={tl:{x:t.mr.x-o,y:t.mr.y-s},tr:{x:t.mr.x+s,y:t.mr.y-o},bl:{x:t.mr.x-s,y:t.mr.y+o},br:{x:t.mr.x+o,y:t.mr.y+s}},t.mb.corner={tl:{x:t.mb.x-o,y:t.mb.y-s},tr:{x:t.mb.x+s,y:t.mb.y-o},bl:{x:t.mb.x-s,y:t.mb.y+o},br:{x:t.mb.x+o,y:t.mb.y+s}},t.mtr.corner={tl:{x:t.mtr.x-o+u*this.rotatingPointOffset,y:t.mtr.y-s-a*this.rotatingPointOffset},tr:{x:t.mtr.x+s+u*this.rotatingPointOffset,y:t.mtr.y-o-a*this.rotatingPointOffset},bl:{x:t.mtr.x-s+u*this.rotatingPointOffset,y:t.mtr.y+o-a*this.rotatingPointOffset},br:{x:t.mtr.x+o+u*this.rotatingPointOffset,y:t.mtr.y+s-a*this.rotatingPointOffset}}},drawBorders:function(e){if(!this.hasBorders)return this;var t=this.padding,n=t*2,r=~~(this.strokeWidth/2)*2;e.save(),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=this.borderColor;var i=1/this._constrainScale(this.scaleX),s=1/this._constrainScale(this.scaleY);e.lineWidth=1/this.borderScaleFactor,e.scale(i,s);var o=this.getWidth(),u=this.getHeight();e.strokeRect(~~(-(o/2)-t-r/2*this.scaleX)-.5,~~(-(u/2)-t-r/2*this.scaleY)-.5,~~(o+n+r*this.scaleX)+1,~~(u+n+r*this.scaleY)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var a=(this.flipY?u+r*this.scaleY+t*2:-u-r*this.scaleY-t*2)/2;e.beginPath(),e.moveTo(0,a),e.lineTo(0,a+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset)),e.closePath(),e.stroke()}return e.restore(),this},drawControls:function(e){if(!this.hasControls)return this;var t=this.cornerSize,n=t/2,r=~~(this.strokeWidth/2),i=-(this.width/2),s=-(this.height/2),o=this.padding/this.scaleX,u=this.padding/this.scaleY,a=n/this.scaleY,f=n/this.scaleX,l=(n-t)/this.scaleX,c=(n-t)/this.scaleY,h=this.height,p=this.width,d=this.transparentCorners?"strokeRect":"fillRect";return e.save(),e.lineWidth=1/Math.max(this.scaleX,this.scaleY),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=e.fillStyle=this.cornerColor,this._drawControl("tl",e,d,i-f-r-o,s-a-r-u),this._drawControl("tr",e,d,i+p-f+r+o,s-a-r-u),this._drawControl("bl",e,d,i-f-r-o,s+h+c+r+u),this._drawControl("br",e,d,i+p+l+r+o,s+h+c+r+u),this.get("lockUniScaling")||(this._drawControl("mt",e,d,i+p/2-f,s-a-r-u),this._drawControl("mb",e,d,i+p/2-f,s+h+c+r+u),this._drawControl("mr",e,d,i+p+l+r+o,s+h/2-a),this._drawControl("ml",e,d,i-f-r-o,s+h/2-a)),this.hasRotatingPoint&&this._drawControl("mtr",e,d,i+p/2-f,this.flipY?s+h+this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleX/2+r+u:s-this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleY/2-r-u),e.restore(),this},_drawControl:function(e,n,r,i,s){var o=this.cornerSize/this.scaleX,u=this.cornerSize/this.scaleY;this.isControlVisible(e)&&(t||this.transparentCorners||n.clearRect(i,s,o,u),n[r](i,s,o,u))},isControlVisible:function(e){return this._getControlsVisibility()[e]},setControlVisible:function(e,t){return this._getControlsVisibility()[e]=t,this},setControlsVisibility:function(e){e||(e={});for(var t in e)this.setControlVisible(t,e[t]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(t){e.set("left",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxCenterObjectV:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(t){e.set("top",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxRemove:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){e.set("active",!1)},onChange:function(t){e.set("opacity",t),s.renderAll(),i()},onComplete:function(){s.remove(e),r()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]=="object"){var e=[],t,n;for(t in arguments[0])e.push(t);for(var r=0,i=e.length;r'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Line.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),t.Line.fromElement=function(e,r){var i=t.parseAttributes(e,t.Line.ATTRIBUTE_NAMES),s=[i.x1||0,i.y1||0,i.x2||0,i.y2||0];return new t.Line(s,n(i,r))},t.Line.fromObject=function(e){var n=[e.x1,e.y1,e.x2,e.y2];return new t.Line(n,e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return"radius"in e&&e.radius>0}var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Circle){t.warn("fabric.Circle is already defined.");return}t.Circle=t.util.createClass(t.Object,{type:"circle",radius:0,initialize:function(e){e=e||{},this.set("radius",e.radius||0),this.callSuper("initialize",e)},_set:function(e,t){return this.callSuper("_set",e,t),e==="radius"&&this.setRadius(t),this},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,e.arc(t?this.left:0,t?this.top:0,this.radius,0,n,!1),e.closePath(),this._renderFill(e),this.stroke&&this._renderStroke(e)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(e){this.radius=e,this.set("width",e*2).set("height",e*2)},complexity:function(){return 1}}),t.Circle.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),t.Circle.fromElement=function(e,n){n||(n={});var s=t.parseAttributes(e,t.Circle.ATTRIBUTE_NAMES);if(!i(s))throw new Error("value of `r` attribute is required and can not be negative");"left"in s&&(s.left-=n.width/2||0),"top"in s&&(s.top-=n.height/2||0);var o=new t.Circle(r(s,n));return o.cx=parseFloat(e.getAttribute("cx"))||0,o.cy=parseFloat(e.getAttribute("cy"))||0,o},t.Circle.fromObject=function(e){return new t.Circle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Triangle){t.warn("fabric.Triangle is already defined");return}t.Triangle=t.util.createClass(t.Object,{type:"triangle",initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("width",e.width||100).set("height",e.height||100)},_render:function(e){var t=this.width/2,n=this.height/2;e.beginPath(),e.moveTo(-t,n),e.lineTo(0,-n),e.lineTo(t,n),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=this.width/2,r=this.height/2;e.beginPath(),t.util.drawDashedLine(e,-n,r,0,-r,this.strokeDashArray),t.util.drawDashedLine(e,0,-r,n,r,this.strokeDashArray),t.util.drawDashedLine(e,n,r,-n,r,this.strokeDashArray),e.closePath()},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.width/2,r=this.height/2,i=[-n+" "+r,"0 "+ -r,n+" "+r].join(",");return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Triangle.fromObject=function(e){return new t.Triangle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Ellipse){t.warn("fabric.Ellipse is already defined.");return}t.Ellipse=t.util.createClass(t.Object,{type:"ellipse",rx:0,ry:0,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("rx",e.rx||0),this.set("ry",e.ry||0),this.set("width",this.get("rx")*2),this.set("height",this.get("ry")*2)},toObject:function(e){return r(this.callSuper("toObject",e),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},render:function(e,t){if(this.rx===0||this.ry===0)return;return this.callSuper("render",e,t)},_render:function(e,t){e.beginPath(),e.save(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&this.group&&e.translate(this.cx,this.cy),e.transform(1,0,0,this.ry/this.rx,0,0),e.arc(t?this.left:0,t?this.top:0,this.rx,0,n,!1),e.restore(),this._renderFill(e),this._renderStroke(e)},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),t.Ellipse.fromElement=function(e,n){n||(n={});var i=t.parseAttributes(e,t.Ellipse.ATTRIBUTE_NAMES),s=i.left,o=i.top;"left"in i&&(i.left-=n.width/2||0),"top"in i&&(i.top-=n.height/2||0);var u=new t.Ellipse(r(i,n));return u.cx=s||0,u.cy=o||0,u},t.Ellipse.fromObject=function(e){return new t.Ellipse(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return e.left=e.left||0,e.top=e.top||0,e}var t=e.fabric||(e.fabric={}),n=t.util.object.extend;if(t.Rect){console.warn("fabric.Rect is already defined");return}var r=t.Object.prototype.stateProperties.concat();r.push("rx","ry","x","y"),t.Rect=t.util.createClass(t.Object,{stateProperties:r,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(e){e=e||{},this.callSuper("initialize",e),this._initRxRy(),this.x=e.x||0,this.y=e.y||0},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e){if(this.width===1&&this.height===1){e.fillRect(0,0,1,1);return}var t=this.rx?Math.min(this.rx,this.width/2):0,n=this.ry?Math.min(this.ry,this.height/2):0,r=this.width,i=this.height,s=-r/2,o=-i/2,u=this.group&&this.group.type==="path-group",a=t!==0||n!==0,f=.4477152502;e.beginPath(),e.globalAlpha=u?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&u&&e.translate(this.width/2+this.x,this.height/2+this.y),!this.transformMatrix&&u&&e.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y),e.moveTo(s+t,o),e.lineTo(s+r-t,o),a&&e.bezierCurveTo(s+r-f*t,o,s+r,o+f*n,s+r,o+n),e.lineTo(s+r,o+i-n),a&&e.bezierCurveTo(s+r,o+i-f*n,s+r-f*t,o+i,s+r-t,o+i),e.lineTo(s+t,o+i),a&&e.bezierCurveTo(s+f*t,o+i,s,o+i-f*n,s,o+i-n),e.lineTo(s,o+n),a&&e.bezierCurveTo(s,o+f*n,s+f*t,o,s+t,o),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=-this.width/2,r=-this.height/2,i=this.width,s=this.height;e.beginPath(),t.util.drawDashedLine(e,n,r,n+i,r,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r,n+i,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r+s,n,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n,r+s,n,r,this.strokeDashArray),e.closePath()},_normalizeLeftTopProperties:function(e){return"left"in e&&this.set("left",e.left+this.getWidth()/2),this.set("x",e.left||0),"top"in e&&this.set("top",e.top+this.getHeight()/2),this.set("y",e.top||0),this},toObject:function(e){var t=n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join -("")):t.join("")},complexity:function(){return 1}}),t.Rect.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),t.Rect.fromElement=function(e,r){if(!e)return null;var s=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);s=i(s);var o=new t.Rect(n(r?t.util.object.clone(r):{},s));return o._normalizeLeftTopProperties(s),o},t.Rect.fromObject=function(e){return new t.Rect(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.toFixed;if(t.Polyline){t.warn("fabric.Polyline is already defined");return}t.Polyline=t.util.createClass(t.Object,{type:"polyline",points:null,initialize:function(e,t,n){t=t||{},this.set("points",e),this.callSuper("initialize",t),this._calcDimensions(n)},_calcDimensions:function(e){return t.Polygon.prototype._calcDimensions.call(this,e)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(e){var t=[],r=this._createBaseSVGMarkup();for(var i=0,s=this.points.length;i'),e?e(r.join("")):r.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n'),e?e(n.join("")):n.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n"},toObject:function(e){var t=i(this.callSuper("toObject",e),{path:this.path.map(function(e){return e.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(t.sourcePath=this.sourcePath),this.transformMatrix&&(t.transformMatrix=this.transformMatrix),t},toDatalessObject:function(e){var t=this.toObject(e);return this.sourcePath&&(t.path=this.sourcePath),delete t.sourcePath,t},toSVG:function(e){var t=[],n=this._createBaseSVGMarkup();for(var r=0,i=this.path.length;r',"",""),e?e(n.join("")):n.join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],t=[],n,r,i=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,s,o;for(var a=0,f,l=this.path.length;ad)for(var v=1,m=f.length;v"];for(var r=0,i=t.length;r"),e?e(n.join("")):n.join("")},toString:function(){return"#"},isSameColor:function(){var e=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(t){return(t.get("fill")||"").toLowerCase()===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e,n){typeof e.paths=="string"?t.loadSVGFromURL(e.paths,function(r){var i=e.paths;delete e.paths;var s=t.util.groupSVGElements(r,e,i);n(s)}):t.util.enlivenObjects(e.paths,function(r){delete e.paths,n(new t.PathGroup(r,e))})},t.PathGroup.async=!0}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.array.min,i=t.util.array.max,s=t.util.array.invoke;if(t.Group)return;var o={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,t.Collection,{type:"group",initialize:function(e,t){t=t||{},this._objects=e||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this._setOpacityIfSame(),this.setCoords(!0),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(e){var t=e.getLeft(),n=e.getTop();e.set({originalLeft:t,originalTop:n,left:t-this.left,top:n-this.top}),e.setCoords(),e.__origHasControls=e.hasControls,e.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(e){return this._restoreObjectsState(),this._objects.push(e),e.group=this,this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(e){e.set("active",!0),e.group=this},removeWithUpdate:function(e){return this._moveFlippedObject(e),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(e),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(e){e.group=this},_onObjectRemoved:function(e){delete e.group,e.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(e,t){if(e in this.delegatedProperties){var n=this._objects.length;this[e]=t;while(n--)this._objects[n].set(e,t)}else this[e]=t},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this._objects,"toObject",e)})},render:function(e,n){if(!this.visible)return;e.save(),this.transform(e),this.clipTo&&t.util.clipContext(this,e);for(var r=0,i=this._objects.length;r'];for(var n=0,r=this._objects.length;n"),e?e(t.join("")):t.join("")},get:function(e){if(e in o){if(this[e])return this[e];for(var t=0,n=this._objects.length;t','");if(this.stroke||this.strokeDashArray){var n=this.fill;this.fill=null,t.push("'),this.fill=n}return t.push(""),e?e(t.join("")):t.join("")},getSrc:function(){if(this.getElement())return this.getElement().src||this.getElement()._src},toString:function(){return'#'},clone:function(e,t){this.constructor.fromObject(this.toObject(t),e)},applyFilters:function(e){if(!this._originalElement)return;if(this.filters.length===0){this._element=this._originalElement,e&&e();return}var t=this._originalElement,n=fabric.util.createCanvasElement(),r=fabric.util.createImage(),i=this;return n.width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0,t.width,t.height),this.filters.forEach(function(e){e&&e.applyTo(n)}),r.width=t.width,r.height=t.height,fabric.isLikelyNode?(r.src=n.toBuffer(undefined,fabric.Image.pngCompression),i._element=r,e&&e()):(r.onload=function(){i._element=r,e&&e(),r.onload=n=t=null},r.src=n.toDataURL("image/png")),this},_render:function(e){this._element&&e.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var e=this.getElement();this.set("width",e.width),this.set("height",e.height)},_initElement:function(e){this.setElement(fabric.util.getById(e)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(e){e||(e={}),this.setOptions(e),this._setWidthHeight(e),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(e,t){e.filters&&e.filters.length?fabric.util.enlivenObjects(e.filters,function(e){t&&t(e)},"fabric.Image.filters"):t&&t()},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement()?this.getElement().width||0:0,this.height="height"in e?e.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){fabric.Image.prototype._initFilters.call(e,e,function(r){e.filters=r||[];var i=new fabric.Image(n,e);t&&t(i)})},null,e.crossOrigin)},fabric.Image.fromURL=function(e,t,n){fabric.util.loadImage(e,function(e){t(new fabric.Image(e,n))},null,n&&n.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(e,n,r){var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(r?fabric.util.object.clone(r):{},i))},fabric.Image.async=!0,fabric.Image.pngCompression=1}(typeof exports!="undefined"?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var e=this.getAngle()%360;return e>0?Math.round((e-1)/90)*90:Math.round(e/90)*90},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(e){e=e||{};var t=function(){},n=e.onComplete||t,r=e.onChange||t,i=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(e){i.setAngle(e),r()},onComplete:function(){i.setCoords(),n()},onStart:function(){i.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(e){return e.straighten(),this.renderAll(),this},fxStraightenObject:function(e){return e.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.Brightness=t.util.createClass(t.Image.filters.BaseFilter,{type:"Brightness",initialize:function(e){e=e||{},this.brightness=e.brightness||0},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.brightness;for(var s=0,o=r.length;sa||C<0||C>u)continue;var k=(N*u+C)*4,L=t[x*i+T];b+=o[k]*L,w+=o[k+1]*L,E+=o[k+2]*L,S+=o[k+3]*L}h[y]=b,h[y+1]=w,h[y+2]=E,h[y+3]=S+p*(255-S)}n.putImageData(c,0,0)},toObject:function(){return n(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),t.Image.filters.Convolute.fromObject=function(e){return new t.Image.filters.Convolute(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.GradientTransparency=t.util.createClass(t.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(e){e=e||{},this.threshold=e.threshold||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.threshold,s=r.length;for(var o=0,u=r.length;o-1?e.channel:0},applyTo:function(e){if(!this.mask)return;var n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=r.data,s=this.mask.getElement(),o=t.util.createCanvasElement(),u=this.channel,a,f=r.width*r.height*4;o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0,s.width,s.height);var l=o.getContext("2d").getImageData(0,0,s.width,s.height),c=l.data;for(a=0;ao&&f>o&&l>o&&u(a-f)'},_render:function(e){var t=this.group&&this.group.type==="path-group";t&&!this.transformMatrix?e.translate(-this.group.width/2+this.left,-this.group.height/2+this.top):t&&this.transformMatrix&&e.translate(-this.group.width/2,-this.group.height/2),typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaNative:function(e){var n=this.text.split(this._reNewline);this.transform(e,t.isLikelyNode),this._setTextStyles(e),this.width=this._getTextWidth(e,n),this.height=this._getTextHeight(e,n),this.clipTo&&t.util.clipContext(this,e),this._renderTextBackground(e,n),this._translateForTextAlign(e),this._renderText(e,n),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,n),this.clipTo&&e.restore(),this._setBoundaries(e,n),this._totalLineHeight=0},_renderText:function(e,t){e.save(),this._setShadow(e),this._renderTextFill(e,t),this._renderTextStroke(e,t),this._removeShadow(e),e.restore()},_translateForTextAlign:function(e){this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0))},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_renderChars:function(e,t,n,r,i){t[e](n,r,i)},_renderTextLine:function(e,t,n,r,i,s){i-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(e,t,n,r,i,s);return}var o=t.measureText(n).width,u=this.width;if(u>o){var a=n.split(/\s+/),f=t.measureText(n.replace(/\s+/g,"")).width,l=u-f,c=a.length-1,h=l/c,p=0;for(var d=0,v=a.length;d-1&&i(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(this.fontSize*this.lineHeight-this.fontSize)},_getFontDeclaration:function(){return[t.isLikelyNode?this.fontWeight:this.fontStyle,t.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(e,t){if(!this.visible)return;e.save();var n=this.transformMatrix;n&&(!this.group||this.group.type==="path-group")&&e.transform(n[0],n[1],n[2],n[3],n[4],n[5]),this._render(e),!t&&this.active&&(this.drawBorders(e),this.drawControls(e)),e.restore()},toObject:function(e){var t=n(this.callSuper("toObject",e),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=[],n=this.text.split(this._reNewline),r=this._getSVGLeftTopOffsets(n),i=this._getSVGTextAndBg(r.lineTop,r.textLeft,n),s=this._getSVGShadows(r.lineTop,n);return r.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(t,i,s,r),e?e(t.join("")):t.join("")},_getSVGLeftTopOffsets:function(e){var t=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,n=-(this.width/2),r=this.useNative?this.fontSize-1:this.height/2-e.length*this.fontSize-this._totalLineHeight;return{textLeft:n,textTop:r,lineTop:t}},_wrapSVGTextAndBg:function(e,t,n,r){e.push('',t.textBgRects.join(""),"',n.join(""),t.textSpans.join(""),"","")},_getSVGShadows:function(e,n){var r=[],s,o,u=1;if(!this.shadow||!this._boundaries)return r;for(s=0,o=n.length;s",t.util.string.escapeXml(n[s]),""),u=1}else u++;return r},_getSVGTextAndBg:function(e,t,n){var r=[],i=[],s=1;this._setSVGBg(i);for(var o=0,u=n.length;o",t.util.string.escapeXml(e),"")},_setSVGTextLineBg:function(e,t,n,r){e.push("')},_setSVGBg:function(e){this.backgroundColor&&this._boundaries&&e.push("')},_getFillAttributes:function(e){var n=e&&typeof e=="string"?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t),e in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),t.Text.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),t.Text.DEFAULT_SVG_FONT_SIZE=16,t.Text.fromElement=function(e,n){if(!e)return null;var r=t.parseAttributes(e,t.Text.ATTRIBUTE_NAMES);n=t.util.object.extend(n?t.util.object.clone(n):{},r),"dx"in r&&(n.left+=r.dx),"dy"in r&&(n.top+=r.dy),"fontSize"in n||(n.fontSize=t.Text.DEFAULT_SVG_FONT_SIZE),n.originX||(n.originX="center");var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i},t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},t.util.createAccessors(t.Text)}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!0,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(e,t){this.styles=t?t.styles||{}:{},this.callSuper("initialize",e,t),this.initBehavior(),fabric.IText.instances.push(this),this.__lineWidths={},this.__lineHeights={},this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return!0;var e=this.styles;for(var t in e)for(var n in e[t])for(var r in e[t][n])return!1;return!0},setSelectionStart:function(e){this.selectionStart!==e&&this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionStart=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=e)},setSelectionEnd:function(e){this.selectionEnd!==e&&this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionEnd=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=e)},getSelectionStyles:function(e,t){if(arguments.length===2){var n=[];for(var r=e;r=r.charIndex&&(a!==o||hs&&a-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,0,this.fontSize/20),u.indexOf("line-through")>-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,o/2,a/20),u.indexOf("overline")>-1&&this._renderCharDecorationAtOffset(e,n,r,i,s-this.fontSize/this._fontSizeFraction,this.fontSize/20)},_renderCharDecorationAtOffset:function(e,t,n,r,i,s){e.fillRect(t,n-i,r,s)},_renderTextLine:function(e,t,n,r,i,s){i+=this.fontSize/4,this.callSuper("_renderTextLine",e,t,n,r,i,s)},_renderTextDecoration:function(e,t){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",e,t)},_renderTextLinesBackground:function(e,t){if(!this.textBackgroundColor&&!this.styles)return;e.save(),this.textBackgroundColor&&(e.fillStyle=this.textBackgroundColor);var n=0,r=this.fontSize/this._fontSizeFraction;for(var i=0,s=t.length;in&&(n=s)}return n},_getHeightOfLine:function(e,t,n){n=n||this.text.split(this._reNewline);var r=this._getHeightOfChar(e,n[t][0],t,0),i=n[t],s=i.split("");for(var o=1,u=s.length;or&&(r=a)}return r*this.lineHeight},_getTextHeight:function(e,t){var n=0;for(var r=0,i=t.length;r-1)t++,n--;return e-t},findWordBoundaryRight:function(e){var t=0,n=e;if(this._reSpace.test(this.text.charAt(n)))while(this._reSpace.test(this.text.charAt(n)))t++,n++;while(/\S/.test(this.text.charAt(n))&&n-1)t++,n--;return e-t},findLineBoundaryRight:function(e){var t=0,n=e;while(!/\n/.test(this.text.charAt(n))&&n0&&nr;s?this.removeStyleObject(s,n+1):this.removeStyleObject(this.get2DCursorLocation(n).charIndex===0,n)}this.text=this.text.slice(0,e)+this.text.slice(t)},insertChars:function(e){var t=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+e+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd&&this.insertStyleObjects(e,t,this.copiedStyles),this.selectionStart+=e.length,this.selectionEnd=this.selectionStart,this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(t,n,r){this.shiftLineStyles(t,1),this.styles[t+1]||(this.styles[t+1]={});var i=this.styles[t][n-1],s={};if(r)s[0]=e(i),this.styles[t+1]=s;else{for(var o in this.styles[t])parseInt(o,10)>=n&&(s[parseInt(o,10)-n]=this.styles[t][o],delete this.styles[t][o]);this.styles[t+1]=s}},insertCharStyleObject:function(t,n,r){var i=this.styles[t],s=e(i);n===0&&!r&&(n=1);for(var o in s){var u=parseInt(o,10);u>=n&&(i[u+1]=s[u])}this.styles[t][n]=r||e(i[n-1])},insertStyleObjects:function(e,t,n){if(this.isEmptyStyles())return;var r=this.get2DCursorLocation(),i=r.lineIndex,s=r.charIndex;this.styles[i]||(this.styles[i]={}),e==="\n"?this.insertNewlineStyleObject(i,s,t):n?this._insertStyles(n):this.insertCharStyleObject(i,s)},_insertStyles:function(e){for(var t=0,n=e.length;tt&&(this.styles[s+n]=r[s])}},removeStyleObject:function(t,n){var r=this.get2DCursorLocation(n),i=r.lineIndex,s=r.charIndex;if(t){var o=this.text.split(this._reNewline),u=o[i-1],a=u?u.length:0;this.styles[i-1]||(this.styles[i-1]={});for(s in this.styles[i])this.styles[i-1][parseInt(s,10)+a]=this.styles[i][s];this.shiftLineStyles(i,-1)}else{var f=this.styles[i];if(f){var l=this.selectionStart===this.selectionEnd?-1:0;delete f[s+l]}var c=e(f);for(var h in c){var p=parseInt(h,10);p>=s&&p!==0&&(f[p-1]=c[p],delete f[p])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+(new Date),this.__lastLastClickTime=+(new Date),this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(e){this.__newClickTime=+(new Date);var t=this.canvas.getPointer(e.e);this.isTripleClick(t)?(this.fire("tripleclick",e),this._stopEvent(e.e)):this.isDoubleClick(t)&&(this.fire("dblclick",e),this._stopEvent(e.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=t,this.__lastIsEditing=this.isEditing},isDoubleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y&&this.__lastIsEditing},isTripleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMousemoveHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(e){this.selectWord(this.getSelectionStartFromPointer(e.e))}),this.on("tripleclick",function(e){this.selectLine -(this.getSelectionStartFromPointer(e.e))})},initMousedownHandler:function(){this.on("mousedown",function(e){var t=this.canvas.getPointer(e.e);this.__mousedownX=t.x,this.__mousedownY=t.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.selected&&this.setCursorByClick(e.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.initDelayedCursor(!0))})},initMousemoveHandler:function(){this.on("mousemove",function(e){if(!this.__isMousedown||!this.isEditing)return;var t=this.getSelectionStartFromPointer(e.e);t>=this.__selectionStartOnMouseDown?(this.setSelectionStart(this.__selectionStartOnMouseDown),this.setSelectionEnd(t)):(this.setSelectionStart(t),this.setSelectionEnd(this.__selectionStartOnMouseDown))})},_isObjectMoved:function(e){var t=this.canvas.getPointer(e);return this.__mousedownX!==t.x||this.__mousedownY!==t.y},initMouseupHandler:function(){this.on("mouseup",function(e){this.__isMousedown=!1;if(this._isObjectMoved(e.e))return;this.selected&&(this.enterEditing(),this.initDelayedCursor(!0)),this.selected=!0})},setCursorByClick:function(e){var t=this.getSelectionStartFromPointer(e);e.shiftKey?to?0:1,f=r+a;return this.flipX&&(f=i-f),f>this.text.length&&(f=this.text.length),s===i&&f--,f}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",13:"insertNewline",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",67:"copy",86:"paste",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap)this[this._keysMap[e.keyCode]](e);else{if(!(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)))return;this[this._ctrlKeysMap[e.keyCode]](e)}e.preventDefault(),e.stopPropagation(),this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){this.selectionStart===this.selectionEnd&&this.moveCursorRight(e),this.removeChars(e)},copy:function(){var e=this.getSelectedText();this.copiedText=e,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(){this.copiedText&&this.insertChars(this.copiedText)},cut:function(e){this.copy(),this.removeChars(e)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey||e.keyCode===8||e.keyCode===13)return;this.insertChars(String.fromCharCode(e.which)),e.preventDefault(),e.stopPropagation()},getDownCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.text.split(this._reNewline),i,s,o=this.text.slice(0,n),u=this.text.slice(n),a=o.slice(o.lastIndexOf("\n")+1),f=u.match(/(.*)\n?/)[1],l=(u.match(/.*\n(.*)\n?/)||{})[1]||"",c=this.get2DCursorLocation(n);if(c.lineIndex===r.length-1||e.metaKey)return this.text.length-n;var h=this._getWidthOfLine(this.ctx,c.lineIndex,r);s=this._getLineLeftOffset(h);var p=s,d=c.lineIndex;for(var v=0,m=a.length;vn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=gthis.text.length&&(this.selectionStart=this.text.length),this.selectionEnd=this.selectionStart},moveCursorDownWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this.selectionStart+=e,this._selectionDirection="left";return}this._selectionDirection="right",this.selectionEnd+=e,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length)},getUpCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.get2DCursorLocation(n);if(r.lineIndex===0||e.metaKey)return n;var i=this.text.slice(0,n),s=i.slice(i.lastIndexOf("\n")+1),o=(i.match(/\n?(.*)\n.*$/)||{})[1]||"",u=this.text.split(this._reNewline),a,f=this._getWidthOfLine(this.ctx,r.lineIndex,u),l=this._getLineLeftOffset(f),c=l,h=r.lineIndex;for(var p=0,d=s.length;pn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=g=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.selectionStart=t}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.selectionStart=n}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[t]?this._setSVGTextLineChars(e,t,n,r,i,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i,s){var o=t===0||this.useNative?"y":"dy",u=e.split(""),a=0,f=this._getSVGLineLeftOffset(t),l=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t);for(var h=0,p=u.length;h'].join("")},_createTextCharSpan:function(e,t,n,r,i,s){var o=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e),""].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.port===443?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function requestFs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){function r(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)}var i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?requestFs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?requestFs(e,function(e){fabric.loadSVGFromString(e.toString(),t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t,n,r){r=r||n;var i=fabric.document.createElement("canvas"),s=new Canvas(e||600,t||600,r);i.style={},i.width=s.width,i.height=s.height;var o=fabric.Canvas||fabric.StaticCanvas,u=new o(i,n);return u.contextContainer=s.getContext("2d"),u.nodeCanvas=s,u.Font=Canvas.Font,u},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this,e),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e){return origSetHeight.call(this,e),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); \ No newline at end of file +var fabric=fabric||{version:"1.4.6"};if(typeof exports!=="undefined"){exports.fabric=fabric}if(typeof document!=="undefined"&&typeof window!=="undefined"){fabric.document=document;fabric.window=window}else{fabric.document=require("jsdom").jsdom("");fabric.window=fabric.document.createWindow()}fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement;fabric.isLikelyNode=typeof Buffer!=="undefined"&&typeof window==="undefined";fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"];var Cufon=function(){var api=function(){return api.replace.apply(null,arguments)};var DOM=api.DOM={ready:function(){var complete=false,readyStatus={loaded:1,complete:1};var queue=[],perform=function(){if(complete)return;complete=true;for(var fn;fn=queue.shift();fn());};if(fabric.document.addEventListener){fabric.document.addEventListener("DOMContentLoaded",perform,false);fabric.window.addEventListener("pageshow",perform,false)}if(!fabric.window.opera&&fabric.document.readyState)(function(){readyStatus[fabric.document.readyState]?perform():setTimeout(arguments.callee,10)})();if(fabric.document.readyState&&fabric.document.createStyleSheet)(function(){try{fabric.document.body.doScroll("left");perform()}catch(e){setTimeout(arguments.callee,1)}})();addEvent(fabric.window,"load",perform);return function(listener){if(!arguments.length)perform();else complete?listener():queue.push(listener)}}()};var CSS=api.CSS={Size:function(value,base){this.value=parseFloat(value);this.unit=String(value).match(/[a-z%]*$/)[0]||"px";this.convert=function(value){return value/base*this.value};this.convertFrom=function(value){return value/this.value*base};this.toString=function(){return this.value+this.unit}},getStyle:function(el){return new Style(el.style)},quotedList:cached(function(value){var list=[],re=/\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g,match;while(match=re.exec(value))list.push(match[3]||match[1]);return list}),ready:function(){var complete=false;var queue=[],perform=function(){complete=true;for(var fn;fn=queue.shift();fn());};var styleElements=Object.prototype.propertyIsEnumerable?elementsByTagName("style"):{length:0};var linkElements=elementsByTagName("link");DOM.ready(function(){var linkStyles=0,link;for(var i=0,l=linkElements.length;link=linkElements[i],i=styleElements.length+linkStyles)perform();else setTimeout(arguments.callee,10)});return function(listener){if(complete)listener();else queue.push(listener)}}(),supports:function(property,value){var checker=fabric.document.createElement("span").style;if(checker[property]===undefined)return false;checker[property]=value;return checker[property]===value},textAlign:function(word,style,position,wordCount){if(style.get("textAlign")=="right"){if(position>0)word=" "+word}else if(position400;if(weight==500)weight=400;for(var alt in weights){alt=parseInt(alt,10);if(!min||altmax)max=alt;alts.push(alt)}if(weightmax)weight=max;alts.sort(function(a,b){return(up?a>weight&&b>weight?ab:ab:amaxWidth){maxWidth=width}lineWidths.push(width);width=0;continue}var glyph=font.glyphs[chars[i]]||font.missingGlyph;if(!glyph)continue;width+=lastWidth=Number(glyph.w||font.w)+letterSpacing}lineWidths.push(width);width=Math.max(maxWidth,width);var lineOffsets=[];for(var i=lineWidths.length;i--;){lineOffsets[i]=width-lineWidths[i]}if(lastWidth===null)return null;expandRight+=viewBox.width-lastWidth;expandLeft+=viewBox.minX;var wrapper,canvas;if(redraw){wrapper=node;canvas=node.firstChild}else{wrapper=fabric.document.createElement("span");wrapper.className="cufon cufon-canvas";wrapper.alt=text;canvas=fabric.document.createElement("canvas");wrapper.appendChild(canvas);if(options.printable){var print=fabric.document.createElement("span");print.className="cufon-alt";print.appendChild(fabric.document.createTextNode(text));wrapper.appendChild(print)}}var wStyle=wrapper.style;var cStyle=canvas.style||{};var height=size.convert(viewBox.height-expandTop+expandBottom);var roundedHeight=Math.ceil(height);var roundingFactor=roundedHeight/height;canvas.width=Math.ceil(size.convert(width+expandRight-expandLeft)*roundingFactor);canvas.height=roundedHeight;expandTop+=viewBox.minY;cStyle.top=Math.round(size.convert(expandTop-font.ascent))+"px";cStyle.left=Math.round(size.convert(expandLeft))+"px";var _width=Math.ceil(size.convert(width*roundingFactor));var wrapperWidth=_width+"px";var _height=size.convert(font.height);var totalLineHeight=(options.lineHeight-1)*size.convert(-font.ascent/5)*(lines-1);Cufon.textOptions.width=_width;Cufon.textOptions.height=_height*lines+totalLineHeight;Cufon.textOptions.lines=lines;Cufon.textOptions.totalLineHeight=totalLineHeight;if(HAS_INLINE_BLOCK){wStyle.width=wrapperWidth;wStyle.height=_height+"px"}else{wStyle.paddingLeft=wrapperWidth;wStyle.paddingBottom=_height-1+"px"}var g=Cufon.textOptions.context||canvas.getContext("2d"),scale=roundedHeight/viewBox.height;Cufon.textOptions.fontAscent=font.ascent*scale;Cufon.textOptions.boundaries=null;for(var offsets=Cufon.textOptions.shadowOffsets,i=shadowOffsets.length;i--;){offsets[i]=[shadowOffsets[i][0]*scale,shadowOffsets[i][1]*scale]}g.save();g.scale(scale,scale);g.translate(-expandLeft-1/scale*canvas.width/2+(Cufon.fonts[font.family].offsetLeft||0),-expandTop-Cufon.textOptions.height/scale/2+(Cufon.fonts[font.family].offsetTop||0));g.lineWidth=font.face["underline-thickness"];g.save();function line(y,color){g.strokeStyle=color;g.beginPath();g.moveTo(0,y);g.lineTo(width,y);g.stroke()}var textDecoration=Cufon.getTextDecoration(options),isItalic=options.fontStyle==="italic";function renderBackground(){g.save();var left=0,lineNum=0,boundaries=[{left:0}];if(options.backgroundColor){g.save();g.fillStyle=options.backgroundColor;g.translate(0,font.ascent);g.fillRect(0,0,width+10,(-font.ascent+font.descent)*lines);g.restore()}if(options.textAlign==="right"){g.translate(lineOffsets[lineNum],0);boundaries[0].left=lineOffsets[lineNum]*scale}else if(options.textAlign==="center"){g.translate(lineOffsets[lineNum]/2,0);boundaries[0].left=lineOffsets[lineNum]/2*scale}for(var i=0,l=chars.length;i'+".cufon-vml-canvas{text-indent:0}"+"@media screen{"+"cvml\\:shape,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute}"+".cufon-vml-canvas{position:absolute;text-align:left}"+".cufon-vml{display:inline-block;position:relative;vertical-align:middle}"+".cufon-vml .cufon-alt{position:absolute;left:-10000in;font-size:1px}"+"a .cufon-vml{cursor:pointer}"+"}"+"@media print{"+".cufon-vml *{display:none}"+".cufon-vml .cufon-alt{display:inline}"+"}"+"");function getFontSizeInPixels(el,value){return getSizeInPixels(el,/(?:em|ex|%)$/i.test(value)?"1em":value)}function getSizeInPixels(el,value){if(/px$/i.test(value))return parseFloat(value);var style=el.style.left,runtimeStyle=el.runtimeStyle.left;el.runtimeStyle.left=el.currentStyle.left;el.style.left=value;var result=el.style.pixelLeft;el.style.left=style;el.runtimeStyle.left=runtimeStyle;return result}return function(font,text,style,options,node,el,hasNext){var redraw=text===null;if(redraw)text=node.alt;var viewBox=font.viewBox;var size=style.computedFontSize||(style.computedFontSize=new Cufon.CSS.Size(getFontSizeInPixels(el,style.get("fontSize"))+"px",font.baseSize));var letterSpacing=style.computedLSpacing;if(letterSpacing==undefined){letterSpacing=style.get("letterSpacing");style.computedLSpacing=letterSpacing=letterSpacing=="normal"?0:~~size.convertFrom(getSizeInPixels(el,letterSpacing))}var wrapper,canvas;if(redraw){wrapper=node;canvas=node.firstChild}else{wrapper=fabric.document.createElement("span");wrapper.className="cufon cufon-vml";wrapper.alt=text;canvas=fabric.document.createElement("span");canvas.className="cufon-vml-canvas";wrapper.appendChild(canvas);if(options.printable){var print=fabric.document.createElement("span");print.className="cufon-alt";print.appendChild(fabric.document.createTextNode(text));wrapper.appendChild(print)}if(!hasNext)wrapper.appendChild(fabric.document.createElement("cvml:shape"))}var wStyle=wrapper.style;var cStyle=canvas.style;var height=size.convert(viewBox.height),roundedHeight=Math.ceil(height);var roundingFactor=roundedHeight/height;var minX=viewBox.minX,minY=viewBox.minY;cStyle.height=roundedHeight;cStyle.top=Math.round(size.convert(minY-font.ascent));cStyle.left=Math.round(size.convert(minX));wStyle.height=size.convert(font.height)+"px";var textDecoration=Cufon.getTextDecoration(options);var color=style.get("color");var chars=Cufon.CSS.textTransform(text,style).split("");var width=0,offsetX=0,advance=null;var glyph,shape,shadows=options.textShadow;for(var i=0,k=0,l=chars.length;itimeout){window.clearInterval(interval)}if(document.querySelector(target)){window.clearInterval(interval);setTimeout(listener,1)}},ms);return}if(typeof target==="string"){target=document.querySelectorAll(target);if(target.length===0)return createError("Missing target on listener!",arguments);if(target.length===1){target=target[0]}}var event;var events={};if(target.length>0&&target!==window){for(var n0=0,length0=target.length;n0=conf.maxFingers){var ids=[];for(var sid in conf.tracker)ids.push(sid);self.identifier=ids.join(",");return isTouchStart}var fingers=0;for(var rid in track){if(track[rid].up){delete track[rid];addTouchStart(touch,sid);conf.cancel=true;break}fingers++}if(track[sid])continue;addTouchStart(touch,sid)}else{track=conf.tracker={};self.bbox=conf.bbox=root.getBoundingBox(conf.target);conf.fingers=0;conf.cancel=false;addTouchStart(touch,sid)}}var ids=[];for(var sid in conf.tracker)ids.push(sid);self.identifier=ids.join(",");return isTouchStart};root.pointerEnd=function(event,self,conf,onPointerUp){var touches=event.touches||[];var length=touches.length;var exists={};for(var i=0;i1)return;var pointers=EVENT.changedTouches||root.getCoords(EVENT);var pointer=pointers[0];var bbox=conf.bbox;var newbbox=root.getBoundingBox(conf.target);if(conf.position==="relative"){var ax=pointer.pageX+bbox.scrollLeft-bbox.x1;var ay=pointer.pageY+bbox.scrollTop-bbox.y1}else{var ax=pointer.pageX-bbox.x1;var ay=pointer.pageY-bbox.y1}if(ax>0&&ax0&&ay0&&ax0&&ay1)){self.state=conf.gesture;for(var key in conf.tracker)break;var point=conf.tracker[key];self.x=point.start.x;self.y=point.start.y;conf.listener(event,self)}clearTimeout(timeout);time0=time1=0}};var self=root.pointerSetup(conf);self.state="dblclick";Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.dbltap=root.dbltap;Event.Gesture._gestureHandlers.dblclick=root.dblclick;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.dragElement=function(that,event){root.drag({event:event,target:that,position:"move",listener:function(event,self){that.style.left=self.x+"px";that.style.top=self.y+"px";Event.prevent(event)}})};root.drag=function(conf){conf.gesture="drag";conf.onPointerDown=function(event){if(root.pointerStart(event,self,conf)){if(!conf.monitor){Event.add(conf.doc,"mousemove",conf.onPointerMove);Event.add(conf.doc,"mouseup",conf.onPointerUp)}}conf.onPointerMove(event,"down")};conf.onPointerMove=function(event,state){if(!conf.tracker)return conf.onPointerDown(event);var bbox=conf.bbox;var touches=event.changedTouches||root.getCoords(event);var length=touches.length;for(var i=0;i0?rotate:-rotate;if(typeof touch.DEG2!=="undefined"){if(rotate>0){touch.rotation+=touch.DEG1-touch.DEG2}else{touch.rotation-=touch.DEG1-touch.DEG2}rotation+=touch.rotation}touches.push(touch.move)}self.touches=touches;self.fingers=conf.fingers;self.scale=scale/conf.fingers;self.rotation=rotation/conf.fingers;self.state="change";conf.listener(event,self)};conf.onPointerUp=function(event){var fingers=conf.fingers;if(root.pointerEnd(event,self,conf)){Event.remove(conf.doc,"mousemove",conf.onPointerMove);Event.remove(conf.doc,"mouseup",conf.onPointerUp)}if(fingers===conf.minFingers&&conf.fingersthreshold){var idx=now*ACCELERATION/abs;var span=Math.abs(idx+DELTA.value);if(DELTA.value&&span=fingers){if(velocity1>conf.threshold){start.x/=length;start.y/=length;self.start=start;self.x=endx/length;self.y=endy/length;self.angle=-(((degree1/conf.snap+.5>>0)*conf.snap||360)-360);self.velocity=velocity1;self.fingers=fingers;self.state="swipe";conf.listener(event,self)}}}};var self=root.pointerSetup(conf);Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.swipe=root.swipe;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.longpress=function(conf){conf.gesture="longpress";return root.tap(conf)};root.tap=function(conf){conf.delay=conf.delay||500;conf.timeout=conf.timeout||250;conf.driftDeviance=conf.driftDeviance||10;conf.gesture=conf.gesture||"tap";var timestamp,timeout;conf.onPointerDown=function(event){if(root.pointerStart(event,self,conf)){timestamp=(new Date).getTime();Event.add(conf.doc,"mousemove",conf.onPointerMove).listener(event);Event.add(conf.doc,"mouseup",conf.onPointerUp);if(conf.gesture!=="longpress")return;timeout=setTimeout(function(){if(event.cancelBubble&&++event.bubble>1)return;var fingers=0;for(var key in conf.tracker){var point=conf.tracker[key];if(point.end===true)return;if(conf.cancel)return;fingers++}if(conf.minFingers<=fingers&&conf.maxFingers>=fingers){self.state="start";self.fingers=fingers;self.x=point.start.x;self.y=point.start.y;conf.listener(event,self)}},conf.delay)}};conf.onPointerMove=function(event){var bbox=conf.bbox;var touches=event.changedTouches||root.getCoords(event);var length=touches.length;for(var i=0;i0&&x0&&y1)return;if(conf.gesture==="longpress"){if(self.state==="start"){self.state="end";conf.listener(event,self)}return}if(conf.cancel)return;if((new Date).getTime()-timestamp>conf.timeout)return;var fingers=conf.gestureFingers;if(conf.minFingers<=fingers&&conf.maxFingers>=fingers){self.state="tap";self.fingers=conf.gestureFingers;conf.listener(event,self)}}};var self=root.pointerSetup(conf);Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.tap=root.tap;Event.Gesture._gestureHandlers.longpress=root.longpress;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.wheel=function(conf){var interval;var timeout=conf.timeout||150;var count=0;var self={gesture:"wheel",state:"start",wheelDelta:0,target:conf.target,listener:conf.listener,preventElasticBounce:function(){var target=this.target;var scrollTop=target.scrollTop;var top=scrollTop+target.offsetHeight;var height=target.scrollHeight;if(top===height&&this.wheelDelta<=0)Event.cancel(event);else if(scrollTop===0&&this.wheelDelta>=0)Event.cancel(event);Event.stop(event)},add:function(){conf.target[add](type,onMouseWheel,false)},remove:function(){conf.target[remove](type,onMouseWheel,false)}};var onMouseWheel=function(event){event=event||window.event;self.state=count++?"change":"start";self.wheelDelta=event.detail?event.detail*-20:event.wheelDelta;conf.listener(event,self);clearTimeout(interval);interval=setTimeout(function(){count=0;self.state="end";self.wheelDelta=0;conf.listener(event,self)},timeout)};var add=document.addEventListener?"addEventListener":"attachEvent";var remove=document.removeEventListener?"removeEventListener":"detachEvent";var type=Event.getEventSupport("mousewheel")?"mousewheel":"DOMMouseScroll";conf.target[add](type,onMouseWheel,false);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.wheel=root.wheel;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.orientation=function(conf){var self={gesture:"orientationchange",previous:null,current:window.orientation,target:conf.target,listener:conf.listener,remove:function(){window.removeEventListener("orientationchange",onOrientationChange,false)}};var onOrientationChange=function(e){self.previous=self.current;self.current=window.orientation;if(self.previous!==null&&self.previous!=self.current){conf.listener(e,self);return}};if(window.DeviceOrientationEvent){window.addEventListener("orientationchange",onOrientationChange,false)}return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.orientation=root.orientation;return root}(Event.proxy);(function(){function _removeEventListener(eventName,handler){if(!this.__eventListeners[eventName])return;if(handler){fabric.util.removeFromArray(this.__eventListeners[eventName],handler)}else{this.__eventListeners[eventName].length=0}}function observe(eventName,handler){if(!this.__eventListeners){this.__eventListeners={}}if(arguments.length===1){for(var prop in eventName){this.on(prop,eventName[prop]) +}}else{if(!this.__eventListeners[eventName]){this.__eventListeners[eventName]=[]}this.__eventListeners[eventName].push(handler)}return this}function stopObserving(eventName,handler){if(!this.__eventListeners)return;if(arguments.length===0){this.__eventListeners={}}else if(arguments.length===1&&typeof arguments[0]==="object"){for(var prop in eventName){_removeEventListener.call(this,prop,eventName[prop])}}else{_removeEventListener.call(this,eventName,handler)}return this}function fire(eventName,options){if(!this.__eventListeners)return;var listenersForEvent=this.__eventListeners[eventName];if(!listenersForEvent)return;for(var i=0,len=listenersForEvent.length;i-1},complexity:function(){return this.getObjects().reduce(function(memo,current){memo+=current.complexity?current.complexity():0;return memo},0)}};(function(global){var sqrt=Math.sqrt,atan2=Math.atan2,PiBy180=Math.PI/180;fabric.util={removeFromArray:function(array,value){var idx=array.indexOf(value);if(idx!==-1){array.splice(idx,1)}return array},getRandomInt:function(min,max){return Math.floor(Math.random()*(max-min+1))+min},degreesToRadians:function(degrees){return degrees*PiBy180},radiansToDegrees:function(radians){return radians/PiBy180},rotatePoint:function(point,origin,radians){var sin=Math.sin(radians),cos=Math.cos(radians);point.subtractEquals(origin);var rx=point.x*cos-point.y*sin,ry=point.x*sin+point.y*cos;return new fabric.Point(rx,ry).addEquals(origin)},transformPoint:function(p,t,ignoreOffset){if(ignoreOffset){return new fabric.Point(t[0]*p.x+t[1]*p.y,t[2]*p.x+t[3]*p.y)}return new fabric.Point(t[0]*p.x+t[1]*p.y+t[4],t[2]*p.x+t[3]*p.y+t[5])},invertTransform:function(t){var r=t.slice(),a=1/(t[0]*t[3]-t[1]*t[2]);r=[a*t[3],-a*t[1],-a*t[2],a*t[0],0,0];var o=fabric.util.transformPoint({x:t[4],y:t[5]},r);r[4]=-o.x;r[5]=-o.y;return r},toFixed:function(number,fractionDigits){return parseFloat(Number(number).toFixed(fractionDigits))},falseFunction:function(){return false},getKlass:function(type,namespace){type=fabric.util.string.camelize(type.charAt(0).toUpperCase()+type.slice(1));return fabric.util.resolveNamespace(namespace)[type]},resolveNamespace:function(namespace){if(!namespace){return fabric}var parts=namespace.split("."),len=parts.length,obj=global||fabric.window;for(var i=0;i1){object=new fabric.PathGroup(elements,options)}else{object=elements[0]}if(typeof path!=="undefined"){object.setSourcePath(path)}return object},populateWithProperties:function(source,destination,properties){if(properties&&Object.prototype.toString.call(properties)==="[object Array]"){for(var i=0,len=properties.length;ix){x+=da[di++%dc];if(x>len){x=len}ctx[draw?"lineTo":"moveTo"](x,0);draw=!draw}ctx.restore()},createCanvasElement:function(canvasEl){canvasEl||(canvasEl=fabric.document.createElement("canvas"));if(!canvasEl.getContext&&typeof G_vmlCanvasManager!=="undefined"){G_vmlCanvasManager.initElement(canvasEl)}return canvasEl},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(klass){var proto=klass.prototype;for(var i=proto.stateProperties.length;i--;){var propName=proto.stateProperties[i],capitalizedPropName=propName.charAt(0).toUpperCase()+propName.slice(1),setterName="set"+capitalizedPropName,getterName="get"+capitalizedPropName;if(!proto[getterName]){proto[getterName]=function(property){return new Function('return this.get("'+property+'")')}(propName)}if(!proto[setterName]){proto[setterName]=function(property){return new Function("value",'return this.set("'+property+'", value)')}(propName)}}},clipContext:function(receiver,ctx){ctx.save();ctx.beginPath();receiver.clipTo(ctx);ctx.clip()},multiplyTransformMatrices:function(matrixA,matrixB){var a=[[matrixA[0],matrixA[2],matrixA[4]],[matrixA[1],matrixA[3],matrixA[5]],[0,0,1]],b=[[matrixB[0],matrixB[2],matrixB[4]],[matrixB[1],matrixB[3],matrixB[5]],[0,0,1]],result=[];for(var r=0;r<3;r++){result[r]=[];for(var c=0;c<3;c++){var sum=0;for(var k=0;k<3;k++){sum+=a[r][k]*b[k][c]}result[r][c]=sum}}return[result[0][0],result[1][0],result[0][1],result[1][1],result[0][2],result[1][2]]},getFunctionBody:function(fn){return(String(fn).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},normalizePoints:function(points,options){var minX=fabric.util.array.min(points,"x"),minY=fabric.util.array.min(points,"y");minX=minX<0?minX:0;minY=minX<0?minY:0;for(var i=0,len=points.length;i0){if(x>tolerance){x-=tolerance}else{x=0}if(y>tolerance){y-=tolerance}else{y=0}}var _isTransparent=true,imageData=ctx.getImageData(x,y,tolerance*2||1,tolerance*2||1);for(var i=3,l=imageData.data.length;i0&&sweep===0){thArc-=2*Math.PI}var segments=Math.ceil(Math.abs(thArc/(Math.PI*.5+.001))),result=[];for(var i=0;i1){pl=Math.sqrt(pl);rx*=pl;ry*=pl}var a00=cosTh/rx,a01=sinTh/rx,a10=-sinTh/ry,a11=cosTh/ry;return{x0:a00*ox+a01*oy,y0:a10*ox+a11*oy,x1:a00*x+a01*y,y1:a10*x+a11*y,sinTh:sinTh,cosTh:cosTh}}function segmentToBezier(cx,cy,th0,th1,rx,ry,sinTh,cosTh){argsString=_join.call(arguments);if(segmentToBezierCache[argsString]){return segmentToBezierCache[argsString]}var a00=cosTh*rx,a01=-sinTh*ry,a10=sinTh*rx,a11=cosTh*ry,thHalf=.5*(th1-th0),t=8/3*Math.sin(thHalf*.5)*Math.sin(thHalf*.5)/Math.sin(thHalf),x1=cx+Math.cos(th0)-t*Math.sin(th0),y1=cy+Math.sin(th0)+t*Math.cos(th0),x3=cx+Math.cos(th1),y3=cy+Math.sin(th1),x2=x3+t*Math.sin(th1),y2=y3-t*Math.cos(th1);segmentToBezierCache[argsString]=[a00*x1+a01*y1,a10*x1+a11*y1,a00*x2+a01*y2,a10*x2+a11*y2,a00*x3+a01*y3,a10*x3+a11*y3];return segmentToBezierCache[argsString]}fabric.util.drawArc=function(ctx,x,y,coords){var rx=coords[0],ry=coords[1],rot=coords[2],large=coords[3],sweep=coords[4],ex=coords[5],ey=coords[6],segs=arcToSegments(ex,ey,rx,ry,large,sweep,rot,x,y);for(var i=0;i>>0;if(len===0){return-1}var n=0;if(arguments.length>0){n=Number(arguments[1]);if(n!==n){n=0}else if(n!==0&&n!==Number.POSITIVE_INFINITY&&n!==Number.NEGATIVE_INFINITY){n=(n>0||-1)*Math.floor(Math.abs(n))}}if(n>=len){return-1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k>>0;i>>0;i>>0;i>>0;i>>0;i>>0,i=0,rv;if(arguments.length>1){rv=arguments[1]}else{do{if(i in this){rv=this[i++];break}if(++i>=len){throw new TypeError}}while(true)}for(;i=value2})}function min(array,byProperty){return find(array,byProperty,function(value1,value2){return value1/g,">")}fabric.util.string={camelize:camelize,capitalize:capitalize,escapeXml:escapeXml}})();(function(){var slice=Array.prototype.slice,apply=Function.prototype.apply,Dummy=function(){};if(!Function.prototype.bind){Function.prototype.bind=function(thisArg){var _this=this,args=slice.call(arguments,1),bound;if(args.length){bound=function(){return apply.call(_this,this instanceof Dummy?this:thisArg,args.concat(slice.call(arguments)))}}else{bound=function(){return apply.call(_this,this instanceof Dummy?this:thisArg,arguments)}}Dummy.prototype=this.prototype;bound.prototype=new Dummy;return bound}}})();(function(){var slice=Array.prototype.slice,emptyFunction=function(){},IS_DONTENUM_BUGGY=function(){for(var p in{toString:1}){if(p==="toString")return false}return true}(),addMethods=function(klass,source,parent){for(var property in source){if(property in klass.prototype&&typeof klass.prototype[property]==="function"&&(source[property]+"").indexOf("callSuper")>-1){klass.prototype[property]=function(property){return function(){var superclass=this.constructor.superclass;this.constructor.superclass=parent;var returnValue=source[property].apply(this,arguments);this.constructor.superclass=superclass;if(property!=="initialize"){return returnValue}}}(property)}else{klass.prototype[property]=source[property]}if(IS_DONTENUM_BUGGY){if(source.toString!==Object.prototype.toString){klass.prototype.toString=source.toString}if(source.valueOf!==Object.prototype.valueOf){klass.prototype.valueOf=source.valueOf}}}};function Subclass(){}function callSuper(methodName){var fn=this.constructor.superclass.prototype[methodName];return arguments.length>1?fn.apply(this,slice.call(arguments,1)):fn.call(this)}function createClass(){var parent=null,properties=slice.call(arguments,0);if(typeof properties[0]==="function"){parent=properties.shift()}function klass(){this.initialize.apply(this,arguments)}klass.superclass=parent;klass.subclasses=[];if(parent){Subclass.prototype=parent.prototype;klass.prototype=new Subclass;parent.subclasses.push(klass)}for(var i=0,length=properties.length;i-1?setOpacity(element,styles.match(/opacity:\s*(\d?\.?\d*)/)[1]):element}for(var property in styles){if(property==="opacity"){setOpacity(element,styles[property])}else{var normalizedProperty=property==="float"||property==="cssFloat"?typeof elementStyle.styleFloat==="undefined"?"cssFloat":"styleFloat":property;elementStyle[normalizedProperty]=styles[property]}}return element}var parseEl=fabric.document.createElement("div"),supportsOpacity=typeof parseEl.style.opacity==="string",supportsFilters=typeof parseEl.style.filter==="string",reOpacity=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,setOpacity=function(element){return element};if(supportsOpacity){setOpacity=function(element,value){element.style.opacity=value;return element}}else if(supportsFilters){setOpacity=function(element,value){var es=element.style;if(element.currentStyle&&!element.currentStyle.hasLayout){es.zoom=1}if(reOpacity.test(es.filter)){value=value>=.9999?"":"alpha(opacity="+value*100+")";es.filter=es.filter.replace(reOpacity,value)}else{es.filter+=" alpha(opacity="+value*100+")"}return element}}fabric.util.setStyle=setStyle})();(function(){var _slice=Array.prototype.slice;function getById(id){return typeof id==="string"?fabric.document.getElementById(id):id}var sliceCanConvertNodelists,toArray=function(arrayLike){return _slice.call(arrayLike,0)};try{sliceCanConvertNodelists=toArray(fabric.document.childNodes)instanceof Array}catch(err){}if(!sliceCanConvertNodelists){toArray=function(arrayLike){var arr=new Array(arrayLike.length),i=arrayLike.length;while(i--){arr[i]=arrayLike[i]}return arr}}function makeElement(tagName,attributes){var el=fabric.document.createElement(tagName);for(var prop in attributes){if(prop==="class"){el.className=attributes[prop]}else if(prop==="for"){el.htmlFor=attributes[prop]}else{el.setAttribute(prop,attributes[prop])}}return el}function addClass(element,className){if(element&&(" "+element.className+" ").indexOf(" "+className+" ")===-1){element.className+=(element.className?" ":"")+className}}function wrapElement(element,wrapper,attributes){if(typeof wrapper==="string"){wrapper=makeElement(wrapper,attributes)}if(element.parentNode){element.parentNode.replaceChild(wrapper,element)}wrapper.appendChild(element);return wrapper}function getScrollLeftTop(element,upperCanvasEl){var firstFixedAncestor,origElement,left=0,top=0,docElement=fabric.document.documentElement,body=fabric.document.body||{scrollLeft:0,scrollTop:0};origElement=element;while(element&&element.parentNode&&!firstFixedAncestor){element=element.parentNode;if(element!==fabric.document&&fabric.util.getElementStyle(element,"position")==="fixed"){firstFixedAncestor=element}if(element!==fabric.document&&origElement!==upperCanvasEl&&fabric.util.getElementStyle(element,"position")==="absolute"){left=0;top=0}else if(element===fabric.document){left=body.scrollLeft||docElement.scrollLeft||0;top=body.scrollTop||docElement.scrollTop||0}else{left+=element.scrollLeft||0;top+=element.scrollTop||0}}return{left:left,top:top}}function getElementOffset(element){var docElem,doc=element&&element.ownerDocument,box={left:0,top:0},offset={left:0,top:0},scrollLeftTop,offsetAttributes={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!doc){return{left:0,top:0}}for(var attr in offsetAttributes){offset[offsetAttributes[attr]]+=parseInt(getElementStyle(element,attr),10)||0}docElem=doc.documentElement;if(typeof element.getBoundingClientRect!=="undefined"){box=element.getBoundingClientRect()}scrollLeftTop=fabric.util.getScrollLeftTop(element,null);return{left:box.left+scrollLeftTop.left-(docElem.clientLeft||0)+offset.left,top:box.top+scrollLeftTop.top-(docElem.clientTop||0)+offset.top}}var getElementStyle;if(fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle){getElementStyle=function(element,attr){return fabric.document.defaultView.getComputedStyle(element,null)[attr]}}else{getElementStyle=function(element,attr){var value=element.style[attr];if(!value&&element.currentStyle){value=element.currentStyle[attr]}return value}}(function(){var style=fabric.document.documentElement.style,selectProp="userSelect"in style?"userSelect":"MozUserSelect"in style?"MozUserSelect":"WebkitUserSelect"in style?"WebkitUserSelect":"KhtmlUserSelect"in style?"KhtmlUserSelect":"";function makeElementUnselectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=fabric.util.falseFunction}if(selectProp){element.style[selectProp]="none"}else if(typeof element.unselectable==="string"){element.unselectable="on"}return element}function makeElementSelectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=null}if(selectProp){element.style[selectProp]=""}else if(typeof element.unselectable==="string"){element.unselectable=""}return element}fabric.util.makeElementUnselectable=makeElementUnselectable;fabric.util.makeElementSelectable=makeElementSelectable})();(function(){function getScript(url,callback){var headEl=fabric.document.getElementsByTagName("head")[0],scriptEl=fabric.document.createElement("script"),loading=true;scriptEl.onload=scriptEl.onreadystatechange=function(e){if(loading){if(typeof this.readyState==="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;loading=false;callback(e||fabric.window.event);scriptEl=scriptEl.onload=scriptEl.onreadystatechange=null}};scriptEl.src=url;headEl.appendChild(scriptEl)}fabric.util.getScript=getScript})();fabric.util.getById=getById;fabric.util.toArray=toArray;fabric.util.makeElement=makeElement;fabric.util.addClass=addClass;fabric.util.wrapElement=wrapElement;fabric.util.getScrollLeftTop=getScrollLeftTop;fabric.util.getElementOffset=getElementOffset;fabric.util.getElementStyle=getElementStyle})();(function(){function addParamToUrl(url,param){return url+(/\?/.test(url)?"&":"?")+param}var makeXHR=function(){var factories=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var i=factories.length;i--;){try{var req=factories[i]();if(req){return factories[i]}}catch(err){}}}();function emptyFn(){}function request(url,options){options||(options={});var method=options.method?options.method.toUpperCase():"GET",onComplete=options.onComplete||function(){},xhr=makeXHR(),body;xhr.onreadystatechange=function(){if(xhr.readyState===4){onComplete(xhr);xhr.onreadystatechange=emptyFn}};if(method==="GET"){body=null;if(typeof options.parameters==="string"){url=addParamToUrl(url,options.parameters)}}xhr.open(method,url,true);if(method==="POST"||method==="PUT"){xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")}xhr.send(body);return xhr}fabric.util.request=request})();fabric.log=function(){};fabric.warn=function(){};if(typeof console!=="undefined"){["log","warn"].forEach(function(methodName){if(typeof console[methodName]!=="undefined"&&console[methodName].apply){fabric[methodName]=function(){return console[methodName].apply(console,arguments)}}})}(function(){function animate(options){requestAnimFrame(function(timestamp){options||(options={});var start=timestamp||+new Date,duration=options.duration||500,finish=start+duration,time,onChange=options.onChange||function(){},abort=options.abort||function(){return false},easing=options.easing||function(t,b,c,d){return-c*Math.cos(t/d*(Math.PI/2))+c+b},startValue="startValue"in options?options.startValue:0,endValue="endValue"in options?options.endValue:100,byValue=options.byValue||endValue-startValue;options.onStart&&options.onStart();(function tick(ticktime){time=ticktime||+new Date;var currentTime=time>finish?duration:time-start;if(abort()){options.onComplete&&options.onComplete();return}onChange(easing(currentTime,startValue,byValue,duration));if(time>finish){options.onComplete&&options.onComplete();return}requestAnimFrame(tick)})(start)})}var _requestAnimFrame=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(callback){fabric.window.setTimeout(callback,1e3/60)};function requestAnimFrame(){return _requestAnimFrame.apply(fabric.window,arguments)}fabric.util.animate=animate;fabric.util.requestAnimFrame=requestAnimFrame})();(function(){function normalize(a,c,p,s){if(a1){matrices.shift();combinedMatrix=fabric.util.multiplyTransformMatrices(combinedMatrix,matrices[0])}return combinedMatrix}}();function parseFontDeclaration(value,oStyle){var match=value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);if(!match)return;var fontStyle=match[1],fontWeight=match[3],fontSize=match[4],lineHeight=match[5],fontFamily=match[6];if(fontStyle){oStyle.fontStyle=fontStyle}if(fontWeight){oStyle.fontWeight=isNaN(parseFloat(fontWeight))?fontWeight:parseFloat(fontWeight)}if(fontSize){oStyle.fontSize=parseFloat(fontSize)}if(fontFamily){oStyle.fontFamily=fontFamily}if(lineHeight){oStyle.lineHeight=lineHeight==="normal"?1:lineHeight}}function parseStyleString(style,oStyle){var attr,value;style.replace(/;$/,"").split(";").forEach(function(chunk){var pair=chunk.split(":");attr=normalizeAttr(pair[0].trim().toLowerCase());value=normalizeValue(attr,pair[1].trim());if(attr==="font"){parseFontDeclaration(value,oStyle)}else{oStyle[attr]=value}})}function parseStyleObject(style,oStyle){var attr,value;for(var prop in style){if(typeof style[prop]==="undefined")continue;attr=normalizeAttr(prop.toLowerCase());value=normalizeValue(attr,style[prop]);if(attr==="font"){parseFontDeclaration(value,oStyle)}else{oStyle[attr]=value}}}function getGlobalStylesForElement(element){var nodeName=element.nodeName,className=element.getAttribute("class"),id=element.getAttribute("id"),styles={};for(var rule in fabric.cssRules){var ruleMatchesElement=className&&new RegExp("^\\."+className).test(rule)||id&&new RegExp("^#"+id).test(rule)||new RegExp("^"+nodeName).test(rule);if(ruleMatchesElement){for(var property in fabric.cssRules[rule]){styles[property]=fabric.cssRules[rule][property]}}}return styles}fabric.parseSVGDocument=function(){var reAllowedSVGTagNames=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",reViewBoxAttrValue=new RegExp("^"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*"+"$");function hasAncestorWithNodeName(element,nodeName){while(element&&(element=element.parentNode)){if(nodeName.test(element.nodeName)){return true}}return false}return function(doc,callback,reviver){if(!doc)return;var startTime=new Date,descendants=fabric.util.toArray(doc.getElementsByTagName("*"));if(descendants.length===0&&fabric.isLikelyNode){descendants=doc.selectNodes('//*[name(.)!="svg"]');var arr=[];for(var i=0,len=descendants.length;i','')}}extend(fabric,{resolveGradients:function(instances){for(var i=instances.length;i--;){var instanceFillValue=instances[i].get("fill");if(!/^url\(/.test(instanceFillValue))continue;var gradientId=instanceFillValue.slice(5,instanceFillValue.length-1);if(fabric.gradientDefs[gradientId]){instances[i].set("fill",fabric.Gradient.fromElement(fabric.gradientDefs[gradientId],instances[i]))}}},getGradientDefs:function(doc){var linearGradientEls=doc.getElementsByTagName("linearGradient"),radialGradientEls=doc.getElementsByTagName("radialGradient"),el,i,gradientDefs={};i=linearGradientEls.length;for(;i--;){el=linearGradientEls[i];gradientDefs[el.getAttribute("id")]=el}i=radialGradientEls.length;for(;i--;){el=radialGradientEls[i];gradientDefs[el.getAttribute("id")]=el}return gradientDefs},parseAttributes:function(element,attributes){if(!element){return}var value,parentAttributes={};if(element.parentNode&&/^g$/i.test(element.parentNode.nodeName)){parentAttributes=fabric.parseAttributes(element.parentNode,attributes)}var ownAttributes=attributes.reduce(function(memo,attr){value=element.getAttribute(attr);if(value){attr=normalizeAttr(attr);value=normalizeValue(attr,value,parentAttributes);memo[attr]=value}return memo},{});ownAttributes=extend(ownAttributes,extend(getGlobalStylesForElement(element),fabric.parseStyleAttribute(element)));return _setStrokeFillOpacity(extend(parentAttributes,ownAttributes))},parseElements:function(elements,callback,options,reviver){new fabric.ElementsParser(elements,callback,options,reviver).parse()},parseStyleAttribute:function(element){var oStyle={},style=element.getAttribute("style");if(!style){return oStyle}if(typeof style==="string"){parseStyleString(style,oStyle)}else{parseStyleObject(style,oStyle)}return oStyle},parsePointsAttribute:function(points){if(!points)return null;points=points.trim();var asPairs=points.indexOf(",")>-1;points=points.split(/\s+/);var parsedPoints=[],i,len;if(asPairs){i=0;len=points.length;for(;i/i,""))}if(!xml||!xml.documentElement)return;fabric.parseSVGDocument(xml.documentElement,function(results,options){svgCache.set(url,{objects:fabric.util.array.invoke(results,"toObject"),options:options});callback(results,options)},reviver)}},loadSVGFromString:function(string,callback,reviver){string=string.trim();var doc;if(typeof DOMParser!=="undefined"){var parser=new DOMParser;if(parser&&parser.parseFromString){doc=parser.parseFromString(string,"text/xml")}}else if(fabric.window.ActiveXObject){doc=new ActiveXObject("Microsoft.XMLDOM");doc.async="false";doc.loadXML(string.replace(//i,""))}fabric.parseSVGDocument(doc.documentElement,function(results,options){callback(results,options)},reviver)},createSVGFontFacesMarkup:function(objects){var markup="";for(var i=0,len=objects.length;i',"",""].join("")}return markup},createSVGRefElementsMarkup:function(canvas){var markup=[];_createSVGPattern(markup,canvas,"backgroundColor");_createSVGPattern(markup,canvas,"overlayColor");return markup.join("")}})})(typeof exports!=="undefined"?exports:this);fabric.ElementsParser=function(elements,callback,options,reviver){this.elements=elements;this.callback=callback;this.options=options;this.reviver=reviver};fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length);this.numElements=this.elements.length;this.createObjects()};fabric.ElementsParser.prototype.createObjects=function(){for(var i=0,len=this.elements.length;ithat.x&&this.y>that.y},gte:function(that){return this.x>=that.x&&this.y>=that.y},lerp:function(that,t){return new Point(this.x+(that.x-this.x)*t,this.y+(that.y-this.y)*t)},distanceFrom:function(that){var dx=this.x-that.x,dy=this.y-that.y;return Math.sqrt(dx*dx+dy*dy)},midPointFrom:function(that){return new Point(this.x+(that.x-this.x)/2,this.y+(that.y-this.y)/2)},min:function(that){return new Point(Math.min(this.x,that.x),Math.min(this.y,that.y))},max:function(that){return new Point(Math.max(this.x,that.x),Math.max(this.y,that.y))},toString:function(){return this.x+","+this.y},setXY:function(x,y){this.x=x;this.y=y},setFromPoint:function(that){this.x=that.x;this.y=that.y},swap:function(that){var x=this.x,y=this.y;this.x=that.x;this.y=that.y;that.x=x;that.y=y}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Intersection){fabric.warn("fabric.Intersection is already defined");return}function Intersection(status){this.status=status;this.points=[]}fabric.Intersection=Intersection;fabric.Intersection.prototype={appendPoint:function(point){this.points.push(point)},appendPoints:function(points){this.points=this.points.concat(points)}};fabric.Intersection.intersectLineLine=function(a1,a2,b1,b2){var result,uaT=(b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x),ubT=(a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x),uB=(b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);if(uB!==0){var ua=uaT/uB,ub=ubT/uB;if(0<=ua&&ua<=1&&0<=ub&&ub<=1){result=new Intersection("Intersection");result.points.push(new fabric.Point(a1.x+ua*(a2.x-a1.x),a1.y+ua*(a2.y-a1.y)))}else{result=new Intersection}}else{if(uaT===0||ubT===0){result=new Intersection("Coincident")}else{result=new Intersection("Parallel")}}return result};fabric.Intersection.intersectLinePolygon=function(a1,a2,points){var result=new Intersection,length=points.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonPolygon=function(points1,points2){var result=new Intersection,length=points1.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonRectangle=function(points,r1,r2){var min=r1.min(r2),max=r1.max(r2),topRight=new fabric.Point(max.x,min.y),bottomLeft=new fabric.Point(min.x,max.y),inter1=Intersection.intersectLinePolygon(min,topRight,points),inter2=Intersection.intersectLinePolygon(topRight,max,points),inter3=Intersection.intersectLinePolygon(max,bottomLeft,points),inter4=Intersection.intersectLinePolygon(bottomLeft,min,points),result=new Intersection;result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0){result.status="Intersection"}return result}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Color){fabric.warn("fabric.Color is already defined.");return}function Color(color){if(!color){this.setSource([0,0,0,1])}else{this._tryParsingColor(color)}}fabric.Color=Color;fabric.Color.prototype={_tryParsingColor:function(color){var source;if(color in Color.colorNameMap){color=Color.colorNameMap[color]}if(color==="transparent"){this.setSource([255,255,255,0]);return}source=Color.sourceFromHex(color);if(!source){source=Color.sourceFromRgb(color)}if(!source){source=Color.sourceFromHsl(color)}if(source){this.setSource(source)}},_rgbToHsl:function(r,g,b){r/=255,g/=255,b/=255;var h,s,l,max=fabric.util.array.max([r,g,b]),min=fabric.util.array.min([r,g,b]);l=(max+min)/2;if(max===min){h=s=0}else{var d=max-min;s=l>.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g1){t-=1}if(t<1/6){return p+(q-p)*6*t}if(t<1/2){return q}if(t<2/3){return p+(q-p)*(2/3-t)*6}return p}fabric.Color.fromRgb=function(color){return Color.fromSource(Color.sourceFromRgb(color))};fabric.Color.sourceFromRgb=function(color){var match=color.match(Color.reRGBa);if(match){var r=parseInt(match[1],10)/(/%$/.test(match[1])?100:1)*(/%$/.test(match[1])?255:1),g=parseInt(match[2],10)/(/%$/.test(match[2])?100:1)*(/%$/.test(match[2])?255:1),b=parseInt(match[3],10)/(/%$/.test(match[3])?100:1)*(/%$/.test(match[3])?255:1);return[parseInt(r,10),parseInt(g,10),parseInt(b,10),match[4]?parseFloat(match[4]):1]}};fabric.Color.fromRgba=Color.fromRgb;fabric.Color.fromHsl=function(color){return Color.fromSource(Color.sourceFromHsl(color))};fabric.Color.sourceFromHsl=function(color){var match=color.match(Color.reHSLa);if(!match)return;var h=(parseFloat(match[1])%360+360)%360/360,s=parseFloat(match[2])/(/%$/.test(match[2])?100:1),l=parseFloat(match[3])/(/%$/.test(match[3])?100:1),r,g,b;if(s===0){r=g=b=l}else{var q=l<=.5?l*(s+1):l+s-l*s,p=l*2-q;r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3)}return[Math.round(r*255),Math.round(g*255),Math.round(b*255),match[4]?parseFloat(match[4]):1]};fabric.Color.fromHsla=Color.fromHsl;fabric.Color.fromHex=function(color){return Color.fromSource(Color.sourceFromHex(color))};fabric.Color.sourceFromHex=function(color){if(color.match(Color.reHex)){var value=color.slice(color.indexOf("#")+1),isShortNotation=value.length===3,r=isShortNotation?value.charAt(0)+value.charAt(0):value.substring(0,2),g=isShortNotation?value.charAt(1)+value.charAt(1):value.substring(2,4),b=isShortNotation?value.charAt(2)+value.charAt(2):value.substring(4,6);return[parseInt(r,16),parseInt(g,16),parseInt(b,16),1]}};fabric.Color.fromSource=function(source){var oColor=new Color;oColor.setSource(source);return oColor}})(typeof exports!=="undefined"?exports:this);(function(){function getColorStop(el){var style=el.getAttribute("style"),offset=el.getAttribute("offset"),color,opacity;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")}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%"}}fabric.Gradient=fabric.util.createClass({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()},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},toObject:function(){return{type:this.type,coords:this.coords,gradientUnits:this.gradientUnits,colorStops:this.colorStops}},toSVG:function(object,normalize){var coords=fabric.util.object.clone(this.coords),markup;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')}markup.push(this.type==="linear"?"":"");return markup.join("")},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'+''+""},toLive:function(ctx){var source=typeof this.source==="function"?this.source():this.source;if(!source){return""}if(typeof source.src!=="undefined"){if(!source.complete){return""}if(source.naturalWidth===0||source.naturalHeight===0){return""}}return ctx.createPattern(source,this.repeat)}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Shadow){fabric.warn("fabric.Shadow is already defined.");return}fabric.Shadow=fabric.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:false,includeDefaultValues:true,initialize:function(options){if(typeof options==="string"){options=this._parseShadow(options)}for(var prop in options){this[prop]=options[prop]}this.id=fabric.Object.__uid++},_parseShadow:function(shadow){var shadowStr=shadow.trim(),offsetsAndBlur=fabric.Shadow.reOffsetsAndBlur.exec(shadowStr)||[],color=shadowStr.replace(fabric.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:color.trim(),offsetX:parseInt(offsetsAndBlur[1],10)||0,offsetY:parseInt(offsetsAndBlur[2],10)||0,blur:parseInt(offsetsAndBlur[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(object){var mode="SourceAlpha";if(object&&(object.fill===this.color||object.stroke===this.color)){mode="SourceGraphic"}return''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues){return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY}}var obj={},proto=fabric.Shadow.prototype;if(this.color!==proto.color){obj.color=this.color}if(this.blur!==proto.blur){obj.blur=this.blur}if(this.offsetX!==proto.offsetX){obj.offsetX=this.offsetX}if(this.offsetY!==proto.offsetY){obj.offsetY=this.offsetY}return obj}});fabric.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/})(typeof exports!=="undefined"?exports:this);(function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var extend=fabric.util.object.extend,getElementOffset=fabric.util.getElementOffset,removeFromArray=fabric.util.removeFromArray,CANVAS_INIT_ERROR=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(el,options){options||(options={});this._initStatic(el,options);fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:true,stateful:true,renderOnAddRemove:true,clipTo:null,controlsAboveOverlay:false,allowTouchScrolling:false,imageSmoothingEnabled:true,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(el,options){this._objects=[];this._createLowerCanvas(el);this._initOptions(options);this._setImageSmoothing();if(options.overlayImage){this.setOverlayImage(options.overlayImage,this.renderAll.bind(this))}if(options.backgroundImage){this.setBackgroundImage(options.backgroundImage,this.renderAll.bind(this))}if(options.backgroundColor){this.setBackgroundColor(options.backgroundColor,this.renderAll.bind(this))}if(options.overlayColor){this.setOverlayColor(options.overlayColor,this.renderAll.bind(this))}this.calcOffset()},calcOffset:function(){this._offset=getElementOffset(this.lowerCanvasEl);return this},setOverlayImage:function(image,callback,options){return this.__setBgOverlayImage("overlayImage",image,callback,options)},setBackgroundImage:function(image,callback,options){return this.__setBgOverlayImage("backgroundImage",image,callback,options)},setOverlayColor:function(overlayColor,callback){return this.__setBgOverlayColor("overlayColor",overlayColor,callback)},setBackgroundColor:function(backgroundColor,callback){return this.__setBgOverlayColor("backgroundColor",backgroundColor,callback)},_setImageSmoothing:function(){var ctx=this.getContext();ctx.imageSmoothingEnabled=this.imageSmoothingEnabled;ctx.webkitImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.mozImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.msImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(property,image,callback,options){if(typeof image==="string"){fabric.util.loadImage(image,function(img){this[property]=new fabric.Image(img,options);callback&&callback()},this)}else{this[property]=image;callback&&callback()}return this},__setBgOverlayColor:function(property,color,callback){if(color.source){var _this=this;fabric.util.loadImage(color.source,function(img){_this[property]=new fabric.Pattern({source:img,repeat:color.repeat,offsetX:color.offsetX,offsetY:color.offsetY});callback&&callback()})}else{this[property]=color;callback&&callback()}return this},_createCanvasElement:function(){var element=fabric.document.createElement("canvas");if(!element.style){element.style={}}if(!element){throw CANVAS_INIT_ERROR}this._initCanvasElement(element);return element},_initCanvasElement:function(element){fabric.util.createCanvasElement(element);if(typeof element.getContext==="undefined"){throw CANVAS_INIT_ERROR}},_initOptions:function(options){for(var prop in options){this[prop]=options[prop]}this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0;this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width;this.lowerCanvasEl.height=this.height;this.lowerCanvasEl.style.width=this.width+"px";this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(canvasEl){this.lowerCanvasEl=fabric.util.getById(canvasEl)||this._createCanvasElement();this._initCanvasElement(this.lowerCanvasEl);fabric.util.addClass(this.lowerCanvasEl,"lower-canvas");if(this.interactive){this._applyCanvasStyle(this.lowerCanvasEl)}this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(value){return this._setDimension("width",value)},setHeight:function(value){return this._setDimension("height",value)},setDimensions:function(dimensions){for(var prop in dimensions){this._setDimension(prop,dimensions[prop])}return this},_setDimension:function(prop,value){this.lowerCanvasEl[prop]=value;this.lowerCanvasEl.style[prop]=value+"px";if(this.upperCanvasEl){this.upperCanvasEl[prop]=value;this.upperCanvasEl.style[prop]=value+"px"}if(this.cacheCanvasEl){this.cacheCanvasEl[prop]=value}if(this.wrapperEl){this.wrapperEl.style[prop]=value+"px"}this[prop]=value;this.calcOffset();this.renderAll();return this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(vpt){this.viewportTransform=vpt;this.renderAll();for(var i=0,len=this._objects.length;i");return markup.join("")},_setSVGPreamble:function(markup,options){if(!options.suppressPreamble){markup.push('','\n')}},_setSVGHeader:function(markup,options){markup.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(markup,reviver){var activeGroup=this.getActiveGroup();if(activeGroup){this.discardActiveGroup()}for(var i=0,objects=this.getObjects(),len=objects.length;i")}else if(this[property]&&property==="overlayColor"){markup.push('")}},sendToBack:function(object){removeFromArray(this._objects,object);this._objects.unshift(object);return this.renderAll&&this.renderAll()},bringToFront:function(object){removeFromArray(this._objects,object);this._objects.push(object);return this.renderAll&&this.renderAll()},sendBackwards:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==0){var newIdx=this._findNewLowerIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx-1;i>=0;--i){var isIntersecting=object.intersectsWithObject(this._objects[i])||object.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(object);if(isIntersecting){newIdx=i;break}}}else{newIdx=idx-1}return newIdx},bringForward:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==this._objects.length-1){var newIdx=this._findNewUpperIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx+1;i"}});extend(fabric.StaticCanvas.prototype,fabric.Observable);extend(fabric.StaticCanvas.prototype,fabric.Collection);extend(fabric.StaticCanvas.prototype,fabric.DataURLExporter);extend(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(methodName){var el=fabric.util.createCanvasElement();if(!el||!el.getContext){return null}var ctx=el.getContext("2d");if(!ctx){return null}switch(methodName){case"getImageData":return typeof ctx.getImageData!=="undefined";case"setLineDash":return typeof ctx.setLineDash!=="undefined";case"toDataURL":return typeof el.toDataURL!=="undefined";case"toDataURLWithQuality":try{el.toDataURL("image/jpeg",0);return true}catch(e){}return false;default:return null}}});fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject})();fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(options){this.shadow=new fabric.Shadow(options);return this},_setBrushStyles:function(){var ctx=this.canvas.contextTop;ctx.strokeStyle=this.color;ctx.lineWidth=this.width;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var ctx=this.canvas.contextTop;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var ctx=this.canvas.contextTop;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0}});(function(){var utilMin=fabric.util.array.min,utilMax=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(canvas){this.canvas=canvas;this._points=[]},onMouseDown:function(pointer){this._prepareForDrawing(pointer);this._captureDrawingPath(pointer);this._render()},onMouseMove:function(pointer){this._captureDrawingPath(pointer);this.canvas.clearContext(this.canvas.contextTop);this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(pointer){var p=new fabric.Point(pointer.x,pointer.y);this._reset();this._addPoint(p);this.canvas.contextTop.moveTo(p.x,p.y)},_addPoint:function(point){this._points.push(point)},_reset:function(){this._points.length=0;this._setBrushStyles();this._setShadow()},_captureDrawingPath:function(pointer){var pointerPoint=new fabric.Point(pointer.x,pointer.y);this._addPoint(pointerPoint)},_render:function(){var ctx=this.canvas.contextTop;var v=this.canvas.viewportTransform;ctx.save();ctx.transform(v[0],v[1],v[2],v[3],v[4],v[5]);ctx.beginPath();var p1=this._points[0],p2=this._points[1];if(this._points.length===2&&p1.x===p2.x&&p1.y===p2.y){p1.x-=.5;p2.x+=.5}ctx.moveTo(p1.x,p1.y);for(var i=1,len=this._points.length;itarget.padding){if(localMouse.x<0){localMouse.x+=target.padding}else{localMouse.x-=target.padding}}else{localMouse.x=0}if(abs(localMouse.y)>target.padding){if(localMouse.y<0){localMouse.y+=target.padding}else{localMouse.y-=target.padding}}else{localMouse.y=0}},_rotateObject:function(x,y){var t=this._currentTransform;if(t.target.get("lockRotation"))return;var lastAngle=atan2(t.ey-t.top,t.ex-t.left),curAngle=atan2(y-t.top,x-t.left),angle=radiansToDegrees(curAngle-lastAngle+t.theta);if(angle<0){angle=360+angle}t.target.angle=angle},_setCursor:function(value){this.upperCanvasEl.style.cursor=value},_resetObjectTransform:function(target){target.scaleX=1;target.scaleY=1;target.setAngle(0)},_drawSelection:function(){var ctx=this.contextTop,groupSelector=this._groupSelector,left=groupSelector.left,top=groupSelector.top,aleft=abs(left),atop=abs(top);ctx.fillStyle=this.selectionColor;ctx.fillRect(groupSelector.ex-(left>0?0:-left),groupSelector.ey-(top>0?0:-top),aleft,atop);ctx.lineWidth=this.selectionLineWidth;ctx.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var px=groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),py=groupSelector.ey+STROKE_OFFSET-(top>0?0:atop);ctx.beginPath();fabric.util.drawDashedLine(ctx,px,py,px+aleft,py,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py+atop-1,px+aleft,py+atop-1,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py,px,py+atop,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px+aleft-1,py,px+aleft-1,py+atop,this.selectionDashArray);ctx.closePath();ctx.stroke()}else{ctx.strokeRect(groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),groupSelector.ey+STROKE_OFFSET-(top>0?0:atop),aleft,atop)}},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,true))},findTarget:function(e,skipGroup){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e)){return this.lastRenderedObjectWithControlsAboveOverlay}var activeGroup=this.getActiveGroup();if(activeGroup&&!skipGroup&&this.containsPoint(e,activeGroup)){return activeGroup}var target=this._searchPossibleTargets(e);this._fireOverOutEvents(target);return target},_fireOverOutEvents:function(target){if(target){if(this._hoveredTarget!==target){this.fire("mouse:over",{target:target});target.fire("mouseover");if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout")}this._hoveredTarget=target}}else if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout");this._hoveredTarget=null}},_checkTarget:function(e,obj,pointer){if(obj&&obj.visible&&obj.evented&&this.containsPoint(e,obj)){if((this.perPixelTargetFind||obj.perPixelTargetFind)&&!obj.isEditing){var isTransparent=this.isTargetTransparent(obj,pointer.x,pointer.y);if(!isTransparent){return true}}else{return true}}},_searchPossibleTargets:function(e){var target,pointer=this.getPointer(e,true);var i=this._objects.length;while(i--){if(this._checkTarget(e,this._objects[i],pointer)){this.relatedTarget=this._objects[i];target=this._objects[i];break}}return target},getPointer:function(e,ignoreZoom,upperCanvasEl){if(!upperCanvasEl){upperCanvasEl=this.upperCanvasEl}var pointer=getPointer(e,upperCanvasEl),bounds=upperCanvasEl.getBoundingClientRect(),cssScale;pointer.x=pointer.x-this._offset.left;pointer.y=pointer.y-this._offset.top;if(!ignoreZoom){pointer=fabric.util.transformPoint(pointer,fabric.util.invertTransform(this.viewportTransform))}if(bounds.width===0||bounds.height===0){cssScale={width:1,height:1}}else{cssScale={width:upperCanvasEl.width/bounds.width,height:upperCanvasEl.height/bounds.height}}return{x:pointer.x*cssScale.width,y:pointer.y*cssScale.height}},_createUpperCanvas:function(){var lowerCanvasClass=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement();fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+lowerCanvasClass);this.wrapperEl.appendChild(this.upperCanvasEl);this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl);this._applyCanvasStyle(this.upperCanvasEl);this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement();this.cacheCanvasEl.setAttribute("width",this.width);this.cacheCanvasEl.setAttribute("height",this.height);this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass});fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"});fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(element){var width=this.getWidth()||element.width,height=this.getHeight()||element.height;fabric.util.setStyle(element,{position:"absolute",width:width+"px",height:height+"px",left:0,top:0});element.width=width;element.height=height;fabric.util.makeElementUnselectable(element)},_copyCanvasStyle:function(fromEl,toEl){toEl.style.cssText=fromEl.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(object){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=object;object.set("active",true)},setActiveObject:function(object,e){this._setActiveObject(object);this.renderAll();this.fire("object:selected",{target:object,e:e});object.fire("selected",{e:e});return this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=null},discardActiveObject:function(e){this._discardActiveObject();this.renderAll();this.fire("selection:cleared",{e:e});return this},_setActiveGroup:function(group){this._activeGroup=group;if(group){group.canvas=this;group._calcBounds();group._updateObjectsCoords();group.setCoords();group.set("active",true)}},setActiveGroup:function(group,e){this._setActiveGroup(group);if(group){this.fire("object:selected",{target:group,e:e});group.fire("selected",{e:e})}return this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var g=this.getActiveGroup();if(g){g.destroy()}this.setActiveGroup(null)},discardActiveGroup:function(e){this._discardActiveGroup();this.fire("selection:cleared",{e:e});return this},deactivateAll:function(){var allObjects=this.getObjects(),i=0,len=allObjects.length;for(;i1){group=new fabric.Group(group.reverse(),{originX:"center",originY:"center"});this.setActiveGroup(group,e);group.saveCoords();this.fire("selection:created",{target:group});this.renderAll()}},_collectObjects:function(){var group=[],currentObject,x1=this._groupSelector.ex,y1=this._groupSelector.ey,x2=x1+this._groupSelector.left,y2=y1+this._groupSelector.top,selectionX1Y1=new fabric.Point(min(x1,x2),min(y1,y2)),selectionX2Y2=new fabric.Point(max(x1,x2),max(y1,y2)),isClick=x1===x2&&y1===y2;for(var i=this._objects.length;i--;){currentObject=this._objects[i];if(!currentObject||!currentObject.selectable||!currentObject.visible)continue;if(currentObject.intersectsWithRect(selectionX1Y1,selectionX2Y2)||currentObject.isContainedWithinRect(selectionX1Y1,selectionX2Y2)||currentObject.containsPoint(selectionX1Y1)||currentObject.containsPoint(selectionX2Y2)){currentObject.set("active",true);group.push(currentObject);if(isClick)break}}return group},_maybeGroupObjects:function(e){if(this.selection&&this._groupSelector){this._groupSelectedObjects(e)}var activeGroup=this.getActiveGroup();if(activeGroup){activeGroup.setObjectsCoords().setCoords();activeGroup.isMoving=false;this._setCursor(this.defaultCursor)}this._groupSelector=null;this._currentTransform=null}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(options){options||(options={});var format=options.format||"png",quality=options.quality||1,multiplier=options.multiplier||1,cropping={left:options.left,top:options.top,width:options.width,height:options.height};if(multiplier!==1){return this.__toDataURLWithMultiplier(format,quality,cropping,multiplier)}else{return this.__toDataURL(format,quality,cropping)}},__toDataURL:function(format,quality,cropping){this.renderAll(true);var canvasEl=this.upperCanvasEl||this.lowerCanvasEl,croppedCanvasEl=this.__getCroppedCanvas(canvasEl,cropping);if(format==="jpg"){format="jpeg"}var data=fabric.StaticCanvas.supports("toDataURLWithQuality")?(croppedCanvasEl||canvasEl).toDataURL("image/"+format,quality):(croppedCanvasEl||canvasEl).toDataURL("image/"+format);this.contextTop&&this.clearContext(this.contextTop);this.renderAll();if(croppedCanvasEl){croppedCanvasEl=null}return data},__getCroppedCanvas:function(canvasEl,cropping){var croppedCanvasEl,croppedCtx,shouldCrop="left"in cropping||"top"in cropping||"width"in cropping||"height"in cropping;if(shouldCrop){croppedCanvasEl=fabric.util.createCanvasElement();croppedCtx=croppedCanvasEl.getContext("2d");croppedCanvasEl.width=cropping.width||this.width;croppedCanvasEl.height=cropping.height||this.height;croppedCtx.drawImage(canvasEl,-cropping.left||0,-cropping.top||0)}return croppedCanvasEl},__toDataURLWithMultiplier:function(format,quality,cropping,multiplier){var origWidth=this.getWidth(),origHeight=this.getHeight(),scaledWidth=origWidth*multiplier,scaledHeight=origHeight*multiplier,activeObject=this.getActiveObject(),activeGroup=this.getActiveGroup(),ctx=this.contextTop||this.contextContainer;if(multiplier>1){this.setWidth(scaledWidth).setHeight(scaledHeight)}ctx.scale(multiplier,multiplier);if(cropping.left){cropping.left*=multiplier}if(cropping.top){cropping.top*=multiplier}if(cropping.width){cropping.width*=multiplier}else if(multiplier<1){cropping.width=scaledWidth}if(cropping.height){cropping.height*=multiplier}else if(multiplier<1){cropping.height=scaledHeight}if(activeGroup){this._tempRemoveBordersControlsFromGroup(activeGroup)}else if(activeObject&&this.deactivateAll){this.deactivateAll()}this.renderAll(true);var data=this.__toDataURL(format,quality,cropping);this.width=origWidth;this.height=origHeight;ctx.scale(1/multiplier,1/multiplier);this.setWidth(origWidth).setHeight(origHeight);if(activeGroup){this._restoreBordersControlsOnGroup(activeGroup)}else if(activeObject&&this.setActiveObject){this.setActiveObject(activeObject)}this.contextTop&&this.clearContext(this.contextTop);this.renderAll();return data},toDataURLWithMultiplier:function(format,multiplier,quality){return this.toDataURL({format:format,multiplier:multiplier,quality:quality})},_tempRemoveBordersControlsFromGroup:function(group){group.origHasControls=group.hasControls;group.origBorderColor=group.borderColor;group.hasControls=true;group.borderColor="rgba(0,0,0,0)";group.forEachObject(function(o){o.origBorderColor=o.borderColor;o.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(group){group.hideControls=group.origHideControls;group.borderColor=group.origBorderColor;group.forEachObject(function(o){o.borderColor=o.origBorderColor;delete o.origBorderColor})}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(json,callback,reviver){return this.loadFromJSON(json,callback,reviver)},loadFromJSON:function(json,callback,reviver){if(!json)return;var serialized=typeof json==="string"?JSON.parse(json):json;this.clear();var _this=this;this._enlivenObjects(serialized.objects,function(){_this._setBgOverlay(serialized,callback)},reviver);return this},_setBgOverlay:function(serialized,callback){var _this=this,loaded={backgroundColor:false,overlayColor:false,backgroundImage:false,overlayImage:false};if(!serialized.backgroundImage&&!serialized.overlayImage&&!serialized.background&&!serialized.overlay){callback&&callback(); +return}var cbIfLoaded=function(){if(loaded.backgroundImage&&loaded.overlayImage&&loaded.backgroundColor&&loaded.overlayColor){_this.renderAll();callback&&callback()}};this.__setBgOverlay("backgroundImage",serialized.backgroundImage,loaded,cbIfLoaded);this.__setBgOverlay("overlayImage",serialized.overlayImage,loaded,cbIfLoaded);this.__setBgOverlay("backgroundColor",serialized.background,loaded,cbIfLoaded);this.__setBgOverlay("overlayColor",serialized.overlay,loaded,cbIfLoaded);cbIfLoaded()},__setBgOverlay:function(property,value,loaded,callback){var _this=this;if(!value){loaded[property]=true;return}if(property==="backgroundImage"||property==="overlayImage"){fabric.Image.fromObject(value,function(img){_this[property]=img;loaded[property]=true;callback&&callback()})}else{this["set"+fabric.util.string.capitalize(property,true)](value,function(){loaded[property]=true;callback&&callback()})}},_enlivenObjects:function(objects,callback,reviver){var _this=this;if(!objects||objects.length===0){callback&&callback();return}var renderOnAddRemove=this.renderOnAddRemove;this.renderOnAddRemove=false;fabric.util.enlivenObjects(objects,function(enlivenedObjects){enlivenedObjects.forEach(function(obj,index){_this.insertAt(obj,index,true)});_this.renderOnAddRemove=renderOnAddRemove;callback&&callback()},null,reviver)},_toDataURL:function(format,callback){this.clone(function(clone){callback(clone.toDataURL(format))})},_toDataURLWithMultiplier:function(format,multiplier,callback){this.clone(function(clone){callback(clone.toDataURLWithMultiplier(format,multiplier))})},clone:function(callback,properties){var data=JSON.stringify(this.toJSON(properties));this.cloneWithoutData(function(clone){clone.loadFromJSON(data,function(){callback&&callback(clone)})})},cloneWithoutData:function(callback){var el=fabric.document.createElement("canvas");el.width=this.getWidth();el.height=this.getHeight();var clone=new fabric.Canvas(el);clone.clipTo=this.clipTo;if(this.backgroundImage){clone.setBackgroundImage(this.backgroundImage.src,function(){clone.renderAll();callback&&callback(clone)});clone.backgroundImageOpacity=this.backgroundImageOpacity;clone.backgroundImageStretch=this.backgroundImageStretch}else{callback&&callback(clone)}}});(function(){var degreesToRadians=fabric.util.degreesToRadians,radiansToDegrees=fabric.util.radiansToDegrees;fabric.util.object.extend(fabric.Canvas.prototype,{__onTransformGesture:function(e,self){if(this.isDrawingMode||!e.touches||e.touches.length!==2||"gesture"!==self.gesture){return}var target=this.findTarget(e);if("undefined"!==typeof target){this.onBeforeScaleRotate(target);this._rotateObjectByAngle(self.rotation);this._scaleObjectBy(self.scale)}this.fire("touch:gesture",{target:target,e:e,self:self})},__onDrag:function(e,self){this.fire("touch:drag",{e:e,self:self})},__onOrientationChange:function(e,self){this.fire("touch:orientation",{e:e,self:self})},__onShake:function(e,self){this.fire("touch:shake",{e:e,self:self})},_scaleObjectBy:function(s,by){var t=this._currentTransform,target=t.target,lockScalingX=target.get("lockScalingX"),lockScalingY=target.get("lockScalingY");if(lockScalingX&&lockScalingY)return;target._scaling=true;if(!by){if(!lockScalingX){target.set("scaleX",t.scaleX*s)}if(!lockScalingY){target.set("scaleY",t.scaleY*s)}}else if(by==="x"&&!target.get("lockUniScaling")){lockScalingX||target.set("scaleX",t.scaleX*s)}else if(by==="y"&&!target.get("lockUniScaling")){lockScalingY||target.set("scaleY",t.scaleY*s)}},_rotateObjectByAngle:function(curAngle){var t=this._currentTransform;if(t.target.get("lockRotation"))return;t.target.angle=radiansToDegrees(degreesToRadians(curAngle)+t.theta)}})})();(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,toFixed=fabric.util.toFixed,capitalize=fabric.util.string.capitalize,degreesToRadians=fabric.util.degreesToRadians,supportsLineDash=fabric.StaticCanvas.supports("setLineDash");if(fabric.Object){return}fabric.Object=fabric.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:false,flipY:false,opacity:1,angle:0,cornerSize:12,transparentCorners:true,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:false,centeredRotation:true,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:true,evented:true,visible:true,hasControls:true,hasBorders:true,hasRotatingPoint:true,rotatingPointOffset:40,perPixelTargetFind:false,includeDefaultValues:true,clipTo:null,lockMovementX:false,lockMovementY:false,lockRotation:false,lockScalingX:false,lockScalingY:false,lockUniScaling:false,stateProperties:("top left width height scaleX scaleY flipX flipY originX originY transformMatrix "+"stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit "+"angle opacity fill fillRule shadow clipTo visible backgroundColor").split(" "),initialize:function(options){if(options){this.setOptions(options)}},_initGradient:function(options){if(options.fill&&options.fill.colorStops&&!(options.fill instanceof fabric.Gradient)){this.set("fill",new fabric.Gradient(options.fill))}},_initPattern:function(options){if(options.fill&&options.fill.source&&!(options.fill instanceof fabric.Pattern)){this.set("fill",new fabric.Pattern(options.fill))}if(options.stroke&&options.stroke.source&&!(options.stroke instanceof fabric.Pattern)){this.set("stroke",new fabric.Pattern(options.stroke))}},_initClipping:function(options){if(!options.clipTo||typeof options.clipTo!=="string")return;var functionBody=fabric.util.getFunctionBody(options.clipTo);if(typeof functionBody!=="undefined"){this.clipTo=new Function("ctx",functionBody)}},setOptions:function(options){for(var prop in options){this.set(prop,options[prop])}this._initGradient(options);this._initPattern(options);this._initClipping(options)},transform:function(ctx,fromLeft){if(this.group){this.group.transform(ctx,fromLeft)}ctx.globalAlpha=this.opacity;var center=fromLeft?this._getLeftTopCoords():this.getCenterPoint();ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));ctx.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(propertiesToInclude){var NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,object={type:this.type,originX:this.originX,originY:this.originY,left:toFixed(this.left,NUM_FRACTION_DIGITS),top:toFixed(this.top,NUM_FRACTION_DIGITS),width:toFixed(this.width,NUM_FRACTION_DIGITS),height:toFixed(this.height,NUM_FRACTION_DIGITS),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:toFixed(this.strokeWidth,NUM_FRACTION_DIGITS),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:toFixed(this.strokeMiterLimit,NUM_FRACTION_DIGITS),scaleX:toFixed(this.scaleX,NUM_FRACTION_DIGITS),scaleY:toFixed(this.scaleY,NUM_FRACTION_DIGITS),angle:toFixed(this.getAngle(),NUM_FRACTION_DIGITS),flipX:this.flipX,flipY:this.flipY,opacity:toFixed(this.opacity,NUM_FRACTION_DIGITS),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};if(!this.includeDefaultValues){object=this._removeDefaultValues(object)}fabric.util.populateWithProperties(this,object,propertiesToInclude);return object},toDatalessObject:function(propertiesToInclude){return this.toObject(propertiesToInclude)},_removeDefaultValues:function(object){var prototype=fabric.util.getKlass(object.type).prototype,stateProperties=prototype.stateProperties;stateProperties.forEach(function(prop){if(object[prop]===prototype[prop]){delete object[prop]}});return object},toString:function(){return"#"},get:function(property){return this[property]},_setObject:function(obj){for(var prop in obj){this._set(prop,obj[prop])}},set:function(key,value){if(typeof key==="object"){this._setObject(key)}else{if(typeof value==="function"&&key!=="clipTo"){this._set(key,value(this.get(key)))}else{this._set(key,value)}}return this},_set:function(key,value){var shouldConstrainValue=key==="scaleX"||key==="scaleY";if(shouldConstrainValue){value=this._constrainScale(value)}if(key==="scaleX"&&value<0){this.flipX=!this.flipX;value*=-1}else if(key==="scaleY"&&value<0){this.flipY=!this.flipY;value*=-1}else if(key==="width"||key==="height"){this.minScaleLimit=toFixed(Math.min(.1,1/Math.max(this.width,this.height)),2)}else if(key==="shadow"&&value&&!(value instanceof fabric.Shadow)){value=new fabric.Shadow(value)}this[key]=value;return this},toggle:function(property){var value=this.get(property);if(typeof value==="boolean"){this.set(property,!value)}return this},setSourcePath:function(value){this.sourcePath=value;return this},getViewportTransform:function(){if(this.canvas&&this.canvas.viewportTransform)return this.canvas.viewportTransform;return[1,0,0,1,0,0]},render:function(ctx,noTransform){if(this.width===0||this.height===0||!this.visible)return;ctx.save();this._setupFillRule(ctx);this._transform(ctx,noTransform);this._setStrokeStyles(ctx);this._setFillStyles(ctx);var m=this.transformMatrix;if(m&&this.group){ctx.translate(-this.group.width/2,-this.group.height/2);ctx.transform(m[0],m[1],m[2],m[3],m[4],m[5])}this._setShadow(ctx);this.clipTo&&fabric.util.clipContext(this,ctx);this._render(ctx,noTransform);this.clipTo&&ctx.restore();this._removeShadow(ctx);this._restoreFillRule(ctx);ctx.restore()},_transform:function(ctx,noTransform){var m=this.transformMatrix;if(m&&!this.group){ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5])}if(!noTransform){this.transform(ctx)}},_setStrokeStyles:function(ctx){if(this.stroke){ctx.lineWidth=this.strokeWidth;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin;ctx.miterLimit=this.strokeMiterLimit;ctx.strokeStyle=this.stroke.toLive?this.stroke.toLive(ctx):this.stroke}},_setFillStyles:function(ctx){if(this.fill){ctx.fillStyle=this.fill.toLive?this.fill.toLive(ctx):this.fill}},_renderControls:function(ctx,noTransform){var v=this.getViewportTransform();ctx.save();if(this.active&&!noTransform){var center;if(this.group){center=fabric.util.transformPoint(this.group.getCenterPoint(),v);ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.group.angle))}center=fabric.util.transformPoint(this.getCenterPoint(),v,null!=this.group);if(this.group){center.x*=this.group.scaleX;center.y*=this.group.scaleY}ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));this.drawBorders(ctx);this.drawControls(ctx)}ctx.restore()},_setShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0},_renderFill:function(ctx){if(!this.fill)return;if(this.fill.toLive){ctx.save();ctx.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)}if(this.fillRule==="destination-over"){ctx.fill("evenodd")}else{ctx.fill()}if(this.fill.toLive){ctx.restore()}if(this.shadow&&!this.shadow.affectStroke){this._removeShadow(ctx)}},_renderStroke:function(ctx){if(!this.stroke)return;ctx.save();if(this.strokeDashArray){if(1&this.strokeDashArray.length){this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray)}if(supportsLineDash){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);ctx.restore()},clone:function(callback,propertiesToInclude){if(this.constructor.fromObject){return this.constructor.fromObject(this.toObject(propertiesToInclude),callback)}return new fabric.Object(this.toObject(propertiesToInclude))},cloneAsImage:function(callback){var dataUrl=this.toDataURL();fabric.util.loadImage(dataUrl,function(img){if(callback){callback(new fabric.Image(img))}});return this},toDataURL:function(options){options||(options={});var el=fabric.util.createCanvasElement(),boundingRect=this.getBoundingRect();el.width=boundingRect.width;el.height=boundingRect.height;fabric.util.wrapElement(el,"div");var canvas=new fabric.Canvas(el);if(options.format==="jpg"){options.format="jpeg"}if(options.format==="jpeg"){canvas.backgroundColor="#fff"}var origParams={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",false);this.setPositionByOrigin(new fabric.Point(el.width/2,el.height/2),"center","center");var originalCanvas=this.canvas;canvas.add(this);var data=canvas.toDataURL(options);this.set(origParams).setCoords();this.canvas=originalCanvas;canvas.dispose();canvas=null;return data},isType:function(type){return this.type===type},complexity:function(){return 0},toJSON:function(propertiesToInclude){return this.toObject(propertiesToInclude)},setGradient:function(property,options){options||(options={});var gradient={colorStops:[]};gradient.type=options.type||(options.r1||options.r2?"radial":"linear");gradient.coords={x1:options.x1,y1:options.y1,x2:options.x2,y2:options.y2};if(options.r1||options.r2){gradient.coords.r1=options.r1;gradient.coords.r2=options.r2}for(var position in options.colorStops){var color=new fabric.Color(options.colorStops[position]);gradient.colorStops.push({offset:position,color:color.toRgb(),opacity:color.getAlpha()})}return this.set(property,fabric.Gradient.forObject(this,gradient))},setPatternFill:function(options){return this.set("fill",new fabric.Pattern(options))},setShadow:function(options){return this.set("shadow",new fabric.Shadow(options))},setColor:function(color){this.set("fill",color);return this},setAngle:function(angle){var shouldCenterOrigin=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;if(shouldCenterOrigin){this._setOriginToCenter()}this.set("angle",angle);if(shouldCenterOrigin){this._resetOrigin()}return this},centerH:function(){this.canvas.centerObjectH(this);return this},centerV:function(){this.canvas.centerObjectV(this);return this},center:function(){this.canvas.centerObject(this);return this},remove:function(){this.canvas.remove(this);return this},getLocalPointer:function(e,pointer){pointer=pointer||this.canvas.getPointer(e);var objectLeftTop=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:pointer.x-objectLeftTop.x,y:pointer.y-objectLeftTop.y}},_setupFillRule:function(ctx){if(this.fillRule){this._prevFillRule=ctx.globalCompositeOperation;ctx.globalCompositeOperation=this.fillRule}},_restoreFillRule:function(ctx){if(this.fillRule&&this._prevFillRule){ctx.globalCompositeOperation=this._prevFillRule}}});fabric.util.createAccessors(fabric.Object);fabric.Object.prototype.rotate=fabric.Object.prototype.setAngle;extend(fabric.Object.prototype,fabric.Observable);fabric.Object.NUM_FRACTION_DIGITS=2;fabric.Object.__uid=0})(typeof exports!=="undefined"?exports:this);(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(point,originX,originY){var cx=point.x,cy=point.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){cx=point.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){cx=point.x-(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){cy=point.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){cy=point.y-(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(cx,cy),point,degreesToRadians(this.angle))},translateToOriginPoint:function(center,originX,originY){var x=center.x,y=center.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(x,y),center,degreesToRadians(this.angle))},getCenterPoint:function(){var leftTop=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(leftTop,this.originX,this.originY)},getPointByOrigin:function(originX,originY){var center=this.getCenterPoint();return this.translateToOriginPoint(center,originX,originY)},toLocalPoint:function(point,originX,originY){var center=this.getCenterPoint(),strokeWidth=this.stroke?this.strokeWidth:0,x,y;if(originX&&originY){if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else{x=center.x}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else{y=center.y}}else{x=this.left;y=this.top}return fabric.util.rotatePoint(new fabric.Point(point.x,point.y),center,-degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x,y))},setPositionByOrigin:function(pos,originX,originY){var center=this.translateToCenterPoint(pos,originX,originY),position=this.translateToOriginPoint(center,this.originX,this.originY);this.set("left",position.x);this.set("top",position.y)},adjustPosition:function(to){var angle=degreesToRadians(this.angle),hypotHalf=this.getWidth()/2,xHalf=Math.cos(angle)*hypotHalf,yHalf=Math.sin(angle)*hypotHalf,hypotFull=this.getWidth(),xFull=Math.cos(angle)*hypotFull,yFull=Math.sin(angle)*hypotFull;if(this.originX==="center"&&to==="left"||this.originX==="right"&&to==="center"){this.left-=xHalf;this.top-=yHalf}else if(this.originX==="left"&&to==="center"||this.originX==="center"&&to==="right"){this.left+=xHalf;this.top+=yHalf}else if(this.originX==="left"&&to==="right"){this.left+=xFull;this.top+=yFull}else if(this.originX==="right"&&to==="left"){this.left-=xFull;this.top-=yFull}this.setCoords();this.originX=to},_setOriginToCenter:function(){this._originalOriginX=this.originX;this._originalOriginY=this.originY;var center=this.getCenterPoint();this.originX="center";this.originY="center";this.left=center.x;this.top=center.y},_resetOrigin:function(){var originPoint=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX;this.originY=this._originalOriginY;this.left=originPoint.x;this.top=originPoint.y;this._originalOriginX=null;this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})})();(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(pointTL,pointBR){var oCoords=this.oCoords,tl=new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr=new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl=new fabric.Point(oCoords.bl.x,oCoords.bl.y),br=new fabric.Point(oCoords.br.x,oCoords.br.y),intersection=fabric.Intersection.intersectPolygonRectangle([tl,tr,br,bl],pointTL,pointBR);return intersection.status==="Intersection"},intersectsWithObject:function(other){function getCoords(oCoords){return{tl:new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr:new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl:new fabric.Point(oCoords.bl.x,oCoords.bl.y),br:new fabric.Point(oCoords.br.x,oCoords.br.y)}}var thisCoords=getCoords(this.oCoords),otherCoords=getCoords(other.oCoords),intersection=fabric.Intersection.intersectPolygonPolygon([thisCoords.tl,thisCoords.tr,thisCoords.br,thisCoords.bl],[otherCoords.tl,otherCoords.tr,otherCoords.br,otherCoords.bl]);return intersection.status==="Intersection"},isContainedWithinObject:function(other){var boundingRect=other.getBoundingRect(),point1=new fabric.Point(boundingRect.left,boundingRect.top),point2=new fabric.Point(boundingRect.left+boundingRect.width,boundingRect.top+boundingRect.height);return this.isContainedWithinRect(point1,point2)},isContainedWithinRect:function(pointTL,pointBR){var boundingRect=this.getBoundingRect();return boundingRect.left>=pointTL.x&&boundingRect.left+boundingRect.width<=pointBR.x&&boundingRect.top>=pointTL.y&&boundingRect.top+boundingRect.height<=pointBR.y},containsPoint:function(point){var lines=this._getImageLines(this.oCoords),xPoints=this._findCrossPoints(point,lines);return xPoints!==0&&xPoints%2===1},_getImageLines:function(oCoords){return{topline:{o:oCoords.tl,d:oCoords.tr},rightline:{o:oCoords.tr,d:oCoords.br},bottomline:{o:oCoords.br,d:oCoords.bl},leftline:{o:oCoords.bl,d:oCoords.tl}}},_findCrossPoints:function(point,oCoords){var b1,b2,a1,a2,xi,yi,xcount=0,iLine;for(var lineKey in oCoords){iLine=oCoords[lineKey];if(iLine.o.y=point.y&&iLine.d.y>=point.y){continue}if(iLine.o.x===iLine.d.x&&iLine.o.x>=point.x){xi=iLine.o.x;yi=point.y}else{b1=0;b2=(iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);a1=point.y-b1*point.x;a2=iLine.o.y-b2*iLine.o.x;xi=-(a1-a2)/(b1-b2);yi=a1+b1*xi}if(xi>=point.x){xcount+=1}if(xcount===2){break}}return xcount},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var xCoords=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],minX=fabric.util.array.min(xCoords),maxX=fabric.util.array.max(xCoords),width=Math.abs(minX-maxX),yCoords=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],minY=fabric.util.array.min(yCoords),maxY=fabric.util.array.max(yCoords),height=Math.abs(minY-maxY);return{left:minX,top:minY,width:width,height:height}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(value){if(Math.abs(value)1?this.strokeWidth:0,theta=degreesToRadians(this.angle),vpt=this.getViewportTransform();var f=function(p){return fabric.util.transformPoint(p,vpt)};this.currentWidth=(this.width+strokeWidth)*this.scaleX;this.currentHeight=(this.height+strokeWidth)*this.scaleY;if(this.currentWidth<0){this.currentWidth=Math.abs(this.currentWidth)}var _hypotenuse=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),_angle=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),offsetX=Math.cos(_angle+theta)*_hypotenuse,offsetY=Math.sin(_angle+theta)*_hypotenuse,sinTh=Math.sin(theta),cosTh=Math.cos(theta),coords=this.getCenterPoint(),wh=new fabric.Point(this.currentWidth,this.currentHeight),_tl=new fabric.Point(coords.x-offsetX,coords.y-offsetY),_tr=new fabric.Point(_tl.x+wh.x*cosTh,_tl.y+wh.x*sinTh),_bl=new fabric.Point(_tl.x-wh.y*sinTh,_tl.y+wh.y*cosTh),_mt=new fabric.Point(_tl.x+wh.x/2*cosTh,_tl.y+wh.x/2*sinTh),tl=f(_tl),tr=f(_tr),br=f(new fabric.Point(_tr.x-wh.y*sinTh,_tr.y+wh.y*cosTh)),bl=f(_bl),ml=f(new fabric.Point(_tl.x-wh.y/2*sinTh,_tl.y+wh.y/2*cosTh)),mt=f(_mt),mr=f(new fabric.Point(_tr.x-wh.y/2*sinTh,_tr.y+wh.y/2*cosTh)),mb=f(new fabric.Point(_bl.x+wh.x/2*cosTh,_bl.y+wh.x/2*sinTh)),mtr=f(new fabric.Point(_mt.x,_mt.y));var padX=Math.cos(_angle+theta)*this.padding*Math.sqrt(2),padY=Math.sin(_angle+theta)*this.padding*Math.sqrt(2);tl=tl.add(new fabric.Point(-padX,-padY));tr=tr.add(new fabric.Point(padY,-padX));br=br.add(new fabric.Point(padX,padY));bl=bl.add(new fabric.Point(-padY,padX));ml=ml.add(new fabric.Point((-padX-padY)/2,(-padY+padX)/2));mt=mt.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));mr=mr.add(new fabric.Point((padY+padX)/2,(padY-padX)/2));mb=mb.add(new fabric.Point((padX-padY)/2,(padX+padY)/2));mtr=mtr.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));this.oCoords={tl:tl,tr:tr,br:br,bl:bl,ml:ml,mt:mt,mr:mr,mb:mb,mtr:mtr};this._setCornerCoords&&this._setCornerCoords();return this}})})();fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){if(this.group){fabric.StaticCanvas.prototype.sendToBack.call(this.group,this)}else{this.canvas.sendToBack(this)}return this},bringToFront:function(){if(this.group){fabric.StaticCanvas.prototype.bringToFront.call(this.group,this)}else{this.canvas.bringToFront(this)}return this},sendBackwards:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,intersecting)}else{this.canvas.sendBackwards(this,intersecting)}return this},bringForward:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.bringForward.call(this.group,this,intersecting)}else{this.canvas.bringForward(this,intersecting)}return this},moveTo:function(index){if(this.group){fabric.StaticCanvas.prototype.moveTo.call(this.group,this,index)}else{this.canvas.moveTo(this,index)}return this}});fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var fill=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",stroke=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",strokeWidth=this.strokeWidth?this.strokeWidth:"0",strokeDashArray=this.strokeDashArray?this.strokeDashArray.join(" "):"",strokeLineCap=this.strokeLineCap?this.strokeLineCap:"butt",strokeLineJoin=this.strokeLineJoin?this.strokeLineJoin:"miter",strokeMiterLimit=this.strokeMiterLimit?this.strokeMiterLimit:"4",opacity=typeof this.opacity!=="undefined"?this.opacity:"1",visibility=this.visible?"":" visibility: hidden;",filter=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",stroke,"; ","stroke-width: ",strokeWidth,"; ","stroke-dasharray: ",strokeDashArray,"; ","stroke-linecap: ",strokeLineCap,"; ","stroke-linejoin: ",strokeLineJoin,"; ","stroke-miterlimit: ",strokeMiterLimit,"; ","fill: ",fill,"; ","opacity: ",opacity,";",filter,visibility].join("")},getSvgTransform:function(){var toFixed=fabric.util.toFixed,angle=this.getAngle(),center=this.getCenterPoint(),NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,translatePart="translate("+toFixed(center.x,NUM_FRACTION_DIGITS)+" "+toFixed(center.y,NUM_FRACTION_DIGITS)+")",anglePart=angle!==0?" rotate("+toFixed(angle,NUM_FRACTION_DIGITS)+")":"",scalePart=this.scaleX===1&&this.scaleY===1?"":" scale("+toFixed(this.scaleX,NUM_FRACTION_DIGITS)+" "+toFixed(this.scaleY,NUM_FRACTION_DIGITS)+")",flipXPart=this.flipX?"matrix(-1 0 0 1 0 0) ":"",flipYPart=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[translatePart,anglePart,scalePart,flipXPart,flipYPart].join("")},_createBaseSVGMarkup:function(){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))}if(this.shadow){markup.push(this.shadow.toSVG(this))}return markup}});fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(prop){return this.get(prop)!==this.originalState[prop]},this)},saveState:function(options){this.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this);if(options&&options.stateProperties){options.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this)}return this},setupState:function(){this.originalState={};this.saveState();return this}});(function(){var degreesToRadians=fabric.util.degreesToRadians,isVML=typeof G_vmlCanvasManager!=="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(pointer){if(!this.hasControls||!this.active)return false;var ex=pointer.x,ey=pointer.y,xPoints,lines;for(var i in this.oCoords){if(!this.isControlVisible(i)){continue}if(i==="mtr"&&!this.hasRotatingPoint){continue}if(this.get("lockUniScaling")&&(i==="mt"||i==="mr"||i==="mb"||i==="ml")){continue}lines=this._getImageLines(this.oCoords[i].corner);xPoints=this._findCrossPoints({x:ex,y:ey},lines);if(xPoints!==0&&xPoints%2===1){this.__corner=i;return i}}return false},_setCornerCoords:function(){var coords=this.oCoords,theta=degreesToRadians(this.angle),newTheta=degreesToRadians(45-this.angle),cornerHypotenuse=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,cosHalfOffset=cornerHypotenuse*Math.cos(newTheta),sinHalfOffset=cornerHypotenuse*Math.sin(newTheta),sinTh=Math.sin(theta),cosTh=Math.cos(theta);coords.tl.corner={tl:{x:coords.tl.x-sinHalfOffset,y:coords.tl.y-cosHalfOffset},tr:{x:coords.tl.x+cosHalfOffset,y:coords.tl.y-sinHalfOffset},bl:{x:coords.tl.x-cosHalfOffset,y:coords.tl.y+sinHalfOffset},br:{x:coords.tl.x+sinHalfOffset,y:coords.tl.y+cosHalfOffset}};coords.tr.corner={tl:{x:coords.tr.x-sinHalfOffset,y:coords.tr.y-cosHalfOffset},tr:{x:coords.tr.x+cosHalfOffset,y:coords.tr.y-sinHalfOffset},br:{x:coords.tr.x+sinHalfOffset,y:coords.tr.y+cosHalfOffset},bl:{x:coords.tr.x-cosHalfOffset,y:coords.tr.y+sinHalfOffset}};coords.bl.corner={tl:{x:coords.bl.x-sinHalfOffset,y:coords.bl.y-cosHalfOffset},bl:{x:coords.bl.x-cosHalfOffset,y:coords.bl.y+sinHalfOffset},br:{x:coords.bl.x+sinHalfOffset,y:coords.bl.y+cosHalfOffset},tr:{x:coords.bl.x+cosHalfOffset,y:coords.bl.y-sinHalfOffset}};coords.br.corner={tr:{x:coords.br.x+cosHalfOffset,y:coords.br.y-sinHalfOffset},bl:{x:coords.br.x-cosHalfOffset,y:coords.br.y+sinHalfOffset},br:{x:coords.br.x+sinHalfOffset,y:coords.br.y+cosHalfOffset},tl:{x:coords.br.x-sinHalfOffset,y:coords.br.y-cosHalfOffset}};coords.ml.corner={tl:{x:coords.ml.x-sinHalfOffset,y:coords.ml.y-cosHalfOffset},tr:{x:coords.ml.x+cosHalfOffset,y:coords.ml.y-sinHalfOffset},bl:{x:coords.ml.x-cosHalfOffset,y:coords.ml.y+sinHalfOffset},br:{x:coords.ml.x+sinHalfOffset,y:coords.ml.y+cosHalfOffset}};coords.mt.corner={tl:{x:coords.mt.x-sinHalfOffset,y:coords.mt.y-cosHalfOffset},tr:{x:coords.mt.x+cosHalfOffset,y:coords.mt.y-sinHalfOffset},bl:{x:coords.mt.x-cosHalfOffset,y:coords.mt.y+sinHalfOffset},br:{x:coords.mt.x+sinHalfOffset,y:coords.mt.y+cosHalfOffset}};coords.mr.corner={tl:{x:coords.mr.x-sinHalfOffset,y:coords.mr.y-cosHalfOffset},tr:{x:coords.mr.x+cosHalfOffset,y:coords.mr.y-sinHalfOffset},bl:{x:coords.mr.x-cosHalfOffset,y:coords.mr.y+sinHalfOffset},br:{x:coords.mr.x+sinHalfOffset,y:coords.mr.y+cosHalfOffset}};coords.mb.corner={tl:{x:coords.mb.x-sinHalfOffset,y:coords.mb.y-cosHalfOffset},tr:{x:coords.mb.x+cosHalfOffset,y:coords.mb.y-sinHalfOffset},bl:{x:coords.mb.x-cosHalfOffset,y:coords.mb.y+sinHalfOffset},br:{x:coords.mb.x+sinHalfOffset,y:coords.mb.y+cosHalfOffset}};coords.mtr.corner={tl:{x:coords.mtr.x-sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-cosHalfOffset-cosTh*this.rotatingPointOffset},tr:{x:coords.mtr.x+cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-sinHalfOffset-cosTh*this.rotatingPointOffset},bl:{x:coords.mtr.x-cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+sinHalfOffset-cosTh*this.rotatingPointOffset},br:{x:coords.mtr.x+sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+cosHalfOffset-cosTh*this.rotatingPointOffset}}},drawBorders:function(ctx){if(!this.hasBorders)return this;var padding=this.padding,padding2=padding*2,strokeWidth=~~(this.strokeWidth/2)*2; +ctx.save();ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=this.borderColor;var scaleX=1/this._constrainScale(this.scaleX),scaleY=1/this._constrainScale(this.scaleY);ctx.lineWidth=1/this.borderScaleFactor;var vpt=this.getViewportTransform(),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),vpt,true),sxy=fabric.util.transformPoint(new fabric.Point(scaleX,scaleY),vpt,true),w=wh.x,h=wh.y,sx=sxy.x,sy=sxy.y;if(this.group){w=w*this.group.scaleX;h=h*this.group.scaleY}ctx.strokeRect(~~(-(w/2)-padding-strokeWidth/2*sx)-.5,~~(-(h/2)-padding-strokeWidth/2*sy)-.5,~~(w+padding2+strokeWidth*sx)+1,~~(h+padding2+strokeWidth*sy)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var rotateHeight=(this.flipY?h+strokeWidth*sx+padding*2:-h-strokeWidth*sy-padding*2)/2;ctx.beginPath();ctx.moveTo(0,rotateHeight);ctx.lineTo(0,rotateHeight+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset));ctx.closePath();ctx.stroke()}ctx.restore();return this},drawControls:function(ctx){if(!this.hasControls)return this;var size=this.cornerSize,size2=size/2,strokeWidth2=~~(this.strokeWidth/2),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),this.getViewportTransform(),true),width=wh.x,height=wh.y,left=-(width/2),top=-(height/2),padding=this.padding,scaleOffset=size2,scaleOffsetSize=size2-size,methodName=this.transparentCorners?"strokeRect":"fillRect";ctx.save();ctx.lineWidth=1;ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=ctx.fillStyle=this.cornerColor;this._drawControl("tl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("tr",ctx,methodName,left+width-scaleOffset+strokeWidth2+padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("bl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("br",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height+scaleOffsetSize+strokeWidth2+padding);if(!this.get("lockUniScaling")){this._drawControl("mt",ctx,methodName,left+width/2-scaleOffset,top-scaleOffset-strokeWidth2-padding);this._drawControl("mb",ctx,methodName,left+width/2-scaleOffset,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("mr",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height/2-scaleOffset);this._drawControl("ml",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height/2-scaleOffset)}if(this.hasRotatingPoint){this._drawControl("mtr",ctx,methodName,left+width/2-scaleOffset,this.flipY?top+height+this.rotatingPointOffset-this.cornerSize/2+strokeWidth2+padding:top-this.rotatingPointOffset-this.cornerSize/2-strokeWidth2-padding)}ctx.restore();return this},_drawControl:function(control,ctx,methodName,left,top){var size=this.cornerSize;if(this.isControlVisible(control)){isVML||this.transparentCorners||ctx.clearRect(left,top,size,size);ctx[methodName](left,top,size,size)}},isControlVisible:function(controlName){return this._getControlsVisibility()[controlName]},setControlVisible:function(controlName,visible){this._getControlsVisibility()[controlName]=visible;return this},setControlsVisibility:function(options){options||(options={});for(var p in options){this.setControlVisible(p,options[p])}return this},_getControlsVisibility:function(){if(!this._controlsVisibility){this._controlsVisibility={tl:true,tr:true,br:true,bl:true,ml:true,mt:true,mr:true,mb:true,mtr:true}}return this._controlsVisibility}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(value){object.set("left",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxCenterObjectV:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(value){object.set("top",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxRemove:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){object.set("active",false)},onChange:function(value){object.set("opacity",value);_this.renderAll();onChange()},onComplete:function(){_this.remove(object);onComplete()}});return this}});fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]==="object"){var propsToAnimate=[],prop,skipCallbacks;for(prop in arguments[0]){propsToAnimate.push(prop)}for(var i=0,len=propsToAnimate.length;i');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Line.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" "));fabric.Line.fromElement=function(element,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Line.ATTRIBUTE_NAMES),points=[parsedAttributes.x1||0,parsedAttributes.y1||0,parsedAttributes.x2||0,parsedAttributes.y2||0];return new fabric.Line(points,extend(parsedAttributes,options))};fabric.Line.fromObject=function(object){var points=[object.x1,object.y1,object.x2,object.y2];return new fabric.Line(points,object)};function makeEdgeToOriginGetter(propertyNames,originValues){var origin=propertyNames.origin,axis1=propertyNames.axis1,axis2=propertyNames.axis2,dimension=propertyNames.dimension,nearest=originValues.nearest,center=originValues.center,farthest=originValues.farthest;return function(){switch(this.get(origin)){case nearest:return Math.min(this.get(axis1),this.get(axis2));case center:return Math.min(this.get(axis1),this.get(axis2))+.5*this.get(dimension);case farthest:return Math.max(this.get(axis1),this.get(axis2))}}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Circle){fabric.warn("fabric.Circle is already defined.");return}fabric.Circle=fabric.util.createClass(fabric.Object,{type:"circle",radius:0,initialize:function(options){options=options||{};this.set("radius",options.radius||0);this.callSuper("initialize",options)},_set:function(key,value){this.callSuper("_set",key,value);if(key==="radius"){this.setRadius(value)}return this},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{radius:this.get("radius")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx,noTransform){ctx.beginPath();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.stroke&&this._renderStroke(ctx)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(value){this.radius=value;this.set("width",value*2).set("height",value*2)},complexity:function(){return 1}});fabric.Circle.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" "));fabric.Circle.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Circle.ATTRIBUTE_NAMES);if(!isValidRadius(parsedAttributes)){throw new Error("value of `r` attribute is required and can not be negative")}if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var obj=new fabric.Circle(extend(parsedAttributes,options));obj.cx=parseFloat(element.getAttribute("cx"))||0;obj.cy=parseFloat(element.getAttribute("cy"))||0;return obj};function isValidRadius(attributes){return"radius"in attributes&&attributes.radius>0}fabric.Circle.fromObject=function(object){return new fabric.Circle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Triangle){fabric.warn("fabric.Triangle is already defined");return}fabric.Triangle=fabric.util.createClass(fabric.Object,{type:"triangle",initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("width",options.width||100).set("height",options.height||100)},_render:function(ctx){var widthBy2=this.width/2,heightBy2=this.height/2;ctx.beginPath();ctx.moveTo(-widthBy2,heightBy2);ctx.lineTo(0,-heightBy2);ctx.lineTo(widthBy2,heightBy2);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_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()},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),widthBy2=this.width/2,heightBy2=this.height/2,points=[-widthBy2+" "+heightBy2,"0 "+-heightBy2,widthBy2+" "+heightBy2].join(",");markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Triangle.fromObject=function(object){return new fabric.Triangle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Ellipse){fabric.warn("fabric.Ellipse is already defined.");return}fabric.Ellipse=fabric.util.createClass(fabric.Object,{type:"ellipse",rx:0,ry:0,initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("rx",options.rx||0);this.set("ry",options.ry||0);this.set("width",this.get("rx")*2);this.set("height",this.get("ry")*2)},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},render:function(ctx,noTransform){if(this.rx===0||this.ry===0)return;return this.callSuper("render",ctx,noTransform)},_render:function(ctx,noTransform){ctx.beginPath();ctx.save();ctx.globalAlpha=this.group?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&this.group){ctx.translate(this.cx,this.cy)}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);ctx.restore();this._renderFill(ctx);this._renderStroke(ctx)},complexity:function(){return 1}});fabric.Ellipse.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" "));fabric.Ellipse.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Ellipse.ATTRIBUTE_NAMES),cx=parsedAttributes.left,cy=parsedAttributes.top;if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var ellipse=new fabric.Ellipse(extend(parsedAttributes,options));ellipse.cx=cx||0;ellipse.cy=cy||0;return ellipse};fabric.Ellipse.fromObject=function(object){return new fabric.Ellipse(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;if(fabric.Rect){console.warn("fabric.Rect is already defined");return}var stateProperties=fabric.Object.prototype.stateProperties.concat();stateProperties.push("rx","ry","x","y");fabric.Rect=fabric.util.createClass(fabric.Object,{stateProperties:stateProperties,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(options){options=options||{};this.callSuper("initialize",options);this._initRxRy();this.x=options.x||0;this.y=options.y||0},_initRxRy:function(){if(this.rx&&!this.ry){this.ry=this.rx}else if(this.ry&&!this.rx){this.rx=this.ry}},_render:function(ctx){if(this.width===1&&this.height===1){ctx.fillRect(0,0,1,1);return}var rx=this.rx?Math.min(this.rx,this.width/2):0,ry=this.ry?Math.min(this.ry,this.height/2):0,w=this.width,h=this.height,x=-w/2,y=-h/2,isInPathGroup=this.group&&this.group.type==="path-group",isRounded=rx!==0||ry!==0,k=1-.5522847498;ctx.beginPath();ctx.globalAlpha=isInPathGroup?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&isInPathGroup){ctx.translate(this.width/2+this.x,this.height/2+this.y)}if(!this.transformMatrix&&isInPathGroup){ctx.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y)}ctx.moveTo(x+rx,y);ctx.lineTo(x+w-rx,y);isRounded&&ctx.bezierCurveTo(x+w-k*rx,y,x+w,y+k*ry,x+w,y+ry);ctx.lineTo(x+w,y+h-ry);isRounded&&ctx.bezierCurveTo(x+w,y+h-k*ry,x+w-k*rx,y+h,x+w-rx,y+h);ctx.lineTo(x+rx,y+h);isRounded&&ctx.bezierCurveTo(x+k*rx,y+h,x,y+h-k*ry,x,y+h-ry);ctx.lineTo(x,y+ry);isRounded&&ctx.bezierCurveTo(x,y+k*ry,x+k*rx,y,x+rx,y);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var x=-this.width/2,y=-this.height/2,w=this.width,h=this.height;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()},_normalizeLeftTopProperties:function(parsedAttributes){if("left"in parsedAttributes){this.set("left",parsedAttributes.left+this.getWidth()/2)}this.set("x",parsedAttributes.left||0);if("top"in parsedAttributes){this.set("top",parsedAttributes.top+this.getHeight()/2)}this.set("y",parsedAttributes.top||0);return this},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Rect.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" "));function _setDefaultLeftTopValues(attributes){attributes.left=attributes.left||0;attributes.top=attributes.top||0;return attributes}fabric.Rect.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Rect.ATTRIBUTE_NAMES);parsedAttributes=_setDefaultLeftTopValues(parsedAttributes);var rect=new fabric.Rect(extend(options?fabric.util.object.clone(options):{},parsedAttributes));rect._normalizeLeftTopProperties(parsedAttributes);return rect};fabric.Rect.fromObject=function(object){return new fabric.Rect(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),toFixed=fabric.util.toFixed;if(fabric.Polyline){fabric.warn("fabric.Polyline is already defined");return}fabric.Polyline=fabric.util.createClass(fabric.Object,{type:"polyline",points:null,initialize:function(points,options,skipOffset){options=options||{};this.set("points",points);this.callSuper("initialize",options);this._calcDimensions(skipOffset)},_calcDimensions:function(skipOffset){return fabric.Polygon.prototype._calcDimensions.call(this,skipOffset)},toObject:function(propertiesToInclude){return fabric.Polygon.prototype.toObject.call(this,propertiesToInclude)},toSVG:function(reviver){var points=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i"},toObject:function(propertiesToInclude){var o=extend(this.callSuper("toObject",propertiesToInclude),{path:this.path.map(function(item){return item.slice()}),pathOffset:this.pathOffset});if(this.sourcePath){o.sourcePath=this.sourcePath}if(this.transformMatrix){o.transformMatrix=this.transformMatrix}return o},toDatalessObject:function(propertiesToInclude){var o=this.toObject(propertiesToInclude);if(this.sourcePath){o.path=this.sourcePath}delete o.sourcePath;return o},toSVG:function(reviver){var chunks=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.path.length;i',"","");return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return this.path.length},_parsePath:function(){var result=[],coords=[],currentPath,parsed,re=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,match,coordsStr;for(var i=0,coordsParsed,len=this.path.length;icommandLength){for(var k=1,klen=coordsParsed.length;k"];for(var i=0,len=objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},toString:function(){return"#"},isSameColor:function(){var firstPathFill=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(path){return(path.get("fill")||"").toLowerCase()===firstPathFill})},complexity:function(){return this.paths.reduce(function(total,path){return total+(path&&path.complexity?path.complexity():0)},0)},getObjects:function(){return this.paths}});fabric.PathGroup.fromObject=function(object,callback){if(typeof object.paths==="string"){fabric.loadSVGFromURL(object.paths,function(elements){var pathUrl=object.paths;delete object.paths;var pathGroup=fabric.util.groupSVGElements(elements,object,pathUrl);callback(pathGroup)})}else{fabric.util.enlivenObjects(object.paths,function(enlivenedObjects){delete object.paths;callback(new fabric.PathGroup(enlivenedObjects,object))})}};fabric.PathGroup.async=true})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,min=fabric.util.array.min,max=fabric.util.array.max,invoke=fabric.util.array.invoke,degreesToRadians=fabric.util.degreesToRadians;if(fabric.Group){return}var _lockProperties={lockMovementX:true,lockMovementY:true,lockRotation:true,lockScalingX:true,lockScalingY:true,lockUniScaling:true};fabric.Group=fabric.util.createClass(fabric.Object,fabric.Collection,{type:"group",initialize:function(objects,options){options=options||{};this._objects=objects||[];for(var i=this._objects.length;i--;){this._objects[i].group=this}this.originalState={};this.callSuper("initialize");if(options){extend(this,options)}this._setOpacityIfSame();this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(object){var objectLeft=object.getLeft(),objectTop=object.getTop();object.set({originalLeft:objectLeft,originalTop:objectTop,left:objectLeft-this.left,top:objectTop-this.top});object.setCoords();object.__origHasControls=object.hasControls;object.hasControls=false},toString:function(){return"#"},addWithUpdate:function(object){this._restoreObjectsState();this._objects.push(object);object.group=this;this.forEachObject(this._setObjectActive,this);this._calcBounds();this._updateObjectsCoords();return this},_setObjectActive:function(object){object.set("active",true);object.group=this},removeWithUpdate:function(object){this._moveFlippedObject(object);this._restoreObjectsState();this.forEachObject(this._setObjectActive,this);this.remove(object);this._calcBounds();this._updateObjectsCoords();return this},_onObjectAdded:function(object){object.group=this},_onObjectRemoved:function(object){delete object.group;object.set("active",false)},delegatedProperties:{fill:true,opacity:true,fontFamily:true,fontWeight:true,fontSize:true,fontStyle:true,lineHeight:true,textDecoration:true,textAlign:true,backgroundColor:true},_set:function(key,value){if(key in this.delegatedProperties){var i=this._objects.length;this[key]=value;while(i--){this._objects[i].set(key,value)}}else{this[key]=value}},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{objects:invoke(this._objects,"toObject",propertiesToInclude)})},render:function(ctx,noTransform){if(!this.visible)return;ctx.save();this.clipTo&&fabric.util.clipContext(this,ctx);for(var i=0,len=this._objects.length;i'];for(var i=0,len=this._objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},get:function(prop){if(prop in _lockProperties){if(this[prop]){return this[prop]}else{for(var i=0,len=this._objects.length;i','");if(this.stroke||this.strokeDashArray){var origFill=this.fill;this.fill=null;markup.push("');this.fill=origFill}markup.push("");return reviver?reviver(markup.join("")):markup.join("")},getSrc:function(){if(this.getElement()){return this.getElement().src||this.getElement()._src}},toString:function(){return'#'},clone:function(callback,propertiesToInclude){this.constructor.fromObject(this.toObject(propertiesToInclude),callback)},applyFilters:function(callback){if(!this._originalElement){return}if(this.filters.length===0){this._element=this._originalElement;callback&&callback();return}var imgEl=this._originalElement,canvasEl=fabric.util.createCanvasElement(),replacement=fabric.util.createImage(),_this=this;canvasEl.width=imgEl.width;canvasEl.height=imgEl.height;canvasEl.getContext("2d").drawImage(imgEl,0,0,imgEl.width,imgEl.height);this.filters.forEach(function(filter){filter&&filter.applyTo(canvasEl)});replacement.width=imgEl.width;replacement.height=imgEl.height;if(fabric.isLikelyNode){replacement.src=canvasEl.toBuffer(undefined,fabric.Image.pngCompression);_this._element=replacement;callback&&callback()}else{replacement.onload=function(){_this._element=replacement;callback&&callback();replacement.onload=canvasEl=imgEl=null};replacement.src=canvasEl.toDataURL("image/png")}return this},_render:function(ctx){this._element&&ctx.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var element=this.getElement();this.set("width",element.width);this.set("height",element.height)},_initElement:function(element){this.setElement(fabric.util.getById(element));fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(options){options||(options={});this.setOptions(options);this._setWidthHeight(options);if(this._element&&this.crossOrigin){this._element.crossOrigin=this.crossOrigin}},_initFilters:function(object,callback){if(object.filters&&object.filters.length){fabric.util.enlivenObjects(object.filters,function(enlivenedObjects){callback&&callback(enlivenedObjects)},"fabric.Image.filters")}else{callback&&callback()}},_setWidthHeight:function(options){this.width="width"in options?options.width:this.getElement()?this.getElement().width||0:0;this.height="height"in options?options.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}});fabric.Image.CSS_CANVAS="canvas-img";fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc;fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){fabric.Image.prototype._initFilters.call(object,object,function(filters){object.filters=filters||[];var instance=new fabric.Image(img,object);callback&&callback(instance)})},null,object.crossOrigin)};fabric.Image.fromURL=function(url,callback,imgOptions){fabric.util.loadImage(url,function(img){callback(new fabric.Image(img,imgOptions))},null,imgOptions&&imgOptions.crossOrigin)};fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" "));fabric.Image.fromElement=function(element,callback,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(parsedAttributes["xlink:href"],callback,extend(options?fabric.util.object.clone(options):{},parsedAttributes))};fabric.Image.async=true;fabric.Image.pngCompression=1})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var angle=this.getAngle()%360;if(angle>0){return Math.round((angle-1)/90)*90}return Math.round(angle/90)*90},straighten:function(){this.setAngle(this._getAngleValueForStraighten());return this},fxStraighten:function(callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(value){_this.setAngle(value);onChange()},onComplete:function(){_this.setCoords();onComplete()},onStart:function(){_this.set("active",false)}});return this}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(object){object.straighten();this.renderAll();return this},fxStraightenObject:function(object){object.fxStraighten({onChange:this.renderAll.bind(this)});return this}});fabric.Image.filters=fabric.Image.filters||{};fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.Brightness=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"Brightness",initialize:function(options){options=options||{};this.brightness=options.brightness||0},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,brightness=this.brightness;for(var i=0,len=data.length;ish||scx<0||scx>sw)continue;var srcOff=(scy*sw+scx)*4,wt=weights[cy*side+cx];r+=src[srcOff]*wt;g+=src[srcOff+1]*wt;b+=src[srcOff+2]*wt;a+=src[srcOff+3]*wt}}dst[dstOff]=r;dst[dstOff+1]=g;dst[dstOff+2]=b;dst[dstOff+3]=a+alphaFac*(255-a)}}context.putImageData(output,0,0)},toObject:function(){return extend(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}});fabric.Image.filters.Convolute.fromObject=function(object){return new fabric.Image.filters.Convolute(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.GradientTransparency=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(options){options=options||{};this.threshold=options.threshold||100},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,threshold=this.threshold,total=data.length;for(var i=0,len=data.length;i-1?options.channel:0},applyTo:function(canvasEl){if(!this.mask)return;var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,maskEl=this.mask.getElement(),maskCanvasEl=fabric.util.createCanvasElement(),channel=this.channel,i,iLen=imageData.width*imageData.height*4;maskCanvasEl.width=maskEl.width;maskCanvasEl.height=maskEl.height;maskCanvasEl.getContext("2d").drawImage(maskEl,0,0,maskEl.width,maskEl.height);var maskImageData=maskCanvasEl.getContext("2d").getImageData(0,0,maskEl.width,maskEl.height),maskData=maskImageData.data;for(i=0;ilimit&&g>limit&&b>limit&&abs(r-g)'},_render:function(ctx){var isInPathGroup=this.group&&this.group.type==="path-group";if(isInPathGroup&&!this.transformMatrix){ctx.translate(-this.group.width/2+this.left,-this.group.height/2+this.top)}else if(isInPathGroup&&this.transformMatrix){ctx.translate(-this.group.width/2,-this.group.height/2)}if(typeof Cufon==="undefined"||this.useNative===true){this._renderViaNative(ctx)}else{this._renderViaCufon(ctx)}},_renderViaNative:function(ctx){var textLines=this.text.split(this._reNewline);this.transform(ctx,fabric.isLikelyNode);this._setTextStyles(ctx);this.width=this._getTextWidth(ctx,textLines);this.height=this._getTextHeight(ctx,textLines);this.clipTo&&fabric.util.clipContext(this,ctx);this._renderTextBackground(ctx,textLines);this._translateForTextAlign(ctx);this._renderText(ctx,textLines);if(this.textAlign!=="left"&&this.textAlign!=="justify"){ctx.restore()}this._renderTextDecoration(ctx,textLines);this.clipTo&&ctx.restore();this._setBoundaries(ctx,textLines);this._totalLineHeight=0},_renderText:function(ctx,textLines){ctx.save();this._setShadow(ctx);this._renderTextFill(ctx,textLines);this._renderTextStroke(ctx,textLines);this._removeShadow(ctx);ctx.restore()},_translateForTextAlign:function(ctx){if(this.textAlign!=="left"&&this.textAlign!=="justify"){ctx.save();ctx.translate(this.textAlign==="center"?this.width/2:this.width,0)}},_setBoundaries:function(ctx,textLines){this._boundaries=[];for(var i=0,len=textLines.length;imaxWidth){maxWidth=currentLineWidth}}return maxWidth},_renderChars:function(method,ctx,chars,left,top){ctx[method](chars,left,top)},_renderTextLine:function(method,ctx,line,left,top,lineIndex){top-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(method,ctx,line,left,top,lineIndex);return}var lineWidth=ctx.measureText(line).width,totalWidth=this.width;if(totalWidth>lineWidth){var words=line.split(/\s+/),wordsWidth=ctx.measureText(line.replace(/\s+/g,"")).width,widthDiff=totalWidth-wordsWidth,numSpaces=words.length-1,spaceWidth=widthDiff/numSpaces,leftOffset=0;for(var i=0,len=words.length;i-1){renderLinesAtOffset(this.fontSize*this.lineHeight)}if(this.textDecoration.indexOf("line-through")>-1){renderLinesAtOffset(this.fontSize*this.lineHeight-this.fontSize/2)}if(this.textDecoration.indexOf("overline")>-1){renderLinesAtOffset(this.fontSize*this.lineHeight-this.fontSize)}},_getFontDeclaration:function(){return[fabric.isLikelyNode?this.fontWeight:this.fontStyle,fabric.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",fabric.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(ctx,noTransform){if(!this.visible)return;ctx.save();var m=this.transformMatrix;if(m&&(!this.group||this.group.type==="path-group")){ctx.transform(m[0],m[1],m[2],m[3],m[4],m[5])}this._render(ctx);ctx.restore()},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=[],textLines=this.text.split(this._reNewline),offsets=this._getSVGLeftTopOffsets(textLines),textAndBg=this._getSVGTextAndBg(offsets.lineTop,offsets.textLeft,textLines),shadowSpans=this._getSVGShadows(offsets.lineTop,textLines);offsets.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0;this._wrapSVGTextAndBg(markup,textAndBg,shadowSpans,offsets);return reviver?reviver(markup.join("")):markup.join("")},_getSVGLeftTopOffsets:function(textLines){var lineTop=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,textLeft=-(this.width/2),textTop=this.useNative?this.fontSize-1:this.height/2-textLines.length*this.fontSize-this._totalLineHeight;return{textLeft:textLeft,textTop:textTop,lineTop:lineTop}},_wrapSVGTextAndBg:function(markup,textAndBg,shadowSpans,offsets){markup.push('',textAndBg.textBgRects.join(""),"',shadowSpans.join(""),textAndBg.textSpans.join(""),"","")},_getSVGShadows:function(lineHeight,textLines){var shadowSpans=[],i,len,lineTopOffsetMultiplier=1;if(!this.shadow||!this._boundaries){return shadowSpans}for(i=0,len=textLines.length;i",fabric.util.string.escapeXml(textLines[i]),"");lineTopOffsetMultiplier=1}else{lineTopOffsetMultiplier++}}return shadowSpans},_getSVGTextAndBg:function(lineHeight,textLeftOffset,textLines){var textSpans=[],textBgRects=[],lineTopOffsetMultiplier=1;this._setSVGBg(textBgRects);for(var i=0,len=textLines.length;i",fabric.util.string.escapeXml(textLine),"")},_setSVGTextLineBg:function(textBgRects,i,textLeftOffset,lineHeight){textBgRects.push("')},_setSVGBg:function(textBgRects){if(this.backgroundColor&&this._boundaries){textBgRects.push("')}},_getFillAttributes:function(value){var fillColor=value&&typeof value==="string"?new fabric.Color(value):"";if(!fillColor||!fillColor.getSource()||fillColor.getAlpha()===1){return'fill="'+value+'"'}return'opacity="'+fillColor.getAlpha()+'" fill="'+fillColor.setAlpha(1).toRgb()+'"'},_set:function(key,value){if(key==="fontFamily"&&this.path){this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+value+"$3")}this.callSuper("_set",key,value);if(key in this._dimensionAffectingProps){this._initDimensions();this.setCoords()}},complexity:function(){return 1}});fabric.Text.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" "));fabric.Text.DEFAULT_SVG_FONT_SIZE=16;fabric.Text.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Text.ATTRIBUTE_NAMES);options=fabric.util.object.extend(options?fabric.util.object.clone(options):{},parsedAttributes);if("dx"in parsedAttributes){options.left+=parsedAttributes.dx}if("dy"in parsedAttributes){options.top+=parsedAttributes.dy}if(!("fontSize"in options)){options.fontSize=fabric.Text.DEFAULT_SVG_FONT_SIZE}if(!options.originX){options.originX="center"}var text=new fabric.Text(element.textContent,options);text.set({left:text.getLeft()+text.getWidth()/2,top:text.getTop()-text.getHeight()/2});return text};fabric.Text.fromObject=function(object){return new fabric.Text(object.text,clone(object))};fabric.util.createAccessors(fabric.Text)})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Text.prototype,{_renderViaCufon:function(ctx){var o=Cufon.textOptions||(Cufon.textOptions={});o.left=this.left;o.top=this.top;o.context=ctx;o.color=this.fill;var el=this._initDummyElementForCufon();this.transform(ctx);Cufon.replaceElement(el,{engine:"canvas",separate:"none",fontFamily:this.fontFamily,fontWeight:this.fontWeight,textDecoration:this.textDecoration,textShadow:this.shadow&&this.shadow.toString(),textAlign:this.textAlign,fontStyle:this.fontStyle,lineHeight:this.lineHeight,stroke:this.stroke,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor});this.width=o.width;this.height=o.height;this._totalLineHeight=o.totalLineHeight;this._fontAscent=o.fontAscent;this._boundaries=o.boundaries;el=null;this.setCoords()},_initDummyElementForCufon:function(){var el=fabric.document.createElement("pre"),container=fabric.document.createElement("div");container.appendChild(el);if(typeof G_vmlCanvasManager==="undefined"){el.innerHTML=this.text}else{el.innerText=this.text.replace(/\r?\n/gi,"\r")}el.style.fontSize=this.fontSize+"px";el.style.letterSpacing="normal";return el}});(function(){var clone=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:false,editable:true,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:true,_skipFillStrokeCheck:true,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:false,_charWidthsCache:{},initialize:function(text,options){this.styles=options?options.styles||{}:{};this.callSuper("initialize",text,options);this.initBehavior();fabric.IText.instances.push(this);this.__lineWidths={};this.__lineHeights={};this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return true;var obj=this.styles;for(var p1 in obj){for(var p2 in obj[p1]){for(var p3 in obj[p1][p2]){return false}}}return true},setSelectionStart:function(index){if(this.selectionStart!==index){this.canvas&&this.canvas.fire("text:selection:changed",{target:this})}this.selectionStart=index;this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=index)},setSelectionEnd:function(index){if(this.selectionEnd!==index){this.canvas&&this.canvas.fire("text:selection:changed",{target:this})}this.selectionEnd=index;this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=index)},getSelectionStyles:function(startIndex,endIndex){if(arguments.length===2){var styles=[];for(var i=startIndex;i=start.charIndex&&(i!==endLine||jstartLine&&i-1){this._renderCharDecorationAtOffset(ctx,left,top+this.fontSize/this._fontSizeFraction,charWidth,0,this.fontSize/20)}if(textDecoration.indexOf("line-through")>-1){this._renderCharDecorationAtOffset(ctx,left,top+this.fontSize/this._fontSizeFraction,charWidth,charHeight/2,fontSize/20)}if(textDecoration.indexOf("overline")>-1){this._renderCharDecorationAtOffset(ctx,left,top,charWidth,lineHeight-this.fontSize/this._fontSizeFraction,this.fontSize/20)}},_renderCharDecorationAtOffset:function(ctx,left,top,charWidth,offset,thickness){ctx.fillRect(left,top-offset,charWidth,thickness)},_renderTextLine:function(method,ctx,line,left,top,lineIndex){top+=this.fontSize/4;this.callSuper("_renderTextLine",method,ctx,line,left,top,lineIndex)},_renderTextDecoration:function(ctx,textLines){if(this.isEmptyStyles()){return this.callSuper("_renderTextDecoration",ctx,textLines)}},_renderTextLinesBackground:function(ctx,textLines){if(!this.textBackgroundColor&&!this.styles)return;ctx.save();if(this.textBackgroundColor){ctx.fillStyle=this.textBackgroundColor}var lineHeights=0,fractionOfFontSize=this.fontSize/this._fontSizeFraction;for(var i=0,len=textLines.length;imaxWidth){maxWidth=currentLineWidth}}return maxWidth},_getHeightOfLine:function(ctx,lineIndex,textLines){textLines=textLines||this.text.split(this._reNewline);var maxHeight=this._getHeightOfChar(ctx,textLines[lineIndex][0],lineIndex,0),line=textLines[lineIndex],chars=line.split("");for(var i=1,len=chars.length;imaxHeight){maxHeight=currentCharHeight}}return maxHeight*this.lineHeight},_getTextHeight:function(ctx,textLines){var height=0;for(var i=0,len=textLines.length;i-1){offset++;index--}return startFrom-offset},findWordBoundaryRight:function(startFrom){var offset=0,index=startFrom;if(this._reSpace.test(this.text.charAt(index))){while(this._reSpace.test(this.text.charAt(index))){offset++;index++}}while(/\S/.test(this.text.charAt(index))&&index-1){offset++;index--}return startFrom-offset},findLineBoundaryRight:function(startFrom){var offset=0,index=startFrom;while(!/\n/.test(this.text.charAt(index))&&index0&&indexprevIndex;if(isNewline){this.removeStyleObject(isNewline,i+1)}else{this.removeStyleObject(this.get2DCursorLocation(i).charIndex===0,i)}}this.text=this.text.slice(0,start)+this.text.slice(end)},insertChars:function(_chars){var isEndOfLine=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+_chars+this.text.slice(this.selectionEnd);if(this.selectionStart===this.selectionEnd){this.insertStyleObjects(_chars,isEndOfLine,this.copiedStyles)}this.selectionStart+=_chars.length;this.selectionEnd=this.selectionStart;if(this.canvas){this.canvas.renderAll().renderAll()}this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(lineIndex,charIndex,isEndOfLine){this.shiftLineStyles(lineIndex,+1);if(!this.styles[lineIndex+1]){this.styles[lineIndex+1]={}}var currentCharStyle=this.styles[lineIndex][charIndex-1],newLineStyles={};if(isEndOfLine){newLineStyles[0]=clone(currentCharStyle);this.styles[lineIndex+1]=newLineStyles}else{for(var index in this.styles[lineIndex]){if(parseInt(index,10)>=charIndex){newLineStyles[parseInt(index,10)-charIndex]=this.styles[lineIndex][index];delete this.styles[lineIndex][index]}}this.styles[lineIndex+1]=newLineStyles}},insertCharStyleObject:function(lineIndex,charIndex,style){var currentLineStyles=this.styles[lineIndex],currentLineStylesCloned=clone(currentLineStyles);if(charIndex===0&&!style){charIndex=1}for(var index in currentLineStylesCloned){var numericIndex=parseInt(index,10);if(numericIndex>=charIndex){currentLineStyles[numericIndex+1]=currentLineStylesCloned[numericIndex]}}this.styles[lineIndex][charIndex]=style||clone(currentLineStyles[charIndex-1])},insertStyleObjects:function(_chars,isEndOfLine,styles){if(this.isEmptyStyles())return;var cursorLocation=this.get2DCursorLocation(),lineIndex=cursorLocation.lineIndex,charIndex=cursorLocation.charIndex;if(!this.styles[lineIndex]){this.styles[lineIndex]={}}if(_chars==="\n"){this.insertNewlineStyleObject(lineIndex,charIndex,isEndOfLine)}else{if(styles){this._insertStyles(styles)}else{this.insertCharStyleObject(lineIndex,charIndex)}}},_insertStyles:function(styles){for(var i=0,len=styles.length;ilineIndex){this.styles[numericLine+offset]=clonedStyles[numericLine]}}},removeStyleObject:function(isBeginningOfLine,index){var cursorLocation=this.get2DCursorLocation(index),lineIndex=cursorLocation.lineIndex,charIndex=cursorLocation.charIndex;if(isBeginningOfLine){var textLines=this.text.split(this._reNewline),textOnPreviousLine=textLines[lineIndex-1],newCharIndexOnPrevLine=textOnPreviousLine?textOnPreviousLine.length:0;if(!this.styles[lineIndex-1]){this.styles[lineIndex-1]={}}for(charIndex in this.styles[lineIndex]){this.styles[lineIndex-1][parseInt(charIndex,10)+newCharIndexOnPrevLine]=this.styles[lineIndex][charIndex]}this.shiftLineStyles(lineIndex,-1)}else{var currentLineStyles=this.styles[lineIndex];if(currentLineStyles){var offset=this.selectionStart===this.selectionEnd?-1:0;delete currentLineStyles[charIndex+offset]}var currentLineStylesCloned=clone(currentLineStyles);for(var i in currentLineStylesCloned){var numericIndex=parseInt(i,10);if(numericIndex>=charIndex&&numericIndex!==0){currentLineStyles[numericIndex-1]=currentLineStylesCloned[numericIndex];delete currentLineStyles[numericIndex]}}}},insertNewline:function(){this.insertChars("\n")}})})();fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date;this.__lastLastClickTime=+new Date;this.__lastPointer={};this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(options){this.__newClickTime=+new Date;var newPointer=this.canvas.getPointer(options.e);if(this.isTripleClick(newPointer)){this.fire("tripleclick",options);this._stopEvent(options.e)}else if(this.isDoubleClick(newPointer)){this.fire("dblclick",options);this._stopEvent(options.e)}this.__lastLastClickTime=this.__lastClickTime;this.__lastClickTime=this.__newClickTime;this.__lastPointer=newPointer;this.__lastIsEditing=this.isEditing;this.__lastSelected=this.selected},isDoubleClick:function(newPointer){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===newPointer.x&&this.__lastPointer.y===newPointer.y&&this.__lastIsEditing},isTripleClick:function(newPointer){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===newPointer.x&&this.__lastPointer.y===newPointer.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault();e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler();this.initMousedownHandler();this.initMousemoveHandler();this.initMouseupHandler();this.initClicks()},initClicks:function(){this.on("dblclick",function(options){this.selectWord(this.getSelectionStartFromPointer(options.e))});this.on("tripleclick",function(options){this.selectLine(this.getSelectionStartFromPointer(options.e))})},initMousedownHandler:function(){this.on("mousedown",function(options){var pointer=this.canvas.getPointer(options.e);this.__mousedownX=pointer.x;this.__mousedownY=pointer.y;this.__isMousedown=true;if(this.hiddenTextarea&&this.canvas){this.canvas.wrapperEl.appendChild(this.hiddenTextarea)}if(this.selected){this.setCursorByClick(options.e)}if(this.isEditing){this.__selectionStartOnMouseDown=this.selectionStart;this.initDelayedCursor(true)}})},initMousemoveHandler:function(){this.on("mousemove",function(options){if(!this.__isMousedown||!this.isEditing)return;var newSelectionStart=this.getSelectionStartFromPointer(options.e);if(newSelectionStart>=this.__selectionStartOnMouseDown){this.setSelectionStart(this.__selectionStartOnMouseDown);this.setSelectionEnd(newSelectionStart)}else{this.setSelectionStart(newSelectionStart);this.setSelectionEnd(this.__selectionStartOnMouseDown)}})},_isObjectMoved:function(e){var pointer=this.canvas.getPointer(e);return this.__mousedownX!==pointer.x||this.__mousedownY!==pointer.y},initMouseupHandler:function(){this.on("mouseup",function(options){this.__isMousedown=false;if(this._isObjectMoved(options.e))return;if(this.__lastSelected){this.enterEditing();this.initDelayedCursor(true)}this.selected=true})},setCursorByClick:function(e){var newSelectionStart=this.getSelectionStartFromPointer(e);if(e.shiftKey){if(newSelectionStartdistanceBtwLastCharAndCursor?0:1,newSelectionStart=index+offset;if(this.flipX){newSelectionStart=jlen-newSelectionStart}if(newSelectionStart>this.text.length){newSelectionStart=this.text.length}if(j===jlen){newSelectionStart--}return newSelectionStart}});fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea");this.hiddenTextarea.setAttribute("autocapitalize","off");this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px";fabric.document.body.appendChild(this.hiddenTextarea);fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this));fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this));if(!this._clickHandlerInitialized&&this.canvas){fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this));this._clickHandlerInitialized=true}},_keysMap:{8:"removeChars",13:"insertNewline",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",67:"copy",86:"paste",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap){this[this._keysMap[e.keyCode]](e)}else if(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)){this[this._ctrlKeysMap[e.keyCode]](e)}else{return}e.preventDefault();e.stopPropagation();this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){if(this.selectionStart===this.selectionEnd){this.moveCursorRight(e)}this.removeChars(e)},copy:function(){var selectedText=this.getSelectedText();this.copiedText=selectedText;this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(){if(this.copiedText){this.insertChars(this.copiedText)}},cut:function(e){this.copy();this.removeChars(e)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey||e.keyCode===8||e.keyCode===13){return}this.insertChars(String.fromCharCode(e.which));e.preventDefault();e.stopPropagation()},getDownCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,textLines=this.text.split(this._reNewline),_char,lineLeftOffset,textBeforeCursor=this.text.slice(0,selectionProp),textAfterCursor=this.text.slice(selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnSameLineAfterCursor=textAfterCursor.match(/(.*)\n?/)[1],textOnNextLine=(textAfterCursor.match(/.*\n(.*)\n?/)||{})[1]||"",cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===textLines.length-1||e.metaKey){return this.text.length-selectionProp}var widthOfSameLineBeforeCursor=this._getWidthOfLine(this.ctx,cursorLocation.lineIndex,textLines);lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor);var widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnNextLine-widthOfChar,rightEdge=widthOfCharsOnNextLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnNextLine=offsetFromRightEdgethis.text.length){this.selectionStart=this.text.length}this.selectionEnd=this.selectionStart},moveCursorDownWithShift:function(offset){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this.selectionStart+=offset;this._selectionDirection="left";return}else{this._selectionDirection="right";this.selectionEnd+=offset;if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}}},getUpCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===0||e.metaKey){return selectionProp}var textBeforeCursor=this.text.slice(0,selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnPreviousLine=(textBeforeCursor.match(/\n?(.*)\n.*$/)||{})[1]||"",textLines=this.text.split(this._reNewline),_char,widthOfSameLineBeforeCursor=this._getWidthOfLine(this.ctx,cursorLocation.lineIndex,textLines),lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor),widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnPreviousLine-widthOfChar,rightEdge=widthOfCharsOnPreviousLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnPrevLine=offsetFromRightEdge=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation();this._currentCursorOpacity=1;if(e.shiftKey){this.moveCursorRightWithShift(e)}else{this.moveCursorRightWithoutShift(e)}this.initDelayedCursor()},moveCursorRightWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this._moveRight(e,"selectionStart")}else{this._selectionDirection="right";this._moveRight(e,"selectionEnd");if(this.text.charAt(this.selectionEnd-1)==="\n"){this.selectionEnd++}if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}}},moveCursorRightWithoutShift:function(e){this._selectionDirection="right";if(this.selectionStart===this.selectionEnd){this._moveRight(e,"selectionStart");this.selectionEnd=this.selectionStart}else{this.selectionEnd+=this.getNumNewLinesInSelectedText();if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}this.selectionStart=this.selectionEnd}},removeChars:function(e){if(this.selectionStart===this.selectionEnd){this._removeCharsNearCursor(e)}else{this._removeCharsFromTo(this.selectionStart,this.selectionEnd)}this.selectionEnd=this.selectionStart;this._removeExtraneousStyles();if(this.canvas){this.canvas.renderAll().renderAll()}this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0){if(e.metaKey){var leftLineBoundary=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftLineBoundary,this.selectionStart);this.selectionStart=leftLineBoundary}else if(e.altKey){var leftWordBoundary=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftWordBoundary,this.selectionStart);this.selectionStart=leftWordBoundary}else{var isBeginningOfLine=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(isBeginningOfLine);this.selectionStart--;this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}});fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects){if(!this.styles[lineIndex]){this.callSuper("_setSVGTextLineText",textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier)}else{this._setSVGTextLineChars(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects)}},_setSVGTextLineChars:function(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects){var yProp=lineIndex===0||this.useNative?"y":"dy",chars=textLine.split(""),charOffset=0,lineLeftOffset=this._getSVGLineLeftOffset(lineIndex),lineTopOffset=this._getSVGLineTopOffset(lineIndex),heightOfLine=this._getHeightOfLine(this.ctx,lineIndex);for(var i=0,len=chars.length;i'].join("")},_createTextCharSpan:function(_char,styleDecl,lineLeftOffset,lineTopOffset,yProp,charOffset){var fillStyles=this.getSvgStyles.call(fabric.util.object.extend({visible:true,fill:this.fill,stroke:this.stroke,type:"text"},styleDecl));return['',fabric.util.string.escapeXml(_char),""].join("")}});(function(){if(typeof document!=="undefined"&&typeof window!=="undefined"){return}var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;function request(url,encoding,callback){var oURL=URL.parse(url);if(!oURL.port){oURL.port=oURL.protocol.indexOf("https:")===0?443:80}var reqHandler=oURL.port===443?HTTPS:HTTP,req=reqHandler.request({hostname:oURL.hostname,port:oURL.port,path:oURL.path,method:"GET"},function(response){var body="";if(encoding){response.setEncoding(encoding)}response.on("end",function(){callback(body)});response.on("data",function(chunk){if(response.statusCode===200){body+=chunk}})});req.on("error",function(err){if(err.errno===process.ECONNREFUSED){fabric.log("ECONNREFUSED: connection refused to "+oURL.hostname+":"+oURL.port)}else{fabric.log(err.message)}});req.end()}function requestFs(path,callback){var fs=require("fs");fs.readFile(path,function(err,data){if(err){fabric.log(err);throw err}else{callback(data)}})}fabric.util.loadImage=function(url,callback,context){function createImageAndCallBack(data){img.src=new Buffer(data,"binary");img._src=url;callback&&callback.call(context,img)}var img=new Image;if(url&&(url instanceof Buffer||url.indexOf("data")===0)){img.src=img._src=url;callback&&callback.call(context,img)}else if(url&&url.indexOf("http")!==0){requestFs(url,createImageAndCallBack)}else if(url){request(url,"binary",createImageAndCallBack)}else{callback&&callback.call(context,url)}};fabric.loadSVGFromURL=function(url,callback,reviver){url=url.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim();if(url.indexOf("http")!==0){requestFs(url,function(body){fabric.loadSVGFromString(body.toString(),callback,reviver)})}else{request(url,"",function(body){fabric.loadSVGFromString(body,callback,reviver)})}};fabric.loadSVGFromString=function(string,callback,reviver){var doc=(new DOMParser).parseFromString(string);fabric.parseSVGDocument(doc.documentElement,function(results,options){callback&&callback(results,options)},reviver)};fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body);callback&&callback()})};fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){var oImg=new fabric.Image(img);oImg._initConfig(object);oImg._initFilters(object,function(filters){oImg.filters=filters||[];callback&&callback(oImg)})})};fabric.createCanvasForNode=function(width,height,options,nodeCanvasOptions){nodeCanvasOptions=nodeCanvasOptions||options;var canvasEl=fabric.document.createElement("canvas"),nodeCanvas=new Canvas(width||600,height||600,nodeCanvasOptions);canvasEl.style={};canvasEl.width=nodeCanvas.width;canvasEl.height=nodeCanvas.height;var FabricCanvas=fabric.Canvas||fabric.StaticCanvas,fabricCanvas=new FabricCanvas(canvasEl,options);fabricCanvas.contextContainer=nodeCanvas.getContext("2d");fabricCanvas.nodeCanvas=nodeCanvas;fabricCanvas.Font=Canvas.Font;return fabricCanvas};fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()};fabric.StaticCanvas.prototype.createJPEGStream=function(opts){return this.nodeCanvas.createJPEGStream(opts)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(width){origSetWidth.call(this,width);this.nodeCanvas.width=width;return this};if(fabric.Canvas){fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth}var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(height){origSetHeight.call(this,height); +this.nodeCanvas.height=height;return this};if(fabric.Canvas){fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight}})(); \ No newline at end of file diff --git a/dist/fabric.min.js.gz b/dist/fabric.min.js.gz index f65a6018397d0a960858b869a2efe7f3c241e86c..85a06b7ecd4f0505029d82cd5032ebc6c6f3534e 100644 GIT binary patch literal 79867 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

Supported key combinations:

- *
-    *   Move cursor:                    left, right, up, down
-    *   Select character:               shift + left, shift + right
-    *   Select text vertically:         shift + up, shift + down
-    *   Move cursor by word:            alt + left, alt + right
-    *   Select words:                   shift + alt + left, shift + alt + right
-    *   Move cursor to line start/end:  cmd + left, cmd + right
-    *   Select till start/end of line:  cmd + shift + left, cmd + shift + right
-    *   Jump to start/end of text:      cmd + up, cmd + down
-    *   Select till start/end of text:  cmd + shift + up, cmd + shift + down
-    *   Delete character:               backspace
-    *   Delete word:                    alt + backspace
-    *   Delete line:                    cmd + backspace
-    *   Forward delete:                 delete
-    *   Copy text:                      ctrl/cmd + c
-    *   Paste text:                     ctrl/cmd + v
-    *   Cut text:                       ctrl/cmd + x
-    *   Select entire text:             ctrl/cmd + a
-    * 
- * - *

Supported mouse/touch combination

- *
-    *   Position cursor:                click/touch
-    *   Create selection:               click/touch & drag
-    *   Create selection:               click & shift + click
-    *   Select word:                    double click
-    *   Select line:                    triple click
-    * 
- */ - fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'i-text', - - /** - * Index where text selection starts (or where cursor is when there is no selection) - * @type Nubmer - * @default - */ - selectionStart: 0, - - /** - * Index where text selection ends - * @type Nubmer - * @default - */ - selectionEnd: 0, - - /** - * Color of text selection - * @type String - * @default - */ - selectionColor: 'rgba(17,119,255,0.3)', - - /** - * Indicates whether text is in editing mode - * @type Boolean - * @default - */ - isEditing: false, - - /** - * Indicates whether a text can be edited - * @type Boolean - * @default - */ - editable: true, - - /** - * Border color of text object while it's in editing mode - * @type String - * @default - */ - editingBorderColor: 'rgba(102,153,255,0.25)', - - /** - * Width of cursor (in px) - * @type Number - * @default - */ - cursorWidth: 2, - - /** - * Color of default cursor (when not overwritten by character style) - * @type String - * @default - */ - cursorColor: '#333', - - /** - * Delay between cursor blink (in ms) - * @type Number - * @default - */ - cursorDelay: 1000, - - /** - * Duration of cursor fadein (in ms) - * @type Number - * @default - */ - cursorDuration: 600, - - /** - * Object containing character styles - * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) - * @type Object - * @default - */ - styles: null, - - /** - * Indicates whether internal text char widths can be cached - * @type Boolean - * @default - */ - caching: true, - - /** - * @private - * @type Boolean - * @default - */ - _skipFillStrokeCheck: true, - - /** - * @private - */ - _reSpace: /\s|\n/, - - /** - * @private - */ - _fontSizeFraction: 4, - - /** - * @private - */ - _currentCursorOpacity: 0, - - /** - * @private - */ - _selectionDirection: null, - - /** - * @private - */ - _abortCursorAnimation: false, - - /** - * @private - */ - _charWidthsCache: { }, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.IText} thisArg - */ - initialize: function(text, options) { - this.styles = options ? (options.styles || { }) : { }; - this.callSuper('initialize', text, options); - this.initBehavior(); - - fabric.IText.instances.push(this); - - // caching - this.__lineWidths = { }; - this.__lineHeights = { }; - this.__lineOffsets = { }; - }, - - /** - * Returns true if object has no styling - */ - isEmptyStyles: function() { - if (!this.styles) return true; - var obj = this.styles; - - for (var p1 in obj) { - for (var p2 in obj[p1]) { - /*jshint unused:false */ - for (var p3 in obj[p1][p2]) { - return false; - } - } - } - return true; - }, - - /** - * Sets selection start (left boundary of a selection) - * @param {Number} index Index to set selection start to - */ - setSelectionStart: function(index) { - if (this.selectionStart !== index) { - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - } - this.selectionStart = index; - this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); - }, - - /** - * Sets selection end (right boundary of a selection) - * @param {Number} index Index to set selection end to - */ - setSelectionEnd: function(index) { - if (this.selectionEnd !== index) { - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - } - this.selectionEnd = index; - this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); - }, - - /** - * Gets style of a current selection/cursor (at the start position) - * @param {Number} [startIndex] Start index to get styles at - * @param {Number} [endIndex] End index to get styles at - * @return {Object} styles Style object at a specified (or current) index - */ - getSelectionStyles: function(startIndex, endIndex) { - - if (arguments.length === 2) { - var styles = [ ]; - for (var i = startIndex; i < endIndex; i++) { - styles.push(this.getSelectionStyles(i)); - } - return styles; - } - - var loc = this.get2DCursorLocation(startIndex); - if (this.styles[loc.lineIndex]) { - return this.styles[loc.lineIndex][loc.charIndex] || { }; - } - - return { }; - }, - - /** - * Sets style of a current selection - * @param {Object} [styles] Styles object - * @return {fabric.IText} thisArg - * @chainable - */ - setSelectionStyles: function(styles) { - if (this.selectionStart === this.selectionEnd) { - this._extendStyles(this.selectionStart, styles); - } - else { - for (var i = this.selectionStart; i < this.selectionEnd; i++) { - this._extendStyles(i, styles); - } - } - return this; - }, - - /** - * @private - */ - _extendStyles: function(index, styles) { - var loc = this.get2DCursorLocation(index); - - if (!this.styles[loc.lineIndex]) { - this.styles[loc.lineIndex] = { }; - } - if (!this.styles[loc.lineIndex][loc.charIndex]) { - this.styles[loc.lineIndex][loc.charIndex] = { }; - } - - fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - this.callSuper('_render', ctx); - this.ctx = ctx; - this.isEditing && this.renderCursorOrSelection(); - }, - - /** - * Renders cursor or selection (depending on what exists) - */ - renderCursorOrSelection: function() { - if (!this.active) return; - - var chars = this.text.split(''), - boundaries; - - if (this.selectionStart === this.selectionEnd) { - boundaries = this._getCursorBoundaries(chars, 'cursor'); - this.renderCursor(boundaries); - } - else { - boundaries = this._getCursorBoundaries(chars, 'selection'); - this.renderSelection(chars, boundaries); - } - }, - - /** - * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) - * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. - */ - get2DCursorLocation: function(selectionStart) { - if (typeof selectionStart === 'undefined') { - selectionStart = this.selectionStart; - } - var textBeforeCursor = this.text.slice(0, selectionStart), - linesBeforeCursor = textBeforeCursor.split(this._reNewline); - - return { - lineIndex: linesBeforeCursor.length - 1, - charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length - }; - }, - - /** - * Returns complete style of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Object} Character style - */ - getCurrentCharStyle: function(lineIndex, charIndex) { - var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; - - return { - fontSize: style && style.fontSize || this.fontSize, - fill: style && style.fill || this.fill, - textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, - textDecoration: style && style.textDecoration || this.textDecoration, - fontFamily: style && style.fontFamily || this.fontFamily, - stroke: style && style.stroke || this.stroke, - strokeWidth: style && style.strokeWidth || this.strokeWidth - }; - }, - - /** - * Returns fontSize of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Number} Character font size - */ - getCurrentCharFontSize: function(lineIndex, charIndex) { - return ( - this.styles[lineIndex] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize; - }, - - /** - * Returns color (fill) of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {String} Character color (fill) - */ - getCurrentCharColor: function(lineIndex, charIndex) { - return ( - this.styles[lineIndex] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && - this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor; - }, - - /** - * Returns cursor boundaries (left, top, leftOffset, topOffset) - * @private - * @param {Array} chars Array of characters - * @param {String} typeOfBoundaries - */ - _getCursorBoundaries: function(chars, typeOfBoundaries) { - - var cursorLocation = this.get2DCursorLocation(), - - textLines = this.text.split(this._reNewline), - - // left/top are left/top of entire text box - // leftOffset/topOffset are offset from that left/top point of a text box - - left = Math.round(this._getLeftOffset()), - top = -this.height / 2, - - offsets = this._getCursorBoundariesOffsets( - chars, typeOfBoundaries, cursorLocation, textLines); - - return { - left: left, - top: top, - leftOffset: offsets.left + offsets.lineLeft, - topOffset: offsets.top - }; - }, - - /** - * @private - */ - _getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) { - - var lineLeftOffset = 0, - - lineIndex = 0, - charIndex = 0, - - leftOffset = 0, - topOffset = typeOfBoundaries === 'cursor' - // selection starts at the very top of the line, - // whereas cursor starts at the padding created by line height - ? (this._getHeightOfLine(this.ctx, 0) - - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex)) - : 0; - - for (var i = 0; i < this.selectionStart; i++) { - if (chars[i] === '\n') { - leftOffset = 0; - var index = lineIndex + (typeOfBoundaries === 'cursor' ? 1 : 0); - topOffset += this._getCachedLineHeight(index); - - lineIndex++; - charIndex = 0; - } - else { - leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); - charIndex++; - } - - lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines); - } - - this._clearCache(); - - return { - top: topOffset, - left: leftOffset, - lineLeft: lineLeftOffset - }; - }, - - /** - * @private - */ - _clearCache: function() { - this.__lineWidths = { }; - this.__lineHeights = { }; - this.__lineOffsets = { }; - }, - - /** - * @private - */ - _getCachedLineHeight: function(index) { - return this.__lineHeights[index] || - (this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index)); - }, - - /** - * @private - */ - _getCachedLineWidth: function(lineIndex, textLines) { - return this.__lineWidths[lineIndex] || - (this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines)); - }, - - /** - * @private - */ - _getCachedLineOffset: function(lineIndex, textLines) { - var widthOfLine = this._getCachedLineWidth(lineIndex, textLines); - - return this.__lineOffsets[lineIndex] || - (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); - }, - - /** - * Renders cursor - * @param {Object} boundaries - */ - renderCursor: function(boundaries) { - var ctx = this.ctx; - - ctx.save(); - - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), - leftOffset = (lineIndex === 0 && charIndex === 0) - ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) - : boundaries.leftOffset; - - ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; - - ctx.fillRect( - boundaries.left + leftOffset, - boundaries.top + boundaries.topOffset, - this.cursorWidth / this.scaleX, - charHeight); - - ctx.restore(); - }, - - /** - * Renders text selection - * @param {Array} chars Array of characters - * @param {Object} boundaries Object with left/top/leftOffset/topOffset - */ - renderSelection: function(chars, boundaries) { - var ctx = this.ctx; - - ctx.save(); - - ctx.fillStyle = this.selectionColor; - - var start = this.get2DCursorLocation(this.selectionStart), - end = this.get2DCursorLocation(this.selectionEnd), - startLine = start.lineIndex, - endLine = end.lineIndex, - textLines = this.text.split(this._reNewline); - - for (var i = startLine; i <= endLine; i++) { - var lineOffset = this._getCachedLineOffset(i, textLines) || 0, - lineHeight = this._getCachedLineHeight(i), - boxWidth = 0; - - if (i === startLine) { - for (var j = 0, len = textLines[i].length; j < len; j++) { - if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { - boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); + var clone = fabric.util.object.clone; + fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, { + type: "i-text", + selectionStart: 0, + selectionEnd: 0, + selectionColor: "rgba(17,119,255,0.3)", + isEditing: false, + editable: true, + editingBorderColor: "rgba(102,153,255,0.25)", + cursorWidth: 2, + cursorColor: "#333", + cursorDelay: 1e3, + cursorDuration: 600, + styles: null, + caching: true, + _skipFillStrokeCheck: true, + _reSpace: /\s|\n/, + _fontSizeFraction: 4, + _currentCursorOpacity: 0, + _selectionDirection: null, + _abortCursorAnimation: false, + _charWidthsCache: {}, + initialize: function(text, options) { + this.styles = options ? options.styles || {} : {}; + this.callSuper("initialize", text, options); + this.initBehavior(); + fabric.IText.instances.push(this); + this.__lineWidths = {}; + this.__lineHeights = {}; + this.__lineOffsets = {}; + }, + isEmptyStyles: function() { + if (!this.styles) return true; + var obj = this.styles; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + for (var p3 in obj[p1][p2]) { + return false; + } + } } - if (j < start.charIndex) { - lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); + return true; + }, + setSelectionStart: function(index) { + if (this.selectionStart !== index) { + this.canvas && this.canvas.fire("text:selection:changed", { + target: this + }); } - } - } - else if (i > startLine && i < endLine) { - boxWidth += this._getCachedLineWidth(i, textLines) || 5; - } - else if (i === endLine) { - for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { - boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); - } - } - - ctx.fillRect( - boundaries.left + lineOffset, - boundaries.top + boundaries.topOffset, - boxWidth, - lineHeight); - - boundaries.topOffset += lineHeight; - } - ctx.restore(); - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderChars: function(method, ctx, line, left, top, lineIndex) { - - if (this.isEmptyStyles()) { - return this._renderCharsFast(method, ctx, line, left, top); - } - - this.skipTextAlign = true; - - // set proper box offset - left -= this.textAlign === 'center' - ? (this.width / 2) - : (this.textAlign === 'right') - ? this.width - : 0; - - // set proper line offset - var textLines = this.text.split(this._reNewline), - lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), - lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), - lineLeftOffset = this._getLineLeftOffset(lineWidth), - chars = line.split(''), - prevStyle, - charsToRender = ''; - - left += lineLeftOffset || 0; - - ctx.save(); - - for (var i = 0, len = chars.length; i <= len; i++) { - prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); - var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); - - if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { - this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); - charsToRender = ''; - prevStyle = thisStyle; - } - charsToRender += chars[i]; - } - - ctx.restore(); - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line - */ - _renderCharsFast: function(method, ctx, line, left, top) { - this.skipTextAlign = false; - - if (method === 'fillText' && this.fill) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - if (method === 'strokeText' && this.stroke) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { - var decl, charWidth, charHeight; - - if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { - - var shouldStroke = decl.stroke || this.stroke, - shouldFill = decl.fill || this.fill; - - ctx.save(); - charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); - charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); - - if (shouldFill) { - ctx.fillText(_char, left, top); - } - if (shouldStroke) { - ctx.strokeText(_char, left, top); - } - - this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight, charHeight); - ctx.restore(); - - ctx.translate(charWidth, 0); - } - else { - if (method === 'strokeText' && this.stroke) { - ctx[method](_char, left, top); - } - if (method === 'fillText' && this.fill) { - ctx[method](_char, left, top); - } - charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); - this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight); - - ctx.translate(ctx.measureText(_char).width, 0); - } - }, - - /** - * @private - * @param {Object} prevStyle - * @param {Object} thisStyle - */ - _hasStyleChanged: function(prevStyle, thisStyle) { - return (prevStyle.fill !== thisStyle.fill || - prevStyle.fontSize !== thisStyle.fontSize || - prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || - prevStyle.textDecoration !== thisStyle.textDecoration || - prevStyle.fontFamily !== thisStyle.fontFamily || - prevStyle.stroke !== thisStyle.stroke || - prevStyle.strokeWidth !== thisStyle.strokeWidth - ); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { - - var textDecoration = styleDeclaration - ? (styleDeclaration.textDecoration || this.textDecoration) - : this.textDecoration, - - fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; - - if (!textDecoration) return; - - if (textDecoration.indexOf('underline') > -1) { - this._renderCharDecorationAtOffset( - ctx, - left, - top + (this.fontSize / this._fontSizeFraction), - charWidth, - 0, - this.fontSize / 20 - ); - } - if (textDecoration.indexOf('line-through') > -1) { - this._renderCharDecorationAtOffset( - ctx, - left, - top + (this.fontSize / this._fontSizeFraction), - charWidth, - charHeight / 2, - fontSize / 20 - ); - } - if (textDecoration.indexOf('overline') > -1) { - this._renderCharDecorationAtOffset( - ctx, - left, - top, - charWidth, - lineHeight - (this.fontSize / this._fontSizeFraction), - this.fontSize / 20 - ); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset, thickness) { - ctx.fillRect(left, top - offset, charWidth, thickness); - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine - top += this.fontSize / 4; - this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines - */ - _renderTextDecoration: function(ctx, textLines) { - if (this.isEmptyStyles()) { - return this.callSuper('_renderTextDecoration', ctx, textLines); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextLinesBackground: function(ctx, textLines) { - if (!this.textBackgroundColor && !this.styles) return; - - ctx.save(); - - if (this.textBackgroundColor) { - ctx.fillStyle = this.textBackgroundColor; - } - - var lineHeights = 0, - fractionOfFontSize = this.fontSize / this._fontSizeFraction; - - for (var i = 0, len = textLines.length; i < len; i++) { - - var heightOfLine = this._getHeightOfLine(ctx, i, textLines); - if (textLines[i] === '') { - lineHeights += heightOfLine; - continue; - } - - var lineWidth = this._getWidthOfLine(ctx, i, textLines), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - if (this.textBackgroundColor) { - ctx.fillStyle = this.textBackgroundColor; - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + lineHeights + fractionOfFontSize, - lineWidth, - heightOfLine - ); - } - if (this.styles[i]) { - for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { - if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { - - var _char = textLines[i][j]; - - ctx.fillStyle = this.styles[i][j].textBackgroundColor; - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines), - this._getTopOffset() + lineHeights + fractionOfFontSize, - this._getWidthOfChar(ctx, _char, i, j, textLines) + 1, - heightOfLine - ); + this.selectionStart = index; + this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); + }, + setSelectionEnd: function(index) { + if (this.selectionEnd !== index) { + this.canvas && this.canvas.fire("text:selection:changed", { + target: this + }); } - } + this.selectionEnd = index; + this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); + }, + getSelectionStyles: function(startIndex, endIndex) { + if (arguments.length === 2) { + var styles = []; + for (var i = startIndex; i < endIndex; i++) { + styles.push(this.getSelectionStyles(i)); + } + return styles; + } + var loc = this.get2DCursorLocation(startIndex); + if (this.styles[loc.lineIndex]) { + return this.styles[loc.lineIndex][loc.charIndex] || {}; + } + return {}; + }, + setSelectionStyles: function(styles) { + if (this.selectionStart === this.selectionEnd) { + this._extendStyles(this.selectionStart, styles); + } else { + for (var i = this.selectionStart; i < this.selectionEnd; i++) { + this._extendStyles(i, styles); + } + } + return this; + }, + _extendStyles: function(index, styles) { + var loc = this.get2DCursorLocation(index); + if (!this.styles[loc.lineIndex]) { + this.styles[loc.lineIndex] = {}; + } + if (!this.styles[loc.lineIndex][loc.charIndex]) { + this.styles[loc.lineIndex][loc.charIndex] = {}; + } + fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles); + }, + _render: function(ctx) { + this.callSuper("_render", ctx); + this.ctx = ctx; + this.isEditing && this.renderCursorOrSelection(); + }, + renderCursorOrSelection: function() { + if (!this.active) return; + var chars = this.text.split(""), boundaries; + if (this.selectionStart === this.selectionEnd) { + boundaries = this._getCursorBoundaries(chars, "cursor"); + this.renderCursor(boundaries); + } else { + boundaries = this._getCursorBoundaries(chars, "selection"); + this.renderSelection(chars, boundaries); + } + }, + get2DCursorLocation: function(selectionStart) { + if (typeof selectionStart === "undefined") { + selectionStart = this.selectionStart; + } + var textBeforeCursor = this.text.slice(0, selectionStart), linesBeforeCursor = textBeforeCursor.split(this._reNewline); + return { + lineIndex: linesBeforeCursor.length - 1, + charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length + }; + }, + getCurrentCharStyle: function(lineIndex, charIndex) { + var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1]; + return { + fontSize: style && style.fontSize || this.fontSize, + fill: style && style.fill || this.fill, + textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, + textDecoration: style && style.textDecoration || this.textDecoration, + fontFamily: style && style.fontFamily || this.fontFamily, + stroke: style && style.stroke || this.stroke, + strokeWidth: style && style.strokeWidth || this.strokeWidth + }; + }, + getCurrentCharFontSize: function(lineIndex, charIndex) { + return this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1] && this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1].fontSize || this.fontSize; + }, + getCurrentCharColor: function(lineIndex, charIndex) { + return this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1] && this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1].fill || this.cursorColor; + }, + _getCursorBoundaries: function(chars, typeOfBoundaries) { + var cursorLocation = this.get2DCursorLocation(), textLines = this.text.split(this._reNewline), left = Math.round(this._getLeftOffset()), top = -this.height / 2, offsets = this._getCursorBoundariesOffsets(chars, typeOfBoundaries, cursorLocation, textLines); + return { + left: left, + top: top, + leftOffset: offsets.left + offsets.lineLeft, + topOffset: offsets.top + }; + }, + _getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) { + var lineLeftOffset = 0, lineIndex = 0, charIndex = 0, leftOffset = 0, topOffset = typeOfBoundaries === "cursor" ? this._getHeightOfLine(this.ctx, 0) - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) : 0; + for (var i = 0; i < this.selectionStart; i++) { + if (chars[i] === "\n") { + leftOffset = 0; + var index = lineIndex + (typeOfBoundaries === "cursor" ? 1 : 0); + topOffset += this._getCachedLineHeight(index); + lineIndex++; + charIndex = 0; + } else { + leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); + charIndex++; + } + lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines); + } + this._clearCache(); + return { + top: topOffset, + left: leftOffset, + lineLeft: lineLeftOffset + }; + }, + _clearCache: function() { + this.__lineWidths = {}; + this.__lineHeights = {}; + this.__lineOffsets = {}; + }, + _getCachedLineHeight: function(index) { + return this.__lineHeights[index] || (this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index)); + }, + _getCachedLineWidth: function(lineIndex, textLines) { + return this.__lineWidths[lineIndex] || (this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines)); + }, + _getCachedLineOffset: function(lineIndex, textLines) { + var widthOfLine = this._getCachedLineWidth(lineIndex, textLines); + return this.__lineOffsets[lineIndex] || (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); + }, + renderCursor: function(boundaries) { + var ctx = this.ctx; + ctx.save(); + var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), leftOffset = lineIndex === 0 && charIndex === 0 ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) : boundaries.leftOffset; + ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + ctx.fillRect(boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); + ctx.restore(); + }, + renderSelection: function(chars, boundaries) { + var ctx = this.ctx; + ctx.save(); + ctx.fillStyle = this.selectionColor; + var start = this.get2DCursorLocation(this.selectionStart), end = this.get2DCursorLocation(this.selectionEnd), startLine = start.lineIndex, endLine = end.lineIndex, textLines = this.text.split(this._reNewline); + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getCachedLineOffset(i, textLines) || 0, lineHeight = this._getCachedLineHeight(i), boxWidth = 0; + if (i === startLine) { + for (var j = 0, len = textLines[i].length; j < len; j++) { + if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } + if (j < start.charIndex) { + lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } + } + } else if (i > startLine && i < endLine) { + boxWidth += this._getCachedLineWidth(i, textLines) || 5; + } else if (i === endLine) { + for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); + } + } + ctx.fillRect(boundaries.left + lineOffset, boundaries.top + boundaries.topOffset, boxWidth, lineHeight); + boundaries.topOffset += lineHeight; + } + ctx.restore(); + }, + _renderChars: function(method, ctx, line, left, top, lineIndex) { + if (this.isEmptyStyles()) { + return this._renderCharsFast(method, ctx, line, left, top); + } + this.skipTextAlign = true; + left -= this.textAlign === "center" ? this.width / 2 : this.textAlign === "right" ? this.width : 0; + var textLines = this.text.split(this._reNewline), lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth), chars = line.split(""), prevStyle, charsToRender = ""; + left += lineLeftOffset || 0; + ctx.save(); + for (var i = 0, len = chars.length; i <= len; i++) { + prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); + var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); + if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { + this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); + charsToRender = ""; + prevStyle = thisStyle; + } + charsToRender += chars[i]; + } + ctx.restore(); + }, + _renderCharsFast: function(method, ctx, line, left, top) { + this.skipTextAlign = false; + if (method === "fillText" && this.fill) { + this.callSuper("_renderChars", method, ctx, line, left, top); + } + if (method === "strokeText" && this.stroke) { + this.callSuper("_renderChars", method, ctx, line, left, top); + } + }, + _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { + var decl, charWidth, charHeight; + if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { + var shouldStroke = decl.stroke || this.stroke, shouldFill = decl.fill || this.fill; + ctx.save(); + charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); + charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); + if (shouldFill) { + ctx.fillText(_char, left, top); + } + if (shouldStroke) { + ctx.strokeText(_char, left, top); + } + this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight, charHeight); + ctx.restore(); + ctx.translate(charWidth, 0); + } else { + if (method === "strokeText" && this.stroke) { + ctx[method](_char, left, top); + } + if (method === "fillText" && this.fill) { + ctx[method](_char, left, top); + } + charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); + this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight); + ctx.translate(ctx.measureText(_char).width, 0); + } + }, + _hasStyleChanged: function(prevStyle, thisStyle) { + return prevStyle.fill !== thisStyle.fill || prevStyle.fontSize !== thisStyle.fontSize || prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || prevStyle.textDecoration !== thisStyle.textDecoration || prevStyle.fontFamily !== thisStyle.fontFamily || prevStyle.stroke !== thisStyle.stroke || prevStyle.strokeWidth !== thisStyle.strokeWidth; + }, + _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { + var textDecoration = styleDeclaration ? styleDeclaration.textDecoration || this.textDecoration : this.textDecoration, fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; + if (!textDecoration) return; + if (textDecoration.indexOf("underline") > -1) { + this._renderCharDecorationAtOffset(ctx, left, top + this.fontSize / this._fontSizeFraction, charWidth, 0, this.fontSize / 20); + } + if (textDecoration.indexOf("line-through") > -1) { + this._renderCharDecorationAtOffset(ctx, left, top + this.fontSize / this._fontSizeFraction, charWidth, charHeight / 2, fontSize / 20); + } + if (textDecoration.indexOf("overline") > -1) { + this._renderCharDecorationAtOffset(ctx, left, top, charWidth, lineHeight - this.fontSize / this._fontSizeFraction, this.fontSize / 20); + } + }, + _renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset, thickness) { + ctx.fillRect(left, top - offset, charWidth, thickness); + }, + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + top += this.fontSize / 4; + this.callSuper("_renderTextLine", method, ctx, line, left, top, lineIndex); + }, + _renderTextDecoration: function(ctx, textLines) { + if (this.isEmptyStyles()) { + return this.callSuper("_renderTextDecoration", ctx, textLines); + } + }, + _renderTextLinesBackground: function(ctx, textLines) { + if (!this.textBackgroundColor && !this.styles) return; + ctx.save(); + if (this.textBackgroundColor) { + ctx.fillStyle = this.textBackgroundColor; + } + var lineHeights = 0, fractionOfFontSize = this.fontSize / this._fontSizeFraction; + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + if (textLines[i] === "") { + lineHeights += heightOfLine; + continue; + } + var lineWidth = this._getWidthOfLine(ctx, i, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth); + if (this.textBackgroundColor) { + ctx.fillStyle = this.textBackgroundColor; + ctx.fillRect(this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + lineHeights + fractionOfFontSize, lineWidth, heightOfLine); + } + if (this.styles[i]) { + for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { + if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { + var _char = textLines[i][j]; + ctx.fillStyle = this.styles[i][j].textBackgroundColor; + ctx.fillRect(this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines), this._getTopOffset() + lineHeights + fractionOfFontSize, this._getWidthOfChar(ctx, _char, i, j, textLines) + 1, heightOfLine); + } + } + } + lineHeights += heightOfLine; + } + ctx.restore(); + }, + _getCacheProp: function(_char, styleDeclaration) { + return _char + styleDeclaration.fontFamily + styleDeclaration.fontSize + styleDeclaration.fontWeight + styleDeclaration.fontStyle + styleDeclaration.shadow; + }, + _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { + var styleDeclaration = decl || this.styles[lineIndex] && this.styles[lineIndex][charIndex]; + if (styleDeclaration) { + styleDeclaration = clone(styleDeclaration); + } else { + styleDeclaration = {}; + } + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) { + return this._charWidthsCache[cacheProp]; + } + if (typeof styleDeclaration.shadow === "string") { + styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); + } + var fill = styleDeclaration.fill || this.fill; + ctx.fillStyle = fill.toLive ? fill.toLive(ctx) : fill; + if (styleDeclaration.stroke) { + ctx.strokeStyle = styleDeclaration.stroke && styleDeclaration.stroke.toLive ? styleDeclaration.stroke.toLive(ctx) : styleDeclaration.stroke; + } + ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; + ctx.font = this._getFontDeclaration.call(styleDeclaration); + this._setShadow.call(styleDeclaration, ctx); + if (!this.caching) { + return ctx.measureText(_char).width; + } + if (!this._charWidthsCache[cacheProp]) { + this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; + } + return this._charWidthsCache[cacheProp]; + }, + _applyFontStyles: function(styleDeclaration) { + if (!styleDeclaration.fontFamily) { + styleDeclaration.fontFamily = this.fontFamily; + } + if (!styleDeclaration.fontSize) { + styleDeclaration.fontSize = this.fontSize; + } + if (!styleDeclaration.fontWeight) { + styleDeclaration.fontWeight = this.fontWeight; + } + if (!styleDeclaration.fontStyle) { + styleDeclaration.fontStyle = this.fontStyle; + } + }, + _getStyleDeclaration: function(lineIndex, charIndex) { + return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? clone(this.styles[lineIndex][charIndex]) : {}; + }, + _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { + var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + if (this._charWidthsCache[cacheProp] && this.caching) { + return this._charWidthsCache[cacheProp]; + } else if (ctx) { + ctx.save(); + var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); + ctx.restore(); + return width; + } + }, + _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) { + if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) { + return this.styles[lineIndex][charIndex].fontSize || this.fontSize; + } + return this.fontSize; + }, + _getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) { + lines = lines || this.text.split(this._reNewline); + var _char = lines[lineIndex].split("")[charIndex]; + return this._getWidthOfChar(ctx, _char, lineIndex, charIndex); + }, + _getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) { + lines = lines || this.text.split(this._reNewline); + var _char = lines[lineIndex].split("")[charIndex]; + return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); + }, + _getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) { + var width = 0; + for (var i = 0; i < charIndex; i++) { + width += this._getWidthOfCharAt(ctx, lineIndex, i, lines); + } + return width; + }, + _getWidthOfLine: function(ctx, lineIndex, textLines) { + return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines); + }, + _getTextWidth: function(ctx, textLines) { + if (this.isEmptyStyles()) { + return this.callSuper("_getTextWidth", ctx, textLines); + } + var maxWidth = this._getWidthOfLine(ctx, 0, textLines); + for (var i = 1, len = textLines.length; i < len; i++) { + var currentLineWidth = this._getWidthOfLine(ctx, i, textLines); + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + _getHeightOfLine: function(ctx, lineIndex, textLines) { + textLines = textLines || this.text.split(this._reNewline); + var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), line = textLines[lineIndex], chars = line.split(""); + for (var i = 1, len = chars.length; i < len; i++) { + var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); + if (currentCharHeight > maxHeight) { + maxHeight = currentCharHeight; + } + } + return maxHeight * this.lineHeight; + }, + _getTextHeight: function(ctx, textLines) { + var height = 0; + for (var i = 0, len = textLines.length; i < len; i++) { + height += this._getHeightOfLine(ctx, i, textLines); + } + return height; + }, + _getTopOffset: function() { + var topOffset = fabric.Text.prototype._getTopOffset.call(this); + return topOffset - this.fontSize / this._fontSizeFraction; + }, + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) return; + ctx.save(); + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(this._getLeftOffset(), this._getTopOffset() + this.fontSize / this._fontSizeFraction, this.width, this.height); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + return fabric.util.object.extend(this.callSuper("toObject", propertiesToInclude), { + styles: clone(this.styles) + }); } - lineHeights += heightOfLine; - } - ctx.restore(); - }, - - /** - * @private - */ - _getCacheProp: function(_char, styleDeclaration) { - return _char + - - styleDeclaration.fontFamily + - styleDeclaration.fontSize + - styleDeclaration.fontWeight + - styleDeclaration.fontStyle + - - styleDeclaration.shadow; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} _char - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Object} [decl] - */ - _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { - var styleDeclaration = decl || - (this.styles[lineIndex] && - this.styles[lineIndex][charIndex]); - - if (styleDeclaration) { - // cloning so that original style object is not polluted with following font declarations - styleDeclaration = clone(styleDeclaration); - } - else { - styleDeclaration = { }; - } - - this._applyFontStyles(styleDeclaration); - - var cacheProp = this._getCacheProp(_char, styleDeclaration); - - // short-circuit if no styles - if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) { - return this._charWidthsCache[cacheProp]; - } - - if (typeof styleDeclaration.shadow === 'string') { - styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); - } - - var fill = styleDeclaration.fill || this.fill; - ctx.fillStyle = fill.toLive - ? fill.toLive(ctx) - : fill; - - if (styleDeclaration.stroke) { - ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) - ? styleDeclaration.stroke.toLive(ctx) - : styleDeclaration.stroke; - } - - ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; - ctx.font = this._getFontDeclaration.call(styleDeclaration); - this._setShadow.call(styleDeclaration, ctx); - - if (!this.caching) { - return ctx.measureText(_char).width; - } - - if (!this._charWidthsCache[cacheProp]) { - this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; - } - - return this._charWidthsCache[cacheProp]; - }, - - /** - * @private - * @param {Object} styleDeclaration - */ - _applyFontStyles: function(styleDeclaration) { - if (!styleDeclaration.fontFamily) { - styleDeclaration.fontFamily = this.fontFamily; - } - if (!styleDeclaration.fontSize) { - styleDeclaration.fontSize = this.fontSize; - } - if (!styleDeclaration.fontWeight) { - styleDeclaration.fontWeight = this.fontWeight; - } - if (!styleDeclaration.fontStyle) { - styleDeclaration.fontStyle = this.fontStyle; - } - }, - - /** - * @private - * @param {Number} lineIndex - * @param {Number} charIndex - */ - _getStyleDeclaration: function(lineIndex, charIndex) { - return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) - ? clone(this.styles[lineIndex][charIndex]) - : { }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); - this._applyFontStyles(styleDeclaration); - var cacheProp = this._getCacheProp(_char, styleDeclaration); - - if (this._charWidthsCache[cacheProp] && this.caching) { - return this._charWidthsCache[cacheProp]; - } - else if (ctx) { - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - ctx.restore(); - return width; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) { - if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) { - return this.styles[lineIndex][charIndex].fontSize || this.fontSize; - } - return this.fontSize; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) { - lines = lines || this.text.split(this._reNewline); - var _char = lines[lineIndex].split('')[charIndex]; - return this._getWidthOfChar(ctx, _char, lineIndex, charIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) { - lines = lines || this.text.split(this._reNewline); - var _char = lines[lineIndex].split('')[charIndex]; - return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) { - var width = 0; - for (var i = 0; i < charIndex; i++) { - width += this._getWidthOfCharAt(ctx, lineIndex, i, lines); - } - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfLine: function(ctx, lineIndex, textLines) { - // if (!this.styles[lineIndex]) { - // return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]); - // } - return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getTextWidth: function(ctx, textLines) { - - if (this.isEmptyStyles()) { - return this.callSuper('_getTextWidth', ctx, textLines); - } - - var maxWidth = this._getWidthOfLine(ctx, 0, textLines); - - for (var i = 1, len = textLines.length; i < len; i++) { - var currentLineWidth = this._getWidthOfLine(ctx, i, textLines); - if (currentLineWidth > maxWidth) { - maxWidth = currentLineWidth; - } - } - return maxWidth; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfLine: function(ctx, lineIndex, textLines) { - - textLines = textLines || this.text.split(this._reNewline); - - var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), - line = textLines[lineIndex], - chars = line.split(''); - - for (var i = 1, len = chars.length; i < len; i++) { - var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); - if (currentCharHeight > maxHeight) { - maxHeight = currentCharHeight; - } - } - - return maxHeight * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getTextHeight: function(ctx, textLines) { - var height = 0; - for (var i = 0, len = textLines.length; i < len; i++) { - height += this._getHeightOfLine(ctx, i, textLines); - } - return height; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getTopOffset: function() { - var topOffset = fabric.Text.prototype._getTopOffset.call(this); - return topOffset - (this.fontSize / this._fontSizeFraction); - }, - - /** - * @private - * This method is overwritten to account for different top offset - */ - _renderTextBoxBackground: function(ctx) { - if (!this.backgroundColor) return; - - ctx.save(); - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset() + (this.fontSize / this._fontSizeFraction), - this.width, - this.height - ); - - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @methd toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { - styles: clone(this.styles) - }); - } - }); - - /** - * Returns fabric.IText instance from an object representation - * @static - * @memberOf fabric.IText - * @param {Object} object Object to create an instance from - * @return {fabric.IText} instance of fabric.IText - */ - fabric.IText.fromObject = function(object) { - return new fabric.IText(object.text, clone(object)); - }; - - /** - * Contains all fabric.IText objects that have been created - * @static - * @memberof fabric.IText - * @type Array - */ - fabric.IText.instances = [ ]; - + }); + fabric.IText.fromObject = function(object) { + return new fabric.IText(object.text, clone(object)); + }; + fabric.IText.instances = []; })(); - (function() { - - var clone = fabric.util.object.clone; - - fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * Initializes all the interactive behavior of IText - */ - initBehavior: function() { - this.initAddedHandler(); - this.initCursorSelectionHandlers(); - this.initDoubleClickSimulation(); - }, - - /** - * Initializes "selected" event handler - */ - initSelectedHandler: function() { - this.on('selected', function() { - - var _this = this; - setTimeout(function() { - _this.selected = true; - }, 100); - }); - }, - - /** - * Initializes "added" event handler - */ - initAddedHandler: function() { - this.on('added', function() { - if (this.canvas && !this.canvas._hasITextHandlers) { - this.canvas._hasITextHandlers = true; - this._initCanvasHandlers(); - } - }); - }, - - /** - * @private - */ - _initCanvasHandlers: function() { - this.canvas.on('selection:cleared', function() { - fabric.IText.prototype.exitEditingOnOthers.call(); - }); - - this.canvas.on('mouse:up', function() { - fabric.IText.instances.forEach(function(obj) { - obj.__isMousedown = false; - }); - }); - - this.canvas.on('object:selected', function(options) { - fabric.IText.prototype.exitEditingOnOthers.call(options.target); - }); - }, - - /** - * @private - */ - _tick: function() { - - var _this = this; - - if (this._abortCursorAnimation) return; - - this.animate('_currentCursorOpacity', 1, { - - duration: this.cursorDuration, - - onComplete: function() { - _this._onTickComplete(); + var clone = fabric.util.object.clone; + fabric.util.object.extend(fabric.IText.prototype, { + initBehavior: function() { + this.initAddedHandler(); + this.initCursorSelectionHandlers(); + this.initDoubleClickSimulation(); }, - - onChange: function() { - _this.canvas && _this.canvas.renderAll(); + initSelectedHandler: function() { + this.on("selected", function() { + var _this = this; + setTimeout(function() { + _this.selected = true; + }, 100); + }); }, - - abort: function() { - return _this._abortCursorAnimation; + initAddedHandler: function() { + this.on("added", function() { + if (this.canvas && !this.canvas._hasITextHandlers) { + this.canvas._hasITextHandlers = true; + this._initCanvasHandlers(); + } + }); + }, + _initCanvasHandlers: function() { + this.canvas.on("selection:cleared", function() { + fabric.IText.prototype.exitEditingOnOthers.call(); + }); + this.canvas.on("mouse:up", function() { + fabric.IText.instances.forEach(function(obj) { + obj.__isMousedown = false; + }); + }); + this.canvas.on("object:selected", function(options) { + fabric.IText.prototype.exitEditingOnOthers.call(options.target); + }); + }, + _tick: function() { + var _this = this; + if (this._abortCursorAnimation) return; + this.animate("_currentCursorOpacity", 1, { + duration: this.cursorDuration, + onComplete: function() { + _this._onTickComplete(); + }, + onChange: function() { + _this.canvas && _this.canvas.renderAll(); + }, + abort: function() { + return _this._abortCursorAnimation; + } + }); + }, + _onTickComplete: function() { + if (this._abortCursorAnimation) return; + var _this = this; + if (this._cursorTimeout1) { + clearTimeout(this._cursorTimeout1); + } + this._cursorTimeout1 = setTimeout(function() { + _this.animate("_currentCursorOpacity", 0, { + duration: this.cursorDuration / 2, + onComplete: function() { + _this._tick(); + }, + onChange: function() { + _this.canvas && _this.canvas.renderAll(); + }, + abort: function() { + return _this._abortCursorAnimation; + } + }); + }, 100); + }, + initDelayedCursor: function(restart) { + var _this = this, delay = restart ? 0 : this.cursorDelay; + if (restart) { + this._abortCursorAnimation = true; + clearTimeout(this._cursorTimeout1); + this._currentCursorOpacity = 1; + this.canvas && this.canvas.renderAll(); + } + if (this._cursorTimeout2) { + clearTimeout(this._cursorTimeout2); + } + this._cursorTimeout2 = setTimeout(function() { + _this._abortCursorAnimation = false; + _this._tick(); + }, delay); + }, + abortCursorAnimation: function() { + this._abortCursorAnimation = true; + clearTimeout(this._cursorTimeout1); + clearTimeout(this._cursorTimeout2); + this._currentCursorOpacity = 0; + this.canvas && this.canvas.renderAll(); + var _this = this; + setTimeout(function() { + _this._abortCursorAnimation = false; + }, 10); + }, + selectAll: function() { + this.selectionStart = 0; + this.selectionEnd = this.text.length; + this.canvas && this.canvas.fire("text:selection:changed", { + target: this + }); + }, + getSelectedText: function() { + return this.text.slice(this.selectionStart, this.selectionEnd); + }, + findWordBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + if (this._reSpace.test(this.text.charAt(index))) { + while (this._reSpace.test(this.text.charAt(index))) { + offset++; + index--; + } + } + while (/\S/.test(this.text.charAt(index)) && index > -1) { + offset++; + index--; + } + return startFrom - offset; + }, + findWordBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + if (this._reSpace.test(this.text.charAt(index))) { + while (this._reSpace.test(this.text.charAt(index))) { + offset++; + index++; + } + } + while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { + offset++; + index++; + } + return startFrom + offset; + }, + findLineBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + while (!/\n/.test(this.text.charAt(index)) && index > -1) { + offset++; + index--; + } + return startFrom - offset; + }, + findLineBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { + offset++; + index++; + } + return startFrom + offset; + }, + getNumNewLinesInSelectedText: function() { + var selectedText = this.getSelectedText(), numNewLines = 0; + for (var i = 0, chars = selectedText.split(""), len = chars.length; i < len; i++) { + if (chars[i] === "\n") { + numNewLines++; + } + } + return numNewLines; + }, + searchWordBoundary: function(selectionStart, direction) { + var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, _char = this.text.charAt(index), reNonWord = /[ \n\.,;!\?\-]/; + while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { + index += direction; + _char = this.text.charAt(index); + } + if (reNonWord.test(_char) && _char !== "\n") { + index += direction === 1 ? 0 : 1; + } + return index; + }, + selectWord: function(selectionStart) { + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), newSelectionEnd = this.searchWordBoundary(selectionStart, 1); + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); + }, + selectLine: function(selectionStart) { + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), newSelectionEnd = this.findLineBoundaryRight(selectionStart); + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); + }, + enterEditing: function() { + if (this.isEditing || !this.editable) return; + this.exitEditingOnOthers(); + this.isEditing = true; + this.initHiddenTextarea(); + this._updateTextarea(); + this._saveEditingProps(); + this._setEditingProps(); + this._tick(); + this.canvas && this.canvas.renderAll(); + this.fire("editing:entered"); + this.canvas && this.canvas.fire("text:editing:entered", { + target: this + }); + return this; + }, + exitEditingOnOthers: function() { + fabric.IText.instances.forEach(function(obj) { + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } + }, this); + }, + _setEditingProps: function() { + this.hoverCursor = "text"; + if (this.canvas) { + this.canvas.defaultCursor = this.canvas.moveCursor = "text"; + } + this.borderColor = this.editingBorderColor; + this.hasControls = this.selectable = false; + this.lockMovementX = this.lockMovementY = true; + }, + _updateTextarea: function() { + if (!this.hiddenTextarea) return; + this.hiddenTextarea.value = this.text; + this.hiddenTextarea.selectionStart = this.selectionStart; + }, + _saveEditingProps: function() { + this._savedProps = { + hasControls: this.hasControls, + borderColor: this.borderColor, + lockMovementX: this.lockMovementX, + lockMovementY: this.lockMovementY, + hoverCursor: this.hoverCursor, + defaultCursor: this.canvas && this.canvas.defaultCursor, + moveCursor: this.canvas && this.canvas.moveCursor + }; + }, + _restoreEditingProps: function() { + if (!this._savedProps) return; + this.hoverCursor = this._savedProps.overCursor; + this.hasControls = this._savedProps.hasControls; + this.borderColor = this._savedProps.borderColor; + this.lockMovementX = this._savedProps.lockMovementX; + this.lockMovementY = this._savedProps.lockMovementY; + if (this.canvas) { + this.canvas.defaultCursor = this._savedProps.defaultCursor; + this.canvas.moveCursor = this._savedProps.moveCursor; + } + }, + exitEditing: function() { + this.selected = false; + this.isEditing = false; + this.selectable = true; + this.selectionEnd = this.selectionStart; + this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); + this.hiddenTextarea = null; + this.abortCursorAnimation(); + this._restoreEditingProps(); + this._currentCursorOpacity = 0; + this.fire("editing:exited"); + this.canvas && this.canvas.fire("text:editing:exited", { + target: this + }); + return this; + }, + _removeExtraneousStyles: function() { + var textLines = this.text.split(this._reNewline); + for (var prop in this.styles) { + if (!textLines[prop]) { + delete this.styles[prop]; + } + } + }, + _removeCharsFromTo: function(start, end) { + var i = end; + while (i !== start) { + var prevIndex = this.get2DCursorLocation(i).charIndex; + i--; + var index = this.get2DCursorLocation(i).charIndex, isNewline = index > prevIndex; + if (isNewline) { + this.removeStyleObject(isNewline, i + 1); + } else { + this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); + } + } + this.text = this.text.slice(0, start) + this.text.slice(end); + }, + insertChars: function(_chars) { + var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === "\n"; + this.text = this.text.slice(0, this.selectionStart) + _chars + this.text.slice(this.selectionEnd); + if (this.selectionStart === this.selectionEnd) { + this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); + } + this.selectionStart += _chars.length; + this.selectionEnd = this.selectionStart; + if (this.canvas) { + this.canvas.renderAll().renderAll(); + } + this.setCoords(); + this.fire("changed"); + this.canvas && this.canvas.fire("text:changed", { + target: this + }); + }, + insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { + this.shiftLineStyles(lineIndex, +1); + if (!this.styles[lineIndex + 1]) { + this.styles[lineIndex + 1] = {}; + } + var currentCharStyle = this.styles[lineIndex][charIndex - 1], newLineStyles = {}; + if (isEndOfLine) { + newLineStyles[0] = clone(currentCharStyle); + this.styles[lineIndex + 1] = newLineStyles; + } else { + for (var index in this.styles[lineIndex]) { + if (parseInt(index, 10) >= charIndex) { + newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; + delete this.styles[lineIndex][index]; + } + } + this.styles[lineIndex + 1] = newLineStyles; + } + }, + insertCharStyleObject: function(lineIndex, charIndex, style) { + var currentLineStyles = this.styles[lineIndex], currentLineStylesCloned = clone(currentLineStyles); + if (charIndex === 0 && !style) { + charIndex = 1; + } + for (var index in currentLineStylesCloned) { + var numericIndex = parseInt(index, 10); + if (numericIndex >= charIndex) { + currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; + } + } + this.styles[lineIndex][charIndex] = style || clone(currentLineStyles[charIndex - 1]); + }, + insertStyleObjects: function(_chars, isEndOfLine, styles) { + if (this.isEmptyStyles()) return; + var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; + if (!this.styles[lineIndex]) { + this.styles[lineIndex] = {}; + } + if (_chars === "\n") { + this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); + } else { + if (styles) { + this._insertStyles(styles); + } else { + this.insertCharStyleObject(lineIndex, charIndex); + } + } + }, + _insertStyles: function(styles) { + for (var i = 0, len = styles.length; i < len; i++) { + var cursorLocation = this.get2DCursorLocation(this.selectionStart + i), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; + this.insertCharStyleObject(lineIndex, charIndex, styles[i]); + } + }, + shiftLineStyles: function(lineIndex, offset) { + var clonedStyles = clone(this.styles); + for (var line in this.styles) { + var numericLine = parseInt(line, 10); + if (numericLine > lineIndex) { + this.styles[numericLine + offset] = clonedStyles[numericLine]; + } + } + }, + removeStyleObject: function(isBeginningOfLine, index) { + var cursorLocation = this.get2DCursorLocation(index), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; + if (isBeginningOfLine) { + var textLines = this.text.split(this._reNewline), textOnPreviousLine = textLines[lineIndex - 1], newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0; + if (!this.styles[lineIndex - 1]) { + this.styles[lineIndex - 1] = {}; + } + for (charIndex in this.styles[lineIndex]) { + this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] = this.styles[lineIndex][charIndex]; + } + this.shiftLineStyles(lineIndex, -1); + } else { + var currentLineStyles = this.styles[lineIndex]; + if (currentLineStyles) { + var offset = this.selectionStart === this.selectionEnd ? -1 : 0; + delete currentLineStyles[charIndex + offset]; + } + var currentLineStylesCloned = clone(currentLineStyles); + for (var i in currentLineStylesCloned) { + var numericIndex = parseInt(i, 10); + if (numericIndex >= charIndex && numericIndex !== 0) { + currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; + delete currentLineStyles[numericIndex]; + } + } + } + }, + insertNewline: function() { + this.insertChars("\n"); } - }); + }); +})(); + +fabric.util.object.extend(fabric.IText.prototype, { + initDoubleClickSimulation: function() { + this.__lastClickTime = +new Date(); + this.__lastLastClickTime = +new Date(); + this.__lastPointer = {}; + this.on("mousedown", this.onMouseDown.bind(this)); }, - - /** - * @private - */ - _onTickComplete: function() { - if (this._abortCursorAnimation) return; - - var _this = this; - if (this._cursorTimeout1) { - clearTimeout(this._cursorTimeout1); - } - this._cursorTimeout1 = setTimeout(function() { - _this.animate('_currentCursorOpacity', 0, { - duration: this.cursorDuration / 2, - onComplete: function() { - _this._tick(); - }, - onChange: function() { - _this.canvas && _this.canvas.renderAll(); - }, - abort: function() { - return _this._abortCursorAnimation; - } + onMouseDown: function(options) { + this.__newClickTime = +new Date(); + var newPointer = this.canvas.getPointer(options.e); + if (this.isTripleClick(newPointer)) { + this.fire("tripleclick", options); + this._stopEvent(options.e); + } else if (this.isDoubleClick(newPointer)) { + this.fire("dblclick", options); + this._stopEvent(options.e); + } + this.__lastLastClickTime = this.__lastClickTime; + this.__lastClickTime = this.__newClickTime; + this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; + this.__lastSelected = this.selected; + }, + isDoubleClick: function(newPointer) { + return this.__newClickTime - this.__lastClickTime < 500 && this.__lastPointer.x === newPointer.x && this.__lastPointer.y === newPointer.y && this.__lastIsEditing; + }, + isTripleClick: function(newPointer) { + return this.__newClickTime - this.__lastClickTime < 500 && this.__lastClickTime - this.__lastLastClickTime < 500 && this.__lastPointer.x === newPointer.x && this.__lastPointer.y === newPointer.y; + }, + _stopEvent: function(e) { + e.preventDefault && e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + }, + initCursorSelectionHandlers: function() { + this.initSelectedHandler(); + this.initMousedownHandler(); + this.initMousemoveHandler(); + this.initMouseupHandler(); + this.initClicks(); + }, + initClicks: function() { + this.on("dblclick", function(options) { + this.selectWord(this.getSelectionStartFromPointer(options.e)); + }); + this.on("tripleclick", function(options) { + this.selectLine(this.getSelectionStartFromPointer(options.e)); }); - }, 100); }, + initMousedownHandler: function() { + this.on("mousedown", function(options) { + var pointer = this.canvas.getPointer(options.e); + this.__mousedownX = pointer.x; + this.__mousedownY = pointer.y; + this.__isMousedown = true; + if (this.hiddenTextarea && this.canvas) { + this.canvas.wrapperEl.appendChild(this.hiddenTextarea); + } + if (this.selected) { + this.setCursorByClick(options.e); + } + if (this.isEditing) { + this.__selectionStartOnMouseDown = this.selectionStart; + this.initDelayedCursor(true); + } + }); + }, + initMousemoveHandler: function() { + this.on("mousemove", function(options) { + if (!this.__isMousedown || !this.isEditing) return; + var newSelectionStart = this.getSelectionStartFromPointer(options.e); + if (newSelectionStart >= this.__selectionStartOnMouseDown) { + this.setSelectionStart(this.__selectionStartOnMouseDown); + this.setSelectionEnd(newSelectionStart); + } else { + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(this.__selectionStartOnMouseDown); + } + }); + }, + _isObjectMoved: function(e) { + var pointer = this.canvas.getPointer(e); + return this.__mousedownX !== pointer.x || this.__mousedownY !== pointer.y; + }, + initMouseupHandler: function() { + this.on("mouseup", function(options) { + this.__isMousedown = false; + if (this._isObjectMoved(options.e)) return; + if (this.__lastSelected) { + this.enterEditing(); + this.initDelayedCursor(true); + } + this.selected = true; + }); + }, + setCursorByClick: function(e) { + var newSelectionStart = this.getSelectionStartFromPointer(e); + if (e.shiftKey) { + if (newSelectionStart < this.selectionStart) { + this.setSelectionEnd(this.selectionStart); + this.setSelectionStart(newSelectionStart); + } else { + this.setSelectionEnd(newSelectionStart); + } + } else { + this.setSelectionStart(newSelectionStart); + this.setSelectionEnd(newSelectionStart); + } + }, + _getLocalRotatedPointer: function(e) { + var pointer = this.canvas.getPointer(e), pClicked = new fabric.Point(pointer.x, pointer.y), pLeftTop = new fabric.Point(this.left, this.top), rotated = fabric.util.rotatePoint(pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle)); + return this.getLocalPointer(e, rotated); + }, + getSelectionStartFromPointer: function(e) { + var mouseOffset = this._getLocalRotatedPointer(e), textLines = this.text.split(this._reNewline), prevWidth = 0, width = 0, height = 0, charIndex = 0, newSelectionStart; + for (var i = 0, len = textLines.length; i < len; i++) { + height += this._getHeightOfLine(this.ctx, i) * this.scaleY; + var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfLine); + width = lineLeftOffset * this.scaleX; + if (this.flipX) { + textLines[i] = textLines[i].split("").reverse().join(""); + } + for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { + var _char = textLines[i][j]; + prevWidth = width; + width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * this.scaleX; + if (height <= mouseOffset.y || width <= mouseOffset.x) { + charIndex++; + continue; + } + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen); + } + if (mouseOffset.y < height) { + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex + i, jlen, j); + } + } + if (typeof newSelectionStart === "undefined") { + return this.text.length; + } + }, + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen, j) { + var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, newSelectionStart = index + offset; + if (this.flipX) { + newSelectionStart = jlen - newSelectionStart; + } + if (newSelectionStart > this.text.length) { + newSelectionStart = this.text.length; + } + if (j === jlen) { + newSelectionStart--; + } + return newSelectionStart; + } +}); - /** - * Initializes delayed cursor - */ - initDelayedCursor: function(restart) { - var _this = this, - delay = restart ? 0 : this.cursorDelay; - - if (restart) { - this._abortCursorAnimation = true; - clearTimeout(this._cursorTimeout1); - this._currentCursorOpacity = 1; +fabric.util.object.extend(fabric.IText.prototype, { + initHiddenTextarea: function() { + this.hiddenTextarea = fabric.document.createElement("textarea"); + this.hiddenTextarea.setAttribute("autocapitalize", "off"); + this.hiddenTextarea.style.cssText = "position: absolute; top: 0; left: -9999px"; + fabric.document.body.appendChild(this.hiddenTextarea); + fabric.util.addListener(this.hiddenTextarea, "keydown", this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, "keypress", this.onKeyPress.bind(this)); + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, "click", this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } + }, + _keysMap: { + 8: "removeChars", + 13: "insertNewline", + 37: "moveCursorLeft", + 38: "moveCursorUp", + 39: "moveCursorRight", + 40: "moveCursorDown", + 46: "forwardDelete" + }, + _ctrlKeysMap: { + 65: "selectAll", + 67: "copy", + 86: "paste", + 88: "cut" + }, + onClick: function() { + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + onKeyDown: function(e) { + if (!this.isEditing) return; + if (e.keyCode in this._keysMap) { + this[this._keysMap[e.keyCode]](e); + } else if (e.keyCode in this._ctrlKeysMap && (e.ctrlKey || e.metaKey)) { + this[this._ctrlKeysMap[e.keyCode]](e); + } else { + return; + } + e.preventDefault(); + e.stopPropagation(); this.canvas && this.canvas.renderAll(); - } - if (this._cursorTimeout2) { - clearTimeout(this._cursorTimeout2); - } - this._cursorTimeout2 = setTimeout(function() { - _this._abortCursorAnimation = false; - _this._tick(); - }, delay); }, - - /** - * Aborts cursor animation and clears all timeouts - */ - abortCursorAnimation: function() { - this._abortCursorAnimation = true; - - clearTimeout(this._cursorTimeout1); - clearTimeout(this._cursorTimeout2); - - this._currentCursorOpacity = 0; - this.canvas && this.canvas.renderAll(); - - var _this = this; - setTimeout(function() { - _this._abortCursorAnimation = false; - }, 10); - }, - - /** - * Selects entire text - */ - selectAll: function() { - this.selectionStart = 0; - this.selectionEnd = this.text.length; - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - }, - - /** - * Returns selected text - * @return {String} - */ - getSelectedText: function() { - return this.text.slice(this.selectionStart, this.selectionEnd); - }, - - /** - * Find new selection index representing start of current word according to current selection index - * @param {Number} startFrom Surrent selection index - * @return {Number} New selection index - */ - findWordBoundaryLeft: function(startFrom) { - var offset = 0, index = startFrom - 1; - - // remove space before cursor first - if (this._reSpace.test(this.text.charAt(index))) { - while (this._reSpace.test(this.text.charAt(index))) { - offset++; - index--; + forwardDelete: function(e) { + if (this.selectionStart === this.selectionEnd) { + this.moveCursorRight(e); } - } - while (/\S/.test(this.text.charAt(index)) && index > -1) { - offset++; - index--; - } - - return startFrom - offset; + this.removeChars(e); }, - - /** - * Find new selection index representing end of current word according to current selection index - * @param {Number} startFrom Current selection index - * @return {Number} New selection index - */ - findWordBoundaryRight: function(startFrom) { - var offset = 0, index = startFrom; - - // remove space after cursor first - if (this._reSpace.test(this.text.charAt(index))) { - while (this._reSpace.test(this.text.charAt(index))) { - offset++; - index++; + copy: function() { + var selectedText = this.getSelectedText(); + this.copiedText = selectedText; + this.copiedStyles = this.getSelectionStyles(this.selectionStart, this.selectionEnd); + }, + paste: function() { + if (this.copiedText) { + this.insertChars(this.copiedText); } - } - while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { - offset++; - index++; - } - - return startFrom + offset; }, - - /** - * Find new selection index representing start of current line according to current selection index - * @param {Number} current selection index - */ - findLineBoundaryLeft: function(startFrom) { - var offset = 0, index = startFrom - 1; - - while (!/\n/.test(this.text.charAt(index)) && index > -1) { - offset++; - index--; - } - - return startFrom - offset; + cut: function(e) { + this.copy(); + this.removeChars(e); }, - - /** - * Find new selection index representing end of current line according to current selection index - * @param {Number} current selection index - */ - findLineBoundaryRight: function(startFrom) { - var offset = 0, index = startFrom; - - while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { - offset++; - index++; - } - - return startFrom + offset; - }, - - /** - * Returns number of newlines in selected text - * @return {Number} Number of newlines in selected text - */ - getNumNewLinesInSelectedText: function() { - var selectedText = this.getSelectedText(), - numNewLines = 0; - - for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { - if (chars[i] === '\n') { - numNewLines++; + onKeyPress: function(e) { + if (!this.isEditing || e.metaKey || e.ctrlKey || e.keyCode === 8 || e.keyCode === 13) { + return; } - } - return numNewLines; + this.insertChars(String.fromCharCode(e.which)); + e.preventDefault(); + e.stopPropagation(); }, - - /** - * Finds index corresponding to beginning or end of a word - * @param {Number} selectionStart Index of a character - * @param {Number} direction: 1 or -1 - */ - searchWordBoundary: function(selectionStart, direction) { - var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, - _char = this.text.charAt(index), - reNonWord = /[ \n\.,;!\?\-]/; - - while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { - index += direction; - _char = this.text.charAt(index); - } - if (reNonWord.test(_char) && _char !== '\n') { - index += direction === 1 ? 0 : 1; - } - return index; - }, - - /** - * Selects a word based on the index - * @param {Number} selectionStart Index of a character - */ - selectWord: function(selectionStart) { - var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ - newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ - - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(newSelectionEnd); - this.initDelayedCursor(true); - }, - - /** - * Selects a line based on the index - * @param {Number} selectionStart Index of a character - */ - selectLine: function(selectionStart) { - var newSelectionStart = this.findLineBoundaryLeft(selectionStart), - newSelectionEnd = this.findLineBoundaryRight(selectionStart); - - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(newSelectionEnd); - this.initDelayedCursor(true); - }, - - /** - * Enters editing state - * @return {fabric.IText} thisArg - * @chainable - */ - enterEditing: function() { - if (this.isEditing || !this.editable) return; - - this.exitEditingOnOthers(); - - this.isEditing = true; - - this.initHiddenTextarea(); - this._updateTextarea(); - this._saveEditingProps(); - this._setEditingProps(); - - this._tick(); - this.canvas && this.canvas.renderAll(); - - this.fire('editing:entered'); - this.canvas && this.canvas.fire('text:editing:entered', { target: this }); - - return this; - }, - - exitEditingOnOthers: function() { - fabric.IText.instances.forEach(function(obj) { - obj.selected = false; - if (obj.isEditing) { - obj.exitEditing(); + getDownCursorOffset: function(e, isRight) { + var selectionProp = isRight ? this.selectionEnd : this.selectionStart, textLines = this.text.split(this._reNewline), _char, lineLeftOffset, textBeforeCursor = this.text.slice(0, selectionProp), textAfterCursor = this.text.slice(selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n") + 1), textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || {})[1] || "", cursorLocation = this.get2DCursorLocation(selectionProp); + if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey) { + return this.text.length - selectionProp; } - }, this); - }, - - /** - * @private - */ - _setEditingProps: function() { - this.hoverCursor = 'text'; - - if (this.canvas) { - this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; - } - - this.borderColor = this.editingBorderColor; - - this.hasControls = this.selectable = false; - this.lockMovementX = this.lockMovementY = true; - }, - - /** - * @private - */ - _updateTextarea: function() { - if (!this.hiddenTextarea) return; - - this.hiddenTextarea.value = this.text; - this.hiddenTextarea.selectionStart = this.selectionStart; - }, - - /** - * @private - */ - _saveEditingProps: function() { - this._savedProps = { - hasControls: this.hasControls, - borderColor: this.borderColor, - lockMovementX: this.lockMovementX, - lockMovementY: this.lockMovementY, - hoverCursor: this.hoverCursor, - defaultCursor: this.canvas && this.canvas.defaultCursor, - moveCursor: this.canvas && this.canvas.moveCursor - }; - }, - - /** - * @private - */ - _restoreEditingProps: function() { - if (!this._savedProps) return; - - this.hoverCursor = this._savedProps.overCursor; - this.hasControls = this._savedProps.hasControls; - this.borderColor = this._savedProps.borderColor; - this.lockMovementX = this._savedProps.lockMovementX; - this.lockMovementY = this._savedProps.lockMovementY; - - if (this.canvas) { - this.canvas.defaultCursor = this._savedProps.defaultCursor; - this.canvas.moveCursor = this._savedProps.moveCursor; - } - }, - - /** - * Exits from editing state - * @return {fabric.IText} thisArg - * @chainable - */ - exitEditing: function() { - - this.selected = false; - this.isEditing = false; - this.selectable = true; - - this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); - this.hiddenTextarea = null; - - this.abortCursorAnimation(); - this._restoreEditingProps(); - this._currentCursorOpacity = 0; - - this.fire('editing:exited'); - this.canvas && this.canvas.fire('text:editing:exited', { target: this }); - - return this; - }, - - /** - * @private - */ - _removeExtraneousStyles: function() { - var textLines = this.text.split(this._reNewline); - for (var prop in this.styles) { - if (!textLines[prop]) { - delete this.styles[prop]; + var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); + lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); + var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; + for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { + _char = textOnSameLineBeforeCursor[i]; + widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); } - } + var indexOnNextLine = this._getIndexOnNextLine(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines); + return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; }, - - /** - * @private - */ - _removeCharsFromTo: function(start, end) { - - var i = end; - while (i !== start) { - - var prevIndex = this.get2DCursorLocation(i).charIndex; - i--; - - var index = this.get2DCursorLocation(i).charIndex, - isNewline = index > prevIndex; - - if (isNewline) { - this.removeStyleObject(isNewline, i + 1); + _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { + var lineIndex = cursorLocation.lineIndex + 1, widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), widthOfCharsOnNextLine = lineLeftOffset, indexOnNextLine = 0, foundMatch; + for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { + var _char = textOnNextLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + widthOfCharsOnNextLine += widthOfChar; + if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { + foundMatch = true; + var leftEdge = widthOfCharsOnNextLine - widthOfChar, rightEdge = widthOfCharsOnNextLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; + break; + } } - else { - this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); + if (!foundMatch) { + indexOnNextLine = textOnNextLine.length; } - - } - - this.text = this.text.slice(0, start) + - this.text.slice(end); + return indexOnNextLine; }, - - /** - * Inserts a character where cursor is (replacing selection if one exists) - * @param {String} _chars Characters to insert - */ - insertChars: function(_chars) { - var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n'; - - this.text = this.text.slice(0, this.selectionStart) + - _chars + - this.text.slice(this.selectionEnd); - - if (this.selectionStart === this.selectionEnd) { - this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); - } - // else if (this.selectionEnd - this.selectionStart > 1) { - // TODO: replace styles properly - // console.log('replacing MORE than 1 char'); - // } - - this.selectionStart += _chars.length; - this.selectionEnd = this.selectionStart; - - if (this.canvas) { - // TODO: double renderAll gets rid of text box shift happenning sometimes - // need to find out what exactly causes it and fix it - this.canvas.renderAll().renderAll(); - } - - this.setCoords(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); + moveCursorDown: function(e) { + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + var offset = this.getDownCursorOffset(e, this._selectionDirection === "right"); + if (e.shiftKey) { + this.moveCursorDownWithShift(offset); + } else { + this.moveCursorDownWithoutShift(offset); + } + this.initDelayedCursor(); }, - - /** - * Inserts new style object - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Boolean} isEndOfLine True if it's end of line - */ - insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { - - this.shiftLineStyles(lineIndex, +1); - - if (!this.styles[lineIndex + 1]) { - this.styles[lineIndex + 1] = { }; - } - - var currentCharStyle = this.styles[lineIndex][charIndex - 1], - newLineStyles = { }; - - // if there's nothing after cursor, - // we clone current char style onto the next (otherwise empty) line - if (isEndOfLine) { - newLineStyles[0] = clone(currentCharStyle); - this.styles[lineIndex + 1] = newLineStyles; - } - // otherwise we clone styles of all chars - // after cursor onto the next line, from the beginning - else { - for (var index in this.styles[lineIndex]) { - if (parseInt(index, 10) >= charIndex) { - newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; - // remove lines from the previous line since they're on a new line now - delete this.styles[lineIndex][index]; - } + moveCursorDownWithoutShift: function(offset) { + this._selectionDirection = "right"; + this.selectionStart += offset; + if (this.selectionStart > this.text.length) { + this.selectionStart = this.text.length; } - this.styles[lineIndex + 1] = newLineStyles; - } + this.selectionEnd = this.selectionStart; }, - - /** - * Inserts style object for a given line/char index - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Object} [style] Style object to insert, if given - */ - insertCharStyleObject: function(lineIndex, charIndex, style) { - - var currentLineStyles = this.styles[lineIndex], - currentLineStylesCloned = clone(currentLineStyles); - - if (charIndex === 0 && !style) { - charIndex = 1; - } - - // shift all char styles by 1 forward - // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 - for (var index in currentLineStylesCloned) { - var numericIndex = parseInt(index, 10); - if (numericIndex >= charIndex) { - currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; - //delete currentLineStyles[index]; + moveCursorDownWithShift: function(offset) { + if (this._selectionDirection === "left" && this.selectionStart !== this.selectionEnd) { + this.selectionStart += offset; + this._selectionDirection = "left"; + return; + } else { + this._selectionDirection = "right"; + this.selectionEnd += offset; + if (this.selectionEnd > this.text.length) { + this.selectionEnd = this.text.length; + } } - } - - this.styles[lineIndex][charIndex] = - style || clone(currentLineStyles[charIndex - 1]); }, - - /** - * Inserts style object(s) - * @param {String} _chars Characters at the location where style is inserted - * @param {Boolean} isEndOfLine True if it's end of line - * @param {Array} [styles] Styles to insert - */ - insertStyleObjects: function(_chars, isEndOfLine, styles) { - - // short-circuit - if (this.isEmptyStyles()) return; - - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex; - - if (!this.styles[lineIndex]) { - this.styles[lineIndex] = { }; - } - - if (_chars === '\n') { - this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); - } - else { - if (styles) { - this._insertStyles(styles); + getUpCursorOffset: function(e, isRight) { + var selectionProp = isRight ? this.selectionEnd : this.selectionStart, cursorLocation = this.get2DCursorLocation(selectionProp); + if (cursorLocation.lineIndex === 0 || e.metaKey) { + return selectionProp; } - else { - // TODO: support multiple style insertion if _chars.length > 1 - this.insertCharStyleObject(lineIndex, charIndex); + var textBeforeCursor = this.text.slice(0, selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n") + 1), textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || "", textLines = this.text.split(this._reNewline), _char, widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; + for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { + _char = textOnSameLineBeforeCursor[i]; + widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); } - } + var indexOnPrevLine = this._getIndexOnPrevLine(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines); + return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; }, - - /** - * @private - */ - _insertStyles: function(styles) { - for (var i = 0, len = styles.length; i < len; i++) { - - var cursorLocation = this.get2DCursorLocation(this.selectionStart + i), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex; - - this.insertCharStyleObject(lineIndex, charIndex, styles[i]); - } + _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { + var lineIndex = cursorLocation.lineIndex - 1, widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), widthOfCharsOnPreviousLine = lineLeftOffset, indexOnPrevLine = 0, foundMatch; + for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { + var _char = textOnPreviousLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + widthOfCharsOnPreviousLine += widthOfChar; + if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { + foundMatch = true; + var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, rightEdge = widthOfCharsOnPreviousLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : j - 1; + break; + } + } + if (!foundMatch) { + indexOnPrevLine = textOnPreviousLine.length - 1; + } + return indexOnPrevLine; }, - - /** - * Shifts line styles up or down - * @param {Number} lineIndex Index of a line - * @param {Number} offset Can be -1 or +1 - */ - shiftLineStyles: function(lineIndex, offset) { - // shift all line styles by 1 upward - var clonedStyles = clone(this.styles); - for (var line in this.styles) { - var numericLine = parseInt(line, 10); - if (numericLine > lineIndex) { - this.styles[numericLine + offset] = clonedStyles[numericLine]; + moveCursorUp: function(e) { + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + var offset = this.getUpCursorOffset(e, this._selectionDirection === "right"); + if (e.shiftKey) { + this.moveCursorUpWithShift(offset); + } else { + this.moveCursorUpWithoutShift(offset); } - } + this.initDelayedCursor(); }, - - /** - * Removes style object - * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line - * @param {Number} [index] Optional index. When not given, current selectionStart is used. - */ - removeStyleObject: function(isBeginningOfLine, index) { - - var cursorLocation = this.get2DCursorLocation(index), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex; - - if (isBeginningOfLine) { - - var textLines = this.text.split(this._reNewline), - textOnPreviousLine = textLines[lineIndex - 1], - newCharIndexOnPrevLine = textOnPreviousLine - ? textOnPreviousLine.length - : 0; - - if (!this.styles[lineIndex - 1]) { - this.styles[lineIndex - 1] = { }; + moveCursorUpWithShift: function(offset) { + if (this.selectionStart === this.selectionEnd) { + this.selectionStart -= offset; + } else { + if (this._selectionDirection === "right") { + this.selectionEnd -= offset; + this._selectionDirection = "right"; + return; + } else { + this.selectionStart -= offset; + } } - - for (charIndex in this.styles[lineIndex]) { - this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] - = this.styles[lineIndex][charIndex]; + if (this.selectionStart < 0) { + this.selectionStart = 0; } - - this.shiftLineStyles(lineIndex, -1); - } - else { - var currentLineStyles = this.styles[lineIndex]; - - if (currentLineStyles) { - var offset = this.selectionStart === this.selectionEnd ? -1 : 0; - delete currentLineStyles[charIndex + offset]; - // console.log('deleting', lineIndex, charIndex + offset); - } - - var currentLineStylesCloned = clone(currentLineStyles); - - // shift all styles by 1 backwards - for (var i in currentLineStylesCloned) { - var numericIndex = parseInt(i, 10); - if (numericIndex >= charIndex && numericIndex !== 0) { - currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; - delete currentLineStyles[numericIndex]; - } - } - } + this._selectionDirection = "left"; }, - - /** - * Inserts new line - */ - insertNewline: function() { - this.insertChars('\n'); + moveCursorUpWithoutShift: function(offset) { + if (this.selectionStart === this.selectionEnd) { + this.selectionStart -= offset; + } + if (this.selectionStart < 0) { + this.selectionStart = 0; + } + this.selectionEnd = this.selectionStart; + this._selectionDirection = "left"; + }, + moveCursorLeft: function(e) { + if (this.selectionStart === 0 && this.selectionEnd === 0) return; + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + if (e.shiftKey) { + this.moveCursorLeftWithShift(e); + } else { + this.moveCursorLeftWithoutShift(e); + } + this.initDelayedCursor(); + }, + _move: function(e, prop, direction) { + if (e.altKey) { + this[prop] = this["findWordBoundary" + direction](this[prop]); + } else if (e.metaKey) { + this[prop] = this["findLineBoundary" + direction](this[prop]); + } else { + this[prop] += direction === "Left" ? -1 : 1; + } + }, + _moveLeft: function(e, prop) { + this._move(e, prop, "Left"); + }, + _moveRight: function(e, prop) { + this._move(e, prop, "Right"); + }, + moveCursorLeftWithoutShift: function(e) { + this._selectionDirection = "left"; + if (this.selectionEnd === this.selectionStart) { + this._moveLeft(e, "selectionStart"); + } + this.selectionEnd = this.selectionStart; + }, + moveCursorLeftWithShift: function(e) { + if (this._selectionDirection === "right" && this.selectionStart !== this.selectionEnd) { + this._moveLeft(e, "selectionEnd"); + } else { + this._selectionDirection = "left"; + this._moveLeft(e, "selectionStart"); + if (this.text.charAt(this.selectionStart) === "\n") { + this.selectionStart--; + } + if (this.selectionStart < 0) { + this.selectionStart = 0; + } + } + }, + moveCursorRight: function(e) { + if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) return; + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + if (e.shiftKey) { + this.moveCursorRightWithShift(e); + } else { + this.moveCursorRightWithoutShift(e); + } + this.initDelayedCursor(); + }, + moveCursorRightWithShift: function(e) { + if (this._selectionDirection === "left" && this.selectionStart !== this.selectionEnd) { + this._moveRight(e, "selectionStart"); + } else { + this._selectionDirection = "right"; + this._moveRight(e, "selectionEnd"); + if (this.text.charAt(this.selectionEnd - 1) === "\n") { + this.selectionEnd++; + } + if (this.selectionEnd > this.text.length) { + this.selectionEnd = this.text.length; + } + } + }, + moveCursorRightWithoutShift: function(e) { + this._selectionDirection = "right"; + if (this.selectionStart === this.selectionEnd) { + this._moveRight(e, "selectionStart"); + this.selectionEnd = this.selectionStart; + } else { + this.selectionEnd += this.getNumNewLinesInSelectedText(); + if (this.selectionEnd > this.text.length) { + this.selectionEnd = this.text.length; + } + this.selectionStart = this.selectionEnd; + } + }, + removeChars: function(e) { + if (this.selectionStart === this.selectionEnd) { + this._removeCharsNearCursor(e); + } else { + this._removeCharsFromTo(this.selectionStart, this.selectionEnd); + } + this.selectionEnd = this.selectionStart; + this._removeExtraneousStyles(); + if (this.canvas) { + this.canvas.renderAll().renderAll(); + } + this.setCoords(); + this.fire("changed"); + this.canvas && this.canvas.fire("text:changed", { + target: this + }); + }, + _removeCharsNearCursor: function(e) { + if (this.selectionStart !== 0) { + if (e.metaKey) { + var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); + this._removeCharsFromTo(leftLineBoundary, this.selectionStart); + this.selectionStart = leftLineBoundary; + } else if (e.altKey) { + var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); + this._removeCharsFromTo(leftWordBoundary, this.selectionStart); + this.selectionStart = leftWordBoundary; + } else { + var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === "\n"; + this.removeStyleObject(isBeginningOfLine); + this.selectionStart--; + this.text = this.text.slice(0, this.selectionStart) + this.text.slice(this.selectionStart + 1); + } + } } - }); -})(); - - -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - /** - * Initializes "dbclick" event handler - */ - initDoubleClickSimulation: function() { - - // for double click - this.__lastClickTime = +new Date(); - - // for triple click - this.__lastLastClickTime = +new Date(); - - this.__lastPointer = { }; - - this.on('mousedown', this.onMouseDown.bind(this)); - }, - - onMouseDown: function(options) { - - this.__newClickTime = +new Date(); - var newPointer = this.canvas.getPointer(options.e); - - if (this.isTripleClick(newPointer)) { - this.fire('tripleclick', options); - this._stopEvent(options.e); - } - else if (this.isDoubleClick(newPointer)) { - this.fire('dblclick', options); - this._stopEvent(options.e); - } - - this.__lastLastClickTime = this.__lastClickTime; - this.__lastClickTime = this.__newClickTime; - this.__lastPointer = newPointer; - this.__lastIsEditing = this.isEditing; - this.__lastSelected = this.selected; - }, - - isDoubleClick: function(newPointer) { - return this.__newClickTime - this.__lastClickTime < 500 && - this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y && this.__lastIsEditing; - }, - - isTripleClick: function(newPointer) { - return this.__newClickTime - this.__lastClickTime < 500 && - this.__lastClickTime - this.__lastLastClickTime < 500 && - this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y; - }, - - /** - * @private - */ - _stopEvent: function(e) { - e.preventDefault && e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - }, - - /** - * Initializes event handlers related to cursor or selection - */ - initCursorSelectionHandlers: function() { - this.initSelectedHandler(); - this.initMousedownHandler(); - this.initMousemoveHandler(); - this.initMouseupHandler(); - this.initClicks(); - }, - - /** - * Initializes double and triple click event handlers - */ - initClicks: function() { - this.on('dblclick', function(options) { - this.selectWord(this.getSelectionStartFromPointer(options.e)); - }); - this.on('tripleclick', function(options) { - this.selectLine(this.getSelectionStartFromPointer(options.e)); - }); - }, - - /** - * Initializes "mousedown" event handler - */ - initMousedownHandler: function() { - this.on('mousedown', function(options) { - - var pointer = this.canvas.getPointer(options.e); - - this.__mousedownX = pointer.x; - this.__mousedownY = pointer.y; - this.__isMousedown = true; - - if (this.hiddenTextarea && this.canvas) { - this.canvas.wrapperEl.appendChild(this.hiddenTextarea); - } - - if (this.selected) { - this.setCursorByClick(options.e); - } - - if (this.isEditing) { - this.__selectionStartOnMouseDown = this.selectionStart; - this.initDelayedCursor(true); - } - }); - }, - - /** - * Initializes "mousemove" event handler - */ - initMousemoveHandler: function() { - this.on('mousemove', function(options) { - if (!this.__isMousedown || !this.isEditing) return; - - var newSelectionStart = this.getSelectionStartFromPointer(options.e); - - if (newSelectionStart >= this.__selectionStartOnMouseDown) { - this.setSelectionStart(this.__selectionStartOnMouseDown); - this.setSelectionEnd(newSelectionStart); - } - else { - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(this.__selectionStartOnMouseDown); - } - }); - }, - - /** - * @private - */ - _isObjectMoved: function(e) { - var pointer = this.canvas.getPointer(e); - - return this.__mousedownX !== pointer.x || - this.__mousedownY !== pointer.y; - }, - - /** - * Initializes "mouseup" event handler - */ - initMouseupHandler: function() { - this.on('mouseup', function(options) { - this.__isMousedown = false; - if (this._isObjectMoved(options.e)) return; - - if (this.__lastSelected) { - this.enterEditing(); - this.initDelayedCursor(true); - } - this.selected = true; - }); - }, - - /** - * Changes cursor location in a text depending on passed pointer (x/y) object - * @param {Object} pointer Pointer object with x and y numeric properties - */ - setCursorByClick: function(e) { - var newSelectionStart = this.getSelectionStartFromPointer(e); - - if (e.shiftKey) { - if (newSelectionStart < this.selectionStart) { - this.setSelectionEnd(this.selectionStart); - this.setSelectionStart(newSelectionStart); - } - else { - this.setSelectionEnd(newSelectionStart); - } - } - else { - this.setSelectionStart(newSelectionStart); - this.setSelectionEnd(newSelectionStart); - } - }, - - /** - * @private - * @param {Event} e Event object - * @param {Object} Object with x/y corresponding to local offset (according to object rotation) - */ - _getLocalRotatedPointer: function(e) { - var pointer = this.canvas.getPointer(e), - - pClicked = new fabric.Point(pointer.x, pointer.y), - pLeftTop = new fabric.Point(this.left, this.top), - - rotated = fabric.util.rotatePoint( - pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle)); - - return this.getLocalPointer(e, rotated); - }, - - /** - * Returns index of a character corresponding to where an object was clicked - * @param {Event} e Event object - * @return {Number} Index of a character - */ - getSelectionStartFromPointer: function(e) { - - var mouseOffset = this._getLocalRotatedPointer(e), - textLines = this.text.split(this._reNewline), - prevWidth = 0, - width = 0, - height = 0, - charIndex = 0, - newSelectionStart; - - for (var i = 0, len = textLines.length; i < len; i++) { - - height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - - var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), - lineLeftOffset = this._getLineLeftOffset(widthOfLine); - - width = lineLeftOffset * this.scaleX; - - if (this.flipX) { - // when oject is horizontally flipped we reverse chars - textLines[i] = textLines[i].split('').reverse().join(''); - } - - for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { - - var _char = textLines[i][j]; - prevWidth = width; - - width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * - this.scaleX; - - if (height <= mouseOffset.y || width <= mouseOffset.x) { - charIndex++; - continue; - } - - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i, jlen); - } - - if (mouseOffset.y < height) { - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i, jlen, j); - } - } - - // clicked somewhere after all chars, so set at the end - if (typeof newSelectionStart === 'undefined') { - return this.text.length; - } - }, - - /** - * @private - */ - _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen, j) { - - var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, - distanceBtwNextCharAndCursor = width - mouseOffset.x, - offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, - newSelectionStart = index + offset; - - // if object is horizontally flipped, mirror cursor location from the end - if (this.flipX) { - newSelectionStart = jlen - newSelectionStart; - } - - if (newSelectionStart > this.text.length) { - newSelectionStart = this.text.length; - } - - if (j === jlen) { - newSelectionStart--; - } - - return newSelectionStart; - } }); - -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * Initializes hidden textarea (needed to bring up keyboard in iOS) - */ - initHiddenTextarea: function() { - this.hiddenTextarea = fabric.document.createElement('textarea'); - - this.hiddenTextarea.setAttribute('autocapitalize', 'off'); - this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px'; - - fabric.document.body.appendChild(this.hiddenTextarea); - - fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); - - if (!this._clickHandlerInitialized && this.canvas) { - fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); - this._clickHandlerInitialized = true; +fabric.util.object.extend(fabric.IText.prototype, { + _setSVGTextLineText: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { + if (!this.styles[lineIndex]) { + this.callSuper("_setSVGTextLineText", textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier); + } else { + this._setSVGTextLineChars(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); + } + }, + _setSVGTextLineChars: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { + var yProp = lineIndex === 0 || this.useNative ? "y" : "dy", chars = textLine.split(""), charOffset = 0, lineLeftOffset = this._getSVGLineLeftOffset(lineIndex), lineTopOffset = this._getSVGLineTopOffset(lineIndex), heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); + for (var i = 0, len = chars.length; i < len; i++) { + var styleDecl = this.styles[lineIndex][i] || {}; + textSpans.push(this._createTextCharSpan(chars[i], styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset)); + var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); + if (styleDecl.textBackgroundColor) { + textBgRects.push(this._createTextCharBg(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset)); + } + charOffset += charWidth; + } + }, + _getSVGLineLeftOffset: function(lineIndex) { + return this._boundaries && this._boundaries[lineIndex] ? fabric.util.toFixed(this._boundaries[lineIndex].left, 2) : 0; + }, + _getSVGLineTopOffset: function(lineIndex) { + var lineTopOffset = 0; + for (var j = 0; j <= lineIndex; j++) { + lineTopOffset += this._getHeightOfLine(this.ctx, j); + } + return lineTopOffset - this.height / 2; + }, + _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { + return [ '' ].join(""); + }, + _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset) { + var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ + visible: true, + fill: this.fill, + stroke: this.stroke, + type: "text" + }, styleDecl)); + return [ '', fabric.util.string.escapeXml(_char), "" ].join(""); } - }, - - /** - * @private - */ - _keysMap: { - 8: 'removeChars', - 13: 'insertNewline', - 37: 'moveCursorLeft', - 38: 'moveCursorUp', - 39: 'moveCursorRight', - 40: 'moveCursorDown', - 46: 'forwardDelete' - }, - - /** - * @private - */ - _ctrlKeysMap: { - 65: 'selectAll', - 67: 'copy', - 86: 'paste', - 88: 'cut' - }, - - onClick: function() { - // No need to trigger click event here, focus is enough to have the keyboard appear on Android - this.hiddenTextarea && this.hiddenTextarea.focus(); - }, - - /** - * Handles keyup event - * @param {Event} e Event object - */ - onKeyDown: function(e) { - if (!this.isEditing) return; - - if (e.keyCode in this._keysMap) { - this[this._keysMap[e.keyCode]](e); - } - else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) { - this[this._ctrlKeysMap[e.keyCode]](e); - } - else { - return; - } - - e.preventDefault(); - e.stopPropagation(); - - this.canvas && this.canvas.renderAll(); - }, - - /** - * Forward delete - */ - forwardDelete: function(e) { - if (this.selectionStart === this.selectionEnd) { - this.moveCursorRight(e); - } - this.removeChars(e); - }, - - /** - * Copies selected text - */ - copy: function() { - var selectedText = this.getSelectedText(); - this.copiedText = selectedText; - this.copiedStyles = this.getSelectionStyles( - this.selectionStart, - this.selectionEnd); - }, - - /** - * Pastes text - */ - paste: function() { - if (this.copiedText) { - this.insertChars(this.copiedText); - } - }, - - /** - * Cuts text - */ - cut: function(e) { - this.copy(); - this.removeChars(e); - }, - - /** - * Handles keypress event - * @param {Event} e Event object - */ - onKeyPress: function(e) { - if (!this.isEditing || e.metaKey || e.ctrlKey || e.keyCode === 8 || e.keyCode === 13) { - return; - } - - this.insertChars(String.fromCharCode(e.which)); - - e.preventDefault(); - e.stopPropagation(); - }, - - /** - * Gets start offset of a selection - * @return {Number} - */ - getDownCursorOffset: function(e, isRight) { - - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, - textLines = this.text.split(this._reNewline), - _char, - lineLeftOffset, - - textBeforeCursor = this.text.slice(0, selectionProp), - textAfterCursor = this.text.slice(selectionProp), - - textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), - textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], - textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '', - - cursorLocation = this.get2DCursorLocation(selectionProp); - - // if on last line, down cursor goes to end of line - if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey) { - - // move to the end of a text - return this.text.length - selectionProp; - } - - var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, - lineIndex = cursorLocation.lineIndex; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnNextLine = this._getIndexOnNextLine( - cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines); - - return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; - }, - - /** - * @private - */ - _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - - var lineIndex = cursorLocation.lineIndex + 1, - widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), - lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), - widthOfCharsOnNextLine = lineLeftOffset, - indexOnNextLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { - - var _char = textOnNextLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnNextLine += widthOfChar; - - if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnNextLine - widthOfChar, - rightEdge = widthOfCharsOnNextLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); - - indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; - - break; - } - } - - // reached end - if (!foundMatch) { - indexOnNextLine = textOnNextLine.length; - } - - return indexOnNextLine; - }, - - /** - * Moves cursor down - * @param {Event} e Event object - */ - moveCursorDown: function(e) { - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right'); - - if (e.shiftKey) { - this.moveCursorDownWithShift(offset); - } - else { - this.moveCursorDownWithoutShift(offset); - } - - this.initDelayedCursor(); - }, - - /** - * Moves cursor down without keeping selection - * @param {Number} offset - */ - moveCursorDownWithoutShift: function(offset) { - - this._selectionDirection = 'right'; - this.selectionStart += offset; - - if (this.selectionStart > this.text.length) { - this.selectionStart = this.text.length; - } - this.selectionEnd = this.selectionStart; - }, - - /** - * Moves cursor down while keeping selection - * @param {Number} offset - */ - moveCursorDownWithShift: function(offset) { - - if (this._selectionDirection === 'left' && (this.selectionStart !== this.selectionEnd)) { - this.selectionStart += offset; - this._selectionDirection = 'left'; - return; - } - else { - this._selectionDirection = 'right'; - this.selectionEnd += offset; - - if (this.selectionEnd > this.text.length) { - this.selectionEnd = this.text.length; - } - } - }, - - getUpCursorOffset: function(e, isRight) { - - var selectionProp = isRight ? this.selectionEnd : this.selectionStart, - cursorLocation = this.get2DCursorLocation(selectionProp); - - // if on first line, up cursor goes to start of line - if (cursorLocation.lineIndex === 0 || e.metaKey) { - return selectionProp; - } - - var textBeforeCursor = this.text.slice(0, selectionProp), - textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), - textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', - textLines = this.text.split(this._reNewline), - _char, - widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), - widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, - lineIndex = cursorLocation.lineIndex; - - for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { - _char = textOnSameLineBeforeCursor[i]; - widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - - var indexOnPrevLine = this._getIndexOnPrevLine( - cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines); - - return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; - }, - - /** - * @private - */ - _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - - var lineIndex = cursorLocation.lineIndex - 1, - widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), - lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), - widthOfCharsOnPreviousLine = lineLeftOffset, - indexOnPrevLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - - var _char = textOnPreviousLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnPreviousLine += widthOfChar; - - if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, - rightEdge = widthOfCharsOnPreviousLine, - offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), - offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); - - indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); - - break; - } - } - - // reached end - if (!foundMatch) { - indexOnPrevLine = textOnPreviousLine.length - 1; - } - - return indexOnPrevLine; - }, - - /** - * Moves cursor up - * @param {Event} e Event object - */ - moveCursorUp: function(e) { - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right'); - - if (e.shiftKey) { - this.moveCursorUpWithShift(offset); - } - else { - this.moveCursorUpWithoutShift(offset); - } - - this.initDelayedCursor(); - }, - - /** - * Moves cursor up with shift - * @param {Number} offset - */ - moveCursorUpWithShift: function(offset) { - - if (this.selectionStart === this.selectionEnd) { - this.selectionStart -= offset; - } - else { - if (this._selectionDirection === 'right') { - this.selectionEnd -= offset; - this._selectionDirection = 'right'; - return; - } - else { - this.selectionStart -= offset; - } - } - - if (this.selectionStart < 0) { - this.selectionStart = 0; - } - - this._selectionDirection = 'left'; - }, - - /** - * Moves cursor up without shift - * @param {Number} offset - */ - moveCursorUpWithoutShift: function(offset) { - if (this.selectionStart === this.selectionEnd) { - this.selectionStart -= offset; - } - if (this.selectionStart < 0) { - this.selectionStart = 0; - } - this.selectionEnd = this.selectionStart; - - this._selectionDirection = 'left'; - }, - - /** - * Moves cursor left - * @param {Event} e Event object - */ - moveCursorLeft: function(e) { - if (this.selectionStart === 0 && this.selectionEnd === 0) return; - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - if (e.shiftKey) { - this.moveCursorLeftWithShift(e); - } - else { - this.moveCursorLeftWithoutShift(e); - } - - this.initDelayedCursor(); - }, - - /** - * @private - */ - _move: function(e, prop, direction) { - if (e.altKey) { - this[prop] = this['findWordBoundary' + direction](this[prop]); - } - else if (e.metaKey) { - this[prop] = this['findLineBoundary' + direction](this[prop]); - } - else { - this[prop] += (direction === 'Left' ? -1 : 1); - } - }, - - /** - * @private - */ - _moveLeft: function(e, prop) { - this._move(e, prop, 'Left'); - }, - - /** - * @private - */ - _moveRight: function(e, prop) { - this._move(e, prop, 'Right'); - }, - - /** - * Moves cursor left without keeping selection - * @param {Event} e - */ - moveCursorLeftWithoutShift: function(e) { - this._selectionDirection = 'left'; - - // only move cursor when there is no selection, - // otherwise we discard it, and leave cursor on same place - if (this.selectionEnd === this.selectionStart) { - this._moveLeft(e, 'selectionStart'); - } - this.selectionEnd = this.selectionStart; - }, - - /** - * Moves cursor left while keeping selection - * @param {Event} e - */ - moveCursorLeftWithShift: function(e) { - if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { - this._moveLeft(e, 'selectionEnd'); - } - else { - this._selectionDirection = 'left'; - this._moveLeft(e, 'selectionStart'); - - // increase selection by one if it's a newline - if (this.text.charAt(this.selectionStart) === '\n') { - this.selectionStart--; - } - if (this.selectionStart < 0) { - this.selectionStart = 0; - } - } - }, - - /** - * Moves cursor right - * @param {Event} e Event object - */ - moveCursorRight: function(e) { - if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) return; - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - - if (e.shiftKey) { - this.moveCursorRightWithShift(e); - } - else { - this.moveCursorRightWithoutShift(e); - } - - this.initDelayedCursor(); - }, - - /** - * Moves cursor right while keeping selection - * @param {Event} e - */ - moveCursorRightWithShift: function(e) { - if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { - this._moveRight(e, 'selectionStart'); - } - else { - this._selectionDirection = 'right'; - this._moveRight(e, 'selectionEnd'); - - // increase selection by one if it's a newline - if (this.text.charAt(this.selectionEnd - 1) === '\n') { - this.selectionEnd++; - } - if (this.selectionEnd > this.text.length) { - this.selectionEnd = this.text.length; - } - } - }, - - /** - * Moves cursor right without keeping selection - * @param {Event} e - */ - moveCursorRightWithoutShift: function(e) { - this._selectionDirection = 'right'; - - if (this.selectionStart === this.selectionEnd) { - this._moveRight(e, 'selectionStart'); - this.selectionEnd = this.selectionStart; - } - else { - this.selectionEnd += this.getNumNewLinesInSelectedText(); - if (this.selectionEnd > this.text.length) { - this.selectionEnd = this.text.length; - } - this.selectionStart = this.selectionEnd; - } - }, - - /** - * Inserts a character where cursor is (replacing selection if one exists) - */ - removeChars: function(e) { - if (this.selectionStart === this.selectionEnd) { - this._removeCharsNearCursor(e); - } - else { - this._removeCharsFromTo(this.selectionStart, this.selectionEnd); - } - - this.selectionEnd = this.selectionStart; - - this._removeExtraneousStyles(); - - if (this.canvas) { - // TODO: double renderAll gets rid of text box shift happenning sometimes - // need to find out what exactly causes it and fix it - this.canvas.renderAll().renderAll(); - } - - this.setCoords(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); - }, - - /** - * @private - */ - _removeCharsNearCursor: function(e) { - if (this.selectionStart !== 0) { - - if (e.metaKey) { - // remove all till the start of current line - var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); - - this._removeCharsFromTo(leftLineBoundary, this.selectionStart); - this.selectionStart = leftLineBoundary; - } - else if (e.altKey) { - // remove all till the start of current word - var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); - - this._removeCharsFromTo(leftWordBoundary, this.selectionStart); - this.selectionStart = leftWordBoundary; - } - else { - var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; - this.removeStyleObject(isBeginningOfLine); - - this.selectionStart--; - this.text = this.text.slice(0, this.selectionStart) + - this.text.slice(this.selectionStart + 1); - } - } - } }); - -/* _TO_SVG_START_ */ -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * @private - */ - _setSVGTextLineText: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { - if (!this.styles[lineIndex]) { - this.callSuper('_setSVGTextLineText', - textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier); - } - else { - this._setSVGTextLineChars( - textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); - } - }, - - /** - * @private - */ - _setSVGTextLineChars: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { - - var yProp = lineIndex === 0 || this.useNative ? 'y' : 'dy', - chars = textLine.split(''), - charOffset = 0, - lineLeftOffset = this._getSVGLineLeftOffset(lineIndex), - lineTopOffset = this._getSVGLineTopOffset(lineIndex), - heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); - - for (var i = 0, len = chars.length; i < len; i++) { - var styleDecl = this.styles[lineIndex][i] || { }; - - textSpans.push( - this._createTextCharSpan( - chars[i], styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset)); - - var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); - - if (styleDecl.textBackgroundColor) { - textBgRects.push( - this._createTextCharBg( - styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset)); - } - - charOffset += charWidth; - } - }, - - /** - * @private - */ - _getSVGLineLeftOffset: function(lineIndex) { - return (this._boundaries && this._boundaries[lineIndex]) - ? fabric.util.toFixed(this._boundaries[lineIndex].left, 2) - : 0; - }, - - /** - * @private - */ - _getSVGLineTopOffset: function(lineIndex) { - var lineTopOffset = 0; - for (var j = 0; j <= lineIndex; j++) { - lineTopOffset += this._getHeightOfLine(this.ctx, j); - } - return lineTopOffset - this.height / 2; - }, - - /** - * @private - */ - _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { - return [ - '' - ].join(''); - }, - - /** - * @private - */ - _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset) { - - var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ - visible: true, - fill: this.fill, - stroke: this.stroke, - type: 'text' - }, styleDecl)); - - return [ - '', - - fabric.util.string.escapeXml(_char), - '' - ].join(''); - } -}); -/* _TO_SVG_END_ */ - - (function() { - - if (typeof document !== 'undefined' && typeof window !== 'undefined') { - return; - } - - var DOMParser = require('xmldom').DOMParser, - URL = require('url'), - HTTP = require('http'), - HTTPS = require('https'), - - Canvas = require('canvas'), - Image = require('canvas').Image; - - /** @private */ - function request(url, encoding, callback) { - var oURL = URL.parse(url); - - // detect if http or https is used - if ( !oURL.port ) { - oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80; + if (typeof document !== "undefined" && typeof window !== "undefined") { + return; } - - // assign request handler based on protocol - var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP, - req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' + var DOMParser = require("xmldom").DOMParser, URL = require("url"), HTTP = require("http"), HTTPS = require("https"), Canvas = require("canvas"), Image = require("canvas").Image; + function request(url, encoding, callback) { + var oURL = URL.parse(url); + if (!oURL.port) { + oURL.port = oURL.protocol.indexOf("https:") === 0 ? 443 : 80; + } + var reqHandler = oURL.port === 443 ? HTTPS : HTTP, req = reqHandler.request({ + hostname: oURL.hostname, + port: oURL.port, + path: oURL.path, + method: "GET" }, function(response) { - var body = ''; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; + var body = ""; + if (encoding) { + response.setEncoding(encoding); } - }); + response.on("end", function() { + callback(body); + }); + response.on("data", function(chunk) { + if (response.statusCode === 200) { + body += chunk; + } + }); }); - - req.on('error', function(err) { - if (err.errno === process.ECONNREFUSED) { - fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port); - } - else { - fabric.log(err.message); - } - }); - - req.end(); - } - - /** @private */ - function requestFs(path, callback){ - var fs = require('fs'); - fs.readFile(path, function (err, data) { - if (err) { - fabric.log(err); - throw err; - } - else { - callback(data); - } - }); - } - - fabric.util.loadImage = function(url, callback, context) { - function createImageAndCallBack(data) { - img.src = new Buffer(data, 'binary'); - // preserving original url, which seems to be lost in node-canvas - img._src = url; - callback && callback.call(context, img); + req.on("error", function(err) { + if (err.errno === process.ECONNREFUSED) { + fabric.log("ECONNREFUSED: connection refused to " + oURL.hostname + ":" + oURL.port); + } else { + fabric.log(err.message); + } + }); + req.end(); } - var img = new Image(); - if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { - img.src = img._src = url; - callback && callback.call(context, img); + function requestFs(path, callback) { + var fs = require("fs"); + fs.readFile(path, function(err, data) { + if (err) { + fabric.log(err); + throw err; + } else { + callback(data); + } + }); } - else if (url && url.indexOf('http') !== 0) { - requestFs(url, createImageAndCallBack); + fabric.util.loadImage = function(url, callback, context) { + function createImageAndCallBack(data) { + img.src = new Buffer(data, "binary"); + img._src = url; + callback && callback.call(context, img); + } + var img = new Image(); + if (url && (url instanceof Buffer || url.indexOf("data") === 0)) { + img.src = img._src = url; + callback && callback.call(context, img); + } else if (url && url.indexOf("http") !== 0) { + requestFs(url, createImageAndCallBack); + } else if (url) { + request(url, "binary", createImageAndCallBack); + } else { + callback && callback.call(context, url); + } + }; + fabric.loadSVGFromURL = function(url, callback, reviver) { + url = url.replace(/^\n\s*/, "").replace(/\?.*$/, "").trim(); + if (url.indexOf("http") !== 0) { + requestFs(url, function(body) { + fabric.loadSVGFromString(body.toString(), callback, reviver); + }); + } else { + request(url, "", function(body) { + fabric.loadSVGFromString(body, callback, reviver); + }); + } + }; + fabric.loadSVGFromString = function(string, callback, reviver) { + var doc = new DOMParser().parseFromString(string); + fabric.parseSVGDocument(doc.documentElement, function(results, options) { + callback && callback(results, options); + }, reviver); + }; + fabric.util.getScript = function(url, callback) { + request(url, "", function(body) { + eval(body); + callback && callback(); + }); + }; + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + var oImg = new fabric.Image(img); + oImg._initConfig(object); + oImg._initFilters(object, function(filters) { + oImg.filters = filters || []; + callback && callback(oImg); + }); + }); + }; + fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { + nodeCanvasOptions = nodeCanvasOptions || options; + var canvasEl = fabric.document.createElement("canvas"), nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); + canvasEl.style = {}; + canvasEl.width = nodeCanvas.width; + canvasEl.height = nodeCanvas.height; + var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, fabricCanvas = new FabricCanvas(canvasEl, options); + fabricCanvas.contextContainer = nodeCanvas.getContext("2d"); + fabricCanvas.nodeCanvas = nodeCanvas; + fabricCanvas.Font = Canvas.Font; + return fabricCanvas; + }; + fabric.StaticCanvas.prototype.createPNGStream = function() { + return this.nodeCanvas.createPNGStream(); + }; + fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { + return this.nodeCanvas.createJPEGStream(opts); + }; + var origSetWidth = fabric.StaticCanvas.prototype.setWidth; + fabric.StaticCanvas.prototype.setWidth = function(width) { + origSetWidth.call(this, width); + this.nodeCanvas.width = width; + return this; + }; + if (fabric.Canvas) { + fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; } - else if (url) { - request(url, 'binary', createImageAndCallBack); + var origSetHeight = fabric.StaticCanvas.prototype.setHeight; + fabric.StaticCanvas.prototype.setHeight = function(height) { + origSetHeight.call(this, height); + this.nodeCanvas.height = height; + return this; + }; + if (fabric.Canvas) { + fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; } - else { - callback && callback.call(context, url); - } - }; - - fabric.loadSVGFromURL = function(url, callback, reviver) { - url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); - if (url.indexOf('http') !== 0) { - requestFs(url, function(body) { - fabric.loadSVGFromString(body.toString(), callback, reviver); - }); - } - else { - request(url, '', function(body) { - fabric.loadSVGFromString(body, callback, reviver); - }); - } - }; - - fabric.loadSVGFromString = function(string, callback, reviver) { - var doc = new DOMParser().parseFromString(string); - fabric.parseSVGDocument(doc.documentElement, function(results, options) { - callback && callback(results, options); - }, reviver); - }; - - fabric.util.getScript = function(url, callback) { - request(url, '', function(body) { - eval(body); - callback && callback(); - }); - }; - - fabric.Image.fromObject = function(object, callback) { - fabric.util.loadImage(object.src, function(img) { - var oImg = new fabric.Image(img); - - oImg._initConfig(object); - oImg._initFilters(object, function(filters) { - oImg.filters = filters || [ ]; - callback && callback(oImg); - }); - }); - }; - - /** - * Only available when running fabric on node.js - * @param width Canvas width - * @param height Canvas height - * @param {Object} options to pass to FabricCanvas. - * @param {Object} options to pass to NodeCanvas. - * @return {Object} wrapped canvas instance - */ - fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { - nodeCanvasOptions = nodeCanvasOptions || options; - - var canvasEl = fabric.document.createElement('canvas'), - nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); - - // jsdom doesn't create style on canvas element, so here be temp. workaround - canvasEl.style = { }; - - canvasEl.width = nodeCanvas.width; - canvasEl.height = nodeCanvas.height; - - var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, - fabricCanvas = new FabricCanvas(canvasEl, options); - - fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); - fabricCanvas.nodeCanvas = nodeCanvas; - fabricCanvas.Font = Canvas.Font; - - return fabricCanvas; - }; - - /** @ignore */ - fabric.StaticCanvas.prototype.createPNGStream = function() { - return this.nodeCanvas.createPNGStream(); - }; - - fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { - return this.nodeCanvas.createJPEGStream(opts); - }; - - var origSetWidth = fabric.StaticCanvas.prototype.setWidth; - fabric.StaticCanvas.prototype.setWidth = function(width) { - origSetWidth.call(this, width); - this.nodeCanvas.width = width; - return this; - }; - if (fabric.Canvas) { - fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; - } - - var origSetHeight = fabric.StaticCanvas.prototype.setHeight; - fabric.StaticCanvas.prototype.setHeight = function(height) { - origSetHeight.call(this, height); - this.nodeCanvas.height = height; - return this; - }; - if (fabric.Canvas) { - fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; - } - })(); - -/* Footer for requirejs AMD support */ - window.fabric = fabric; -// make sure exports.fabric is always defined when used as 'global' later scopes var exports = exports || {}; + exports.fabric = fabric; -if (typeof define === 'function' && define.amd) { - define([], function() { return fabric }); -} - +if (typeof define === "function" && define.amd) { + define([], function() { + return fabric; + }); +} \ No newline at end of file From 02b0b203436c98627ca503648ae15e82ceec7149 Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 12 Jun 2014 13:10:42 +0100 Subject: [PATCH 13/27] Zoom overlay and background images --- dist/fabric.js | 4 ++-- dist/fabric.min.js | 2 +- dist/fabric.min.js.gz | Bin 79867 -> 79868 bytes dist/fabric.require.js | 4 ++-- src/static_canvas.class.js | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 9fd9c6d7..2aead10f 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -9670,7 +9670,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this.height); } if (this.backgroundImage) { - this.backgroundImage.render(ctx); + this._draw(ctx, this.backgroundImage); } }, @@ -9691,7 +9691,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this.height); } if (this.overlayImage) { - this.overlayImage.render(ctx); + this._draw(ctx, this.overlayImage); } }, diff --git a/dist/fabric.min.js b/dist/fabric.min.js index 4dd89d34..3f694a1d 100644 --- a/dist/fabric.min.js +++ b/dist/fabric.min.js @@ -2,7 +2,7 @@ var fabric=fabric||{version:"1.4.6"};if(typeof exports!=="undefined"){exports.fa var id=getID(target)+"."+getID(listener)+"."+(useCapture?1:0);if(root.Gesture&&root.Gesture._gestureHandlers[type]){id=type+id;if(trigger==="remove"){if(!wrappers[id])return;wrappers[id].remove();delete wrappers[id]}else if(trigger==="add"){if(wrappers[id]){wrappers[id].add();return wrappers[id]}if(configure.useCall&&!root.modifyEventListener){var tmp=listener;listener=function(event,self){for(var key in self)event[key]=self[key];return tmp.call(target,event)}}configure.gesture=type;configure.target=target;configure.listener=listener;configure.fromOverwrite=fromOverwrite;wrappers[id]=root.proxy[type](configure)}return wrappers[id]}else{var eventList=getEventList(type);for(var n=0,eventId;n=conf.maxFingers){var ids=[];for(var sid in conf.tracker)ids.push(sid);self.identifier=ids.join(",");return isTouchStart}var fingers=0;for(var rid in track){if(track[rid].up){delete track[rid];addTouchStart(touch,sid);conf.cancel=true;break}fingers++}if(track[sid])continue;addTouchStart(touch,sid)}else{track=conf.tracker={};self.bbox=conf.bbox=root.getBoundingBox(conf.target);conf.fingers=0;conf.cancel=false;addTouchStart(touch,sid)}}var ids=[];for(var sid in conf.tracker)ids.push(sid);self.identifier=ids.join(",");return isTouchStart};root.pointerEnd=function(event,self,conf,onPointerUp){var touches=event.touches||[];var length=touches.length;var exists={};for(var i=0;i1)return;var pointers=EVENT.changedTouches||root.getCoords(EVENT);var pointer=pointers[0];var bbox=conf.bbox;var newbbox=root.getBoundingBox(conf.target);if(conf.position==="relative"){var ax=pointer.pageX+bbox.scrollLeft-bbox.x1;var ay=pointer.pageY+bbox.scrollTop-bbox.y1}else{var ax=pointer.pageX-bbox.x1;var ay=pointer.pageY-bbox.y1}if(ax>0&&ax0&&ay0&&ax0&&ay1)){self.state=conf.gesture;for(var key in conf.tracker)break;var point=conf.tracker[key];self.x=point.start.x;self.y=point.start.y;conf.listener(event,self)}clearTimeout(timeout);time0=time1=0}};var self=root.pointerSetup(conf);self.state="dblclick";Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.dbltap=root.dbltap;Event.Gesture._gestureHandlers.dblclick=root.dblclick;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.dragElement=function(that,event){root.drag({event:event,target:that,position:"move",listener:function(event,self){that.style.left=self.x+"px";that.style.top=self.y+"px";Event.prevent(event)}})};root.drag=function(conf){conf.gesture="drag";conf.onPointerDown=function(event){if(root.pointerStart(event,self,conf)){if(!conf.monitor){Event.add(conf.doc,"mousemove",conf.onPointerMove);Event.add(conf.doc,"mouseup",conf.onPointerUp)}}conf.onPointerMove(event,"down")};conf.onPointerMove=function(event,state){if(!conf.tracker)return conf.onPointerDown(event);var bbox=conf.bbox;var touches=event.changedTouches||root.getCoords(event);var length=touches.length;for(var i=0;i0?rotate:-rotate;if(typeof touch.DEG2!=="undefined"){if(rotate>0){touch.rotation+=touch.DEG1-touch.DEG2}else{touch.rotation-=touch.DEG1-touch.DEG2}rotation+=touch.rotation}touches.push(touch.move)}self.touches=touches;self.fingers=conf.fingers;self.scale=scale/conf.fingers;self.rotation=rotation/conf.fingers;self.state="change";conf.listener(event,self)};conf.onPointerUp=function(event){var fingers=conf.fingers;if(root.pointerEnd(event,self,conf)){Event.remove(conf.doc,"mousemove",conf.onPointerMove);Event.remove(conf.doc,"mouseup",conf.onPointerUp)}if(fingers===conf.minFingers&&conf.fingersthreshold){var idx=now*ACCELERATION/abs;var span=Math.abs(idx+DELTA.value);if(DELTA.value&&span=fingers){if(velocity1>conf.threshold){start.x/=length;start.y/=length;self.start=start;self.x=endx/length;self.y=endy/length;self.angle=-(((degree1/conf.snap+.5>>0)*conf.snap||360)-360);self.velocity=velocity1;self.fingers=fingers;self.state="swipe";conf.listener(event,self)}}}};var self=root.pointerSetup(conf);Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.swipe=root.swipe;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.longpress=function(conf){conf.gesture="longpress";return root.tap(conf)};root.tap=function(conf){conf.delay=conf.delay||500;conf.timeout=conf.timeout||250;conf.driftDeviance=conf.driftDeviance||10;conf.gesture=conf.gesture||"tap";var timestamp,timeout;conf.onPointerDown=function(event){if(root.pointerStart(event,self,conf)){timestamp=(new Date).getTime();Event.add(conf.doc,"mousemove",conf.onPointerMove).listener(event);Event.add(conf.doc,"mouseup",conf.onPointerUp);if(conf.gesture!=="longpress")return;timeout=setTimeout(function(){if(event.cancelBubble&&++event.bubble>1)return;var fingers=0;for(var key in conf.tracker){var point=conf.tracker[key];if(point.end===true)return;if(conf.cancel)return;fingers++}if(conf.minFingers<=fingers&&conf.maxFingers>=fingers){self.state="start";self.fingers=fingers;self.x=point.start.x;self.y=point.start.y;conf.listener(event,self)}},conf.delay)}};conf.onPointerMove=function(event){var bbox=conf.bbox;var touches=event.changedTouches||root.getCoords(event);var length=touches.length;for(var i=0;i0&&x0&&y1)return;if(conf.gesture==="longpress"){if(self.state==="start"){self.state="end";conf.listener(event,self)}return}if(conf.cancel)return;if((new Date).getTime()-timestamp>conf.timeout)return;var fingers=conf.gestureFingers;if(conf.minFingers<=fingers&&conf.maxFingers>=fingers){self.state="tap";self.fingers=conf.gestureFingers;conf.listener(event,self)}}};var self=root.pointerSetup(conf);Event.add(conf.target,"mousedown",conf.onPointerDown);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.tap=root.tap;Event.Gesture._gestureHandlers.longpress=root.longpress;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.wheel=function(conf){var interval;var timeout=conf.timeout||150;var count=0;var self={gesture:"wheel",state:"start",wheelDelta:0,target:conf.target,listener:conf.listener,preventElasticBounce:function(){var target=this.target;var scrollTop=target.scrollTop;var top=scrollTop+target.offsetHeight;var height=target.scrollHeight;if(top===height&&this.wheelDelta<=0)Event.cancel(event);else if(scrollTop===0&&this.wheelDelta>=0)Event.cancel(event);Event.stop(event)},add:function(){conf.target[add](type,onMouseWheel,false)},remove:function(){conf.target[remove](type,onMouseWheel,false)}};var onMouseWheel=function(event){event=event||window.event;self.state=count++?"change":"start";self.wheelDelta=event.detail?event.detail*-20:event.wheelDelta;conf.listener(event,self);clearTimeout(interval);interval=setTimeout(function(){count=0;self.state="end";self.wheelDelta=0;conf.listener(event,self)},timeout)};var add=document.addEventListener?"addEventListener":"attachEvent";var remove=document.removeEventListener?"removeEventListener":"detachEvent";var type=Event.getEventSupport("mousewheel")?"mousewheel":"DOMMouseScroll";conf.target[add](type,onMouseWheel,false);return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.wheel=root.wheel;return root}(Event.proxy);if(typeof Event==="undefined")var Event={};if(typeof Event.proxy==="undefined")Event.proxy={};Event.proxy=function(root){"use strict";root.orientation=function(conf){var self={gesture:"orientationchange",previous:null,current:window.orientation,target:conf.target,listener:conf.listener,remove:function(){window.removeEventListener("orientationchange",onOrientationChange,false)}};var onOrientationChange=function(e){self.previous=self.current;self.current=window.orientation;if(self.previous!==null&&self.previous!=self.current){conf.listener(e,self);return}};if(window.DeviceOrientationEvent){window.addEventListener("orientationchange",onOrientationChange,false)}return self};Event.Gesture=Event.Gesture||{};Event.Gesture._gestureHandlers=Event.Gesture._gestureHandlers||{};Event.Gesture._gestureHandlers.orientation=root.orientation;return root}(Event.proxy);(function(){function _removeEventListener(eventName,handler){if(!this.__eventListeners[eventName])return;if(handler){fabric.util.removeFromArray(this.__eventListeners[eventName],handler)}else{this.__eventListeners[eventName].length=0}}function observe(eventName,handler){if(!this.__eventListeners){this.__eventListeners={}}if(arguments.length===1){for(var prop in eventName){this.on(prop,eventName[prop]) }}else{if(!this.__eventListeners[eventName]){this.__eventListeners[eventName]=[]}this.__eventListeners[eventName].push(handler)}return this}function stopObserving(eventName,handler){if(!this.__eventListeners)return;if(arguments.length===0){this.__eventListeners={}}else if(arguments.length===1&&typeof arguments[0]==="object"){for(var prop in eventName){_removeEventListener.call(this,prop,eventName[prop])}}else{_removeEventListener.call(this,eventName,handler)}return this}function fire(eventName,options){if(!this.__eventListeners)return;var listenersForEvent=this.__eventListeners[eventName];if(!listenersForEvent)return;for(var i=0,len=listenersForEvent.length;i-1},complexity:function(){return this.getObjects().reduce(function(memo,current){memo+=current.complexity?current.complexity():0;return memo},0)}};(function(global){var sqrt=Math.sqrt,atan2=Math.atan2,PiBy180=Math.PI/180;fabric.util={removeFromArray:function(array,value){var idx=array.indexOf(value);if(idx!==-1){array.splice(idx,1)}return array},getRandomInt:function(min,max){return Math.floor(Math.random()*(max-min+1))+min},degreesToRadians:function(degrees){return degrees*PiBy180},radiansToDegrees:function(radians){return radians/PiBy180},rotatePoint:function(point,origin,radians){var sin=Math.sin(radians),cos=Math.cos(radians);point.subtractEquals(origin);var rx=point.x*cos-point.y*sin,ry=point.x*sin+point.y*cos;return new fabric.Point(rx,ry).addEquals(origin)},transformPoint:function(p,t,ignoreOffset){if(ignoreOffset){return new fabric.Point(t[0]*p.x+t[1]*p.y,t[2]*p.x+t[3]*p.y)}return new fabric.Point(t[0]*p.x+t[1]*p.y+t[4],t[2]*p.x+t[3]*p.y+t[5])},invertTransform:function(t){var r=t.slice(),a=1/(t[0]*t[3]-t[1]*t[2]);r=[a*t[3],-a*t[1],-a*t[2],a*t[0],0,0];var o=fabric.util.transformPoint({x:t[4],y:t[5]},r);r[4]=-o.x;r[5]=-o.y;return r},toFixed:function(number,fractionDigits){return parseFloat(Number(number).toFixed(fractionDigits))},falseFunction:function(){return false},getKlass:function(type,namespace){type=fabric.util.string.camelize(type.charAt(0).toUpperCase()+type.slice(1));return fabric.util.resolveNamespace(namespace)[type]},resolveNamespace:function(namespace){if(!namespace){return fabric}var parts=namespace.split("."),len=parts.length,obj=global||fabric.window;for(var i=0;i1){object=new fabric.PathGroup(elements,options)}else{object=elements[0]}if(typeof path!=="undefined"){object.setSourcePath(path)}return object},populateWithProperties:function(source,destination,properties){if(properties&&Object.prototype.toString.call(properties)==="[object Array]"){for(var i=0,len=properties.length;ix){x+=da[di++%dc];if(x>len){x=len}ctx[draw?"lineTo":"moveTo"](x,0);draw=!draw}ctx.restore()},createCanvasElement:function(canvasEl){canvasEl||(canvasEl=fabric.document.createElement("canvas"));if(!canvasEl.getContext&&typeof G_vmlCanvasManager!=="undefined"){G_vmlCanvasManager.initElement(canvasEl)}return canvasEl},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(klass){var proto=klass.prototype;for(var i=proto.stateProperties.length;i--;){var propName=proto.stateProperties[i],capitalizedPropName=propName.charAt(0).toUpperCase()+propName.slice(1),setterName="set"+capitalizedPropName,getterName="get"+capitalizedPropName;if(!proto[getterName]){proto[getterName]=function(property){return new Function('return this.get("'+property+'")')}(propName)}if(!proto[setterName]){proto[setterName]=function(property){return new Function("value",'return this.set("'+property+'", value)')}(propName)}}},clipContext:function(receiver,ctx){ctx.save();ctx.beginPath();receiver.clipTo(ctx);ctx.clip()},multiplyTransformMatrices:function(matrixA,matrixB){var a=[[matrixA[0],matrixA[2],matrixA[4]],[matrixA[1],matrixA[3],matrixA[5]],[0,0,1]],b=[[matrixB[0],matrixB[2],matrixB[4]],[matrixB[1],matrixB[3],matrixB[5]],[0,0,1]],result=[];for(var r=0;r<3;r++){result[r]=[];for(var c=0;c<3;c++){var sum=0;for(var k=0;k<3;k++){sum+=a[r][k]*b[k][c]}result[r][c]=sum}}return[result[0][0],result[1][0],result[0][1],result[1][1],result[0][2],result[1][2]]},getFunctionBody:function(fn){return(String(fn).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},normalizePoints:function(points,options){var minX=fabric.util.array.min(points,"x"),minY=fabric.util.array.min(points,"y");minX=minX<0?minX:0;minY=minX<0?minY:0;for(var i=0,len=points.length;i0){if(x>tolerance){x-=tolerance}else{x=0}if(y>tolerance){y-=tolerance}else{y=0}}var _isTransparent=true,imageData=ctx.getImageData(x,y,tolerance*2||1,tolerance*2||1);for(var i=3,l=imageData.data.length;i0&&sweep===0){thArc-=2*Math.PI}var segments=Math.ceil(Math.abs(thArc/(Math.PI*.5+.001))),result=[];for(var i=0;i1){pl=Math.sqrt(pl);rx*=pl;ry*=pl}var a00=cosTh/rx,a01=sinTh/rx,a10=-sinTh/ry,a11=cosTh/ry;return{x0:a00*ox+a01*oy,y0:a10*ox+a11*oy,x1:a00*x+a01*y,y1:a10*x+a11*y,sinTh:sinTh,cosTh:cosTh}}function segmentToBezier(cx,cy,th0,th1,rx,ry,sinTh,cosTh){argsString=_join.call(arguments);if(segmentToBezierCache[argsString]){return segmentToBezierCache[argsString]}var a00=cosTh*rx,a01=-sinTh*ry,a10=sinTh*rx,a11=cosTh*ry,thHalf=.5*(th1-th0),t=8/3*Math.sin(thHalf*.5)*Math.sin(thHalf*.5)/Math.sin(thHalf),x1=cx+Math.cos(th0)-t*Math.sin(th0),y1=cy+Math.sin(th0)+t*Math.cos(th0),x3=cx+Math.cos(th1),y3=cy+Math.sin(th1),x2=x3+t*Math.sin(th1),y2=y3-t*Math.cos(th1);segmentToBezierCache[argsString]=[a00*x1+a01*y1,a10*x1+a11*y1,a00*x2+a01*y2,a10*x2+a11*y2,a00*x3+a01*y3,a10*x3+a11*y3];return segmentToBezierCache[argsString]}fabric.util.drawArc=function(ctx,x,y,coords){var rx=coords[0],ry=coords[1],rot=coords[2],large=coords[3],sweep=coords[4],ex=coords[5],ey=coords[6],segs=arcToSegments(ex,ey,rx,ry,large,sweep,rot,x,y);for(var i=0;i>>0;if(len===0){return-1}var n=0;if(arguments.length>0){n=Number(arguments[1]);if(n!==n){n=0}else if(n!==0&&n!==Number.POSITIVE_INFINITY&&n!==Number.NEGATIVE_INFINITY){n=(n>0||-1)*Math.floor(Math.abs(n))}}if(n>=len){return-1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k>>0;i>>0;i>>0;i>>0;i>>0;i>>0,i=0,rv;if(arguments.length>1){rv=arguments[1]}else{do{if(i in this){rv=this[i++];break}if(++i>=len){throw new TypeError}}while(true)}for(;i=value2})}function min(array,byProperty){return find(array,byProperty,function(value1,value2){return value1/g,">")}fabric.util.string={camelize:camelize,capitalize:capitalize,escapeXml:escapeXml}})();(function(){var slice=Array.prototype.slice,apply=Function.prototype.apply,Dummy=function(){};if(!Function.prototype.bind){Function.prototype.bind=function(thisArg){var _this=this,args=slice.call(arguments,1),bound;if(args.length){bound=function(){return apply.call(_this,this instanceof Dummy?this:thisArg,args.concat(slice.call(arguments)))}}else{bound=function(){return apply.call(_this,this instanceof Dummy?this:thisArg,arguments)}}Dummy.prototype=this.prototype;bound.prototype=new Dummy;return bound}}})();(function(){var slice=Array.prototype.slice,emptyFunction=function(){},IS_DONTENUM_BUGGY=function(){for(var p in{toString:1}){if(p==="toString")return false}return true}(),addMethods=function(klass,source,parent){for(var property in source){if(property in klass.prototype&&typeof klass.prototype[property]==="function"&&(source[property]+"").indexOf("callSuper")>-1){klass.prototype[property]=function(property){return function(){var superclass=this.constructor.superclass;this.constructor.superclass=parent;var returnValue=source[property].apply(this,arguments);this.constructor.superclass=superclass;if(property!=="initialize"){return returnValue}}}(property)}else{klass.prototype[property]=source[property]}if(IS_DONTENUM_BUGGY){if(source.toString!==Object.prototype.toString){klass.prototype.toString=source.toString}if(source.valueOf!==Object.prototype.valueOf){klass.prototype.valueOf=source.valueOf}}}};function Subclass(){}function callSuper(methodName){var fn=this.constructor.superclass.prototype[methodName];return arguments.length>1?fn.apply(this,slice.call(arguments,1)):fn.call(this)}function createClass(){var parent=null,properties=slice.call(arguments,0);if(typeof properties[0]==="function"){parent=properties.shift()}function klass(){this.initialize.apply(this,arguments)}klass.superclass=parent;klass.subclasses=[];if(parent){Subclass.prototype=parent.prototype;klass.prototype=new Subclass;parent.subclasses.push(klass)}for(var i=0,length=properties.length;i-1?setOpacity(element,styles.match(/opacity:\s*(\d?\.?\d*)/)[1]):element}for(var property in styles){if(property==="opacity"){setOpacity(element,styles[property])}else{var normalizedProperty=property==="float"||property==="cssFloat"?typeof elementStyle.styleFloat==="undefined"?"cssFloat":"styleFloat":property;elementStyle[normalizedProperty]=styles[property]}}return element}var parseEl=fabric.document.createElement("div"),supportsOpacity=typeof parseEl.style.opacity==="string",supportsFilters=typeof parseEl.style.filter==="string",reOpacity=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,setOpacity=function(element){return element};if(supportsOpacity){setOpacity=function(element,value){element.style.opacity=value;return element}}else if(supportsFilters){setOpacity=function(element,value){var es=element.style;if(element.currentStyle&&!element.currentStyle.hasLayout){es.zoom=1}if(reOpacity.test(es.filter)){value=value>=.9999?"":"alpha(opacity="+value*100+")";es.filter=es.filter.replace(reOpacity,value)}else{es.filter+=" alpha(opacity="+value*100+")"}return element}}fabric.util.setStyle=setStyle})();(function(){var _slice=Array.prototype.slice;function getById(id){return typeof id==="string"?fabric.document.getElementById(id):id}var sliceCanConvertNodelists,toArray=function(arrayLike){return _slice.call(arrayLike,0)};try{sliceCanConvertNodelists=toArray(fabric.document.childNodes)instanceof Array}catch(err){}if(!sliceCanConvertNodelists){toArray=function(arrayLike){var arr=new Array(arrayLike.length),i=arrayLike.length;while(i--){arr[i]=arrayLike[i]}return arr}}function makeElement(tagName,attributes){var el=fabric.document.createElement(tagName);for(var prop in attributes){if(prop==="class"){el.className=attributes[prop]}else if(prop==="for"){el.htmlFor=attributes[prop]}else{el.setAttribute(prop,attributes[prop])}}return el}function addClass(element,className){if(element&&(" "+element.className+" ").indexOf(" "+className+" ")===-1){element.className+=(element.className?" ":"")+className}}function wrapElement(element,wrapper,attributes){if(typeof wrapper==="string"){wrapper=makeElement(wrapper,attributes)}if(element.parentNode){element.parentNode.replaceChild(wrapper,element)}wrapper.appendChild(element);return wrapper}function getScrollLeftTop(element,upperCanvasEl){var firstFixedAncestor,origElement,left=0,top=0,docElement=fabric.document.documentElement,body=fabric.document.body||{scrollLeft:0,scrollTop:0};origElement=element;while(element&&element.parentNode&&!firstFixedAncestor){element=element.parentNode;if(element!==fabric.document&&fabric.util.getElementStyle(element,"position")==="fixed"){firstFixedAncestor=element}if(element!==fabric.document&&origElement!==upperCanvasEl&&fabric.util.getElementStyle(element,"position")==="absolute"){left=0;top=0}else if(element===fabric.document){left=body.scrollLeft||docElement.scrollLeft||0;top=body.scrollTop||docElement.scrollTop||0}else{left+=element.scrollLeft||0;top+=element.scrollTop||0}}return{left:left,top:top}}function getElementOffset(element){var docElem,doc=element&&element.ownerDocument,box={left:0,top:0},offset={left:0,top:0},scrollLeftTop,offsetAttributes={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!doc){return{left:0,top:0}}for(var attr in offsetAttributes){offset[offsetAttributes[attr]]+=parseInt(getElementStyle(element,attr),10)||0}docElem=doc.documentElement;if(typeof element.getBoundingClientRect!=="undefined"){box=element.getBoundingClientRect()}scrollLeftTop=fabric.util.getScrollLeftTop(element,null);return{left:box.left+scrollLeftTop.left-(docElem.clientLeft||0)+offset.left,top:box.top+scrollLeftTop.top-(docElem.clientTop||0)+offset.top}}var getElementStyle;if(fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle){getElementStyle=function(element,attr){return fabric.document.defaultView.getComputedStyle(element,null)[attr]}}else{getElementStyle=function(element,attr){var value=element.style[attr];if(!value&&element.currentStyle){value=element.currentStyle[attr]}return value}}(function(){var style=fabric.document.documentElement.style,selectProp="userSelect"in style?"userSelect":"MozUserSelect"in style?"MozUserSelect":"WebkitUserSelect"in style?"WebkitUserSelect":"KhtmlUserSelect"in style?"KhtmlUserSelect":"";function makeElementUnselectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=fabric.util.falseFunction}if(selectProp){element.style[selectProp]="none"}else if(typeof element.unselectable==="string"){element.unselectable="on"}return element}function makeElementSelectable(element){if(typeof element.onselectstart!=="undefined"){element.onselectstart=null}if(selectProp){element.style[selectProp]=""}else if(typeof element.unselectable==="string"){element.unselectable=""}return element}fabric.util.makeElementUnselectable=makeElementUnselectable;fabric.util.makeElementSelectable=makeElementSelectable})();(function(){function getScript(url,callback){var headEl=fabric.document.getElementsByTagName("head")[0],scriptEl=fabric.document.createElement("script"),loading=true;scriptEl.onload=scriptEl.onreadystatechange=function(e){if(loading){if(typeof this.readyState==="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;loading=false;callback(e||fabric.window.event);scriptEl=scriptEl.onload=scriptEl.onreadystatechange=null}};scriptEl.src=url;headEl.appendChild(scriptEl)}fabric.util.getScript=getScript})();fabric.util.getById=getById;fabric.util.toArray=toArray;fabric.util.makeElement=makeElement;fabric.util.addClass=addClass;fabric.util.wrapElement=wrapElement;fabric.util.getScrollLeftTop=getScrollLeftTop;fabric.util.getElementOffset=getElementOffset;fabric.util.getElementStyle=getElementStyle})();(function(){function addParamToUrl(url,param){return url+(/\?/.test(url)?"&":"?")+param}var makeXHR=function(){var factories=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var i=factories.length;i--;){try{var req=factories[i]();if(req){return factories[i]}}catch(err){}}}();function emptyFn(){}function request(url,options){options||(options={});var method=options.method?options.method.toUpperCase():"GET",onComplete=options.onComplete||function(){},xhr=makeXHR(),body;xhr.onreadystatechange=function(){if(xhr.readyState===4){onComplete(xhr);xhr.onreadystatechange=emptyFn}};if(method==="GET"){body=null;if(typeof options.parameters==="string"){url=addParamToUrl(url,options.parameters)}}xhr.open(method,url,true);if(method==="POST"||method==="PUT"){xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")}xhr.send(body);return xhr}fabric.util.request=request})();fabric.log=function(){};fabric.warn=function(){};if(typeof console!=="undefined"){["log","warn"].forEach(function(methodName){if(typeof console[methodName]!=="undefined"&&console[methodName].apply){fabric[methodName]=function(){return console[methodName].apply(console,arguments)}}})}(function(){function animate(options){requestAnimFrame(function(timestamp){options||(options={});var start=timestamp||+new Date,duration=options.duration||500,finish=start+duration,time,onChange=options.onChange||function(){},abort=options.abort||function(){return false},easing=options.easing||function(t,b,c,d){return-c*Math.cos(t/d*(Math.PI/2))+c+b},startValue="startValue"in options?options.startValue:0,endValue="endValue"in options?options.endValue:100,byValue=options.byValue||endValue-startValue;options.onStart&&options.onStart();(function tick(ticktime){time=ticktime||+new Date;var currentTime=time>finish?duration:time-start;if(abort()){options.onComplete&&options.onComplete();return}onChange(easing(currentTime,startValue,byValue,duration));if(time>finish){options.onComplete&&options.onComplete();return}requestAnimFrame(tick)})(start)})}var _requestAnimFrame=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(callback){fabric.window.setTimeout(callback,1e3/60)};function requestAnimFrame(){return _requestAnimFrame.apply(fabric.window,arguments)}fabric.util.animate=animate;fabric.util.requestAnimFrame=requestAnimFrame})();(function(){function normalize(a,c,p,s){if(a1){matrices.shift();combinedMatrix=fabric.util.multiplyTransformMatrices(combinedMatrix,matrices[0])}return combinedMatrix}}();function parseFontDeclaration(value,oStyle){var match=value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);if(!match)return;var fontStyle=match[1],fontWeight=match[3],fontSize=match[4],lineHeight=match[5],fontFamily=match[6];if(fontStyle){oStyle.fontStyle=fontStyle}if(fontWeight){oStyle.fontWeight=isNaN(parseFloat(fontWeight))?fontWeight:parseFloat(fontWeight)}if(fontSize){oStyle.fontSize=parseFloat(fontSize)}if(fontFamily){oStyle.fontFamily=fontFamily}if(lineHeight){oStyle.lineHeight=lineHeight==="normal"?1:lineHeight}}function parseStyleString(style,oStyle){var attr,value;style.replace(/;$/,"").split(";").forEach(function(chunk){var pair=chunk.split(":");attr=normalizeAttr(pair[0].trim().toLowerCase());value=normalizeValue(attr,pair[1].trim());if(attr==="font"){parseFontDeclaration(value,oStyle)}else{oStyle[attr]=value}})}function parseStyleObject(style,oStyle){var attr,value;for(var prop in style){if(typeof style[prop]==="undefined")continue;attr=normalizeAttr(prop.toLowerCase());value=normalizeValue(attr,style[prop]);if(attr==="font"){parseFontDeclaration(value,oStyle)}else{oStyle[attr]=value}}}function getGlobalStylesForElement(element){var nodeName=element.nodeName,className=element.getAttribute("class"),id=element.getAttribute("id"),styles={};for(var rule in fabric.cssRules){var ruleMatchesElement=className&&new RegExp("^\\."+className).test(rule)||id&&new RegExp("^#"+id).test(rule)||new RegExp("^"+nodeName).test(rule);if(ruleMatchesElement){for(var property in fabric.cssRules[rule]){styles[property]=fabric.cssRules[rule][property]}}}return styles}fabric.parseSVGDocument=function(){var reAllowedSVGTagNames=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",reViewBoxAttrValue=new RegExp("^"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*,?"+"\\s*("+reNum+"+)\\s*"+"$");function hasAncestorWithNodeName(element,nodeName){while(element&&(element=element.parentNode)){if(nodeName.test(element.nodeName)){return true}}return false}return function(doc,callback,reviver){if(!doc)return;var startTime=new Date,descendants=fabric.util.toArray(doc.getElementsByTagName("*"));if(descendants.length===0&&fabric.isLikelyNode){descendants=doc.selectNodes('//*[name(.)!="svg"]');var arr=[];for(var i=0,len=descendants.length;i','')}}extend(fabric,{resolveGradients:function(instances){for(var i=instances.length;i--;){var instanceFillValue=instances[i].get("fill");if(!/^url\(/.test(instanceFillValue))continue;var gradientId=instanceFillValue.slice(5,instanceFillValue.length-1);if(fabric.gradientDefs[gradientId]){instances[i].set("fill",fabric.Gradient.fromElement(fabric.gradientDefs[gradientId],instances[i]))}}},getGradientDefs:function(doc){var linearGradientEls=doc.getElementsByTagName("linearGradient"),radialGradientEls=doc.getElementsByTagName("radialGradient"),el,i,gradientDefs={};i=linearGradientEls.length;for(;i--;){el=linearGradientEls[i];gradientDefs[el.getAttribute("id")]=el}i=radialGradientEls.length;for(;i--;){el=radialGradientEls[i];gradientDefs[el.getAttribute("id")]=el}return gradientDefs},parseAttributes:function(element,attributes){if(!element){return}var value,parentAttributes={};if(element.parentNode&&/^g$/i.test(element.parentNode.nodeName)){parentAttributes=fabric.parseAttributes(element.parentNode,attributes)}var ownAttributes=attributes.reduce(function(memo,attr){value=element.getAttribute(attr);if(value){attr=normalizeAttr(attr);value=normalizeValue(attr,value,parentAttributes);memo[attr]=value}return memo},{});ownAttributes=extend(ownAttributes,extend(getGlobalStylesForElement(element),fabric.parseStyleAttribute(element)));return _setStrokeFillOpacity(extend(parentAttributes,ownAttributes))},parseElements:function(elements,callback,options,reviver){new fabric.ElementsParser(elements,callback,options,reviver).parse()},parseStyleAttribute:function(element){var oStyle={},style=element.getAttribute("style");if(!style){return oStyle}if(typeof style==="string"){parseStyleString(style,oStyle)}else{parseStyleObject(style,oStyle)}return oStyle},parsePointsAttribute:function(points){if(!points)return null;points=points.trim();var asPairs=points.indexOf(",")>-1;points=points.split(/\s+/);var parsedPoints=[],i,len;if(asPairs){i=0;len=points.length;for(;i/i,""))}if(!xml||!xml.documentElement)return;fabric.parseSVGDocument(xml.documentElement,function(results,options){svgCache.set(url,{objects:fabric.util.array.invoke(results,"toObject"),options:options});callback(results,options)},reviver)}},loadSVGFromString:function(string,callback,reviver){string=string.trim();var doc;if(typeof DOMParser!=="undefined"){var parser=new DOMParser;if(parser&&parser.parseFromString){doc=parser.parseFromString(string,"text/xml")}}else if(fabric.window.ActiveXObject){doc=new ActiveXObject("Microsoft.XMLDOM");doc.async="false";doc.loadXML(string.replace(//i,""))}fabric.parseSVGDocument(doc.documentElement,function(results,options){callback(results,options)},reviver)},createSVGFontFacesMarkup:function(objects){var markup="";for(var i=0,len=objects.length;i',"",""].join("")}return markup},createSVGRefElementsMarkup:function(canvas){var markup=[];_createSVGPattern(markup,canvas,"backgroundColor");_createSVGPattern(markup,canvas,"overlayColor");return markup.join("")}})})(typeof exports!=="undefined"?exports:this);fabric.ElementsParser=function(elements,callback,options,reviver){this.elements=elements;this.callback=callback;this.options=options;this.reviver=reviver};fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length);this.numElements=this.elements.length;this.createObjects()};fabric.ElementsParser.prototype.createObjects=function(){for(var i=0,len=this.elements.length;ithat.x&&this.y>that.y},gte:function(that){return this.x>=that.x&&this.y>=that.y},lerp:function(that,t){return new Point(this.x+(that.x-this.x)*t,this.y+(that.y-this.y)*t)},distanceFrom:function(that){var dx=this.x-that.x,dy=this.y-that.y;return Math.sqrt(dx*dx+dy*dy)},midPointFrom:function(that){return new Point(this.x+(that.x-this.x)/2,this.y+(that.y-this.y)/2)},min:function(that){return new Point(Math.min(this.x,that.x),Math.min(this.y,that.y))},max:function(that){return new Point(Math.max(this.x,that.x),Math.max(this.y,that.y))},toString:function(){return this.x+","+this.y},setXY:function(x,y){this.x=x;this.y=y},setFromPoint:function(that){this.x=that.x;this.y=that.y},swap:function(that){var x=this.x,y=this.y;this.x=that.x;this.y=that.y;that.x=x;that.y=y}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Intersection){fabric.warn("fabric.Intersection is already defined");return}function Intersection(status){this.status=status;this.points=[]}fabric.Intersection=Intersection;fabric.Intersection.prototype={appendPoint:function(point){this.points.push(point)},appendPoints:function(points){this.points=this.points.concat(points)}};fabric.Intersection.intersectLineLine=function(a1,a2,b1,b2){var result,uaT=(b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x),ubT=(a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x),uB=(b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);if(uB!==0){var ua=uaT/uB,ub=ubT/uB;if(0<=ua&&ua<=1&&0<=ub&&ub<=1){result=new Intersection("Intersection");result.points.push(new fabric.Point(a1.x+ua*(a2.x-a1.x),a1.y+ua*(a2.y-a1.y)))}else{result=new Intersection}}else{if(uaT===0||ubT===0){result=new Intersection("Coincident")}else{result=new Intersection("Parallel")}}return result};fabric.Intersection.intersectLinePolygon=function(a1,a2,points){var result=new Intersection,length=points.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonPolygon=function(points1,points2){var result=new Intersection,length=points1.length;for(var i=0;i0){result.status="Intersection"}return result};fabric.Intersection.intersectPolygonRectangle=function(points,r1,r2){var min=r1.min(r2),max=r1.max(r2),topRight=new fabric.Point(max.x,min.y),bottomLeft=new fabric.Point(min.x,max.y),inter1=Intersection.intersectLinePolygon(min,topRight,points),inter2=Intersection.intersectLinePolygon(topRight,max,points),inter3=Intersection.intersectLinePolygon(max,bottomLeft,points),inter4=Intersection.intersectLinePolygon(bottomLeft,min,points),result=new Intersection;result.appendPoints(inter1.points);result.appendPoints(inter2.points);result.appendPoints(inter3.points);result.appendPoints(inter4.points);if(result.points.length>0){result.status="Intersection"}return result}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Color){fabric.warn("fabric.Color is already defined.");return}function Color(color){if(!color){this.setSource([0,0,0,1])}else{this._tryParsingColor(color)}}fabric.Color=Color;fabric.Color.prototype={_tryParsingColor:function(color){var source;if(color in Color.colorNameMap){color=Color.colorNameMap[color]}if(color==="transparent"){this.setSource([255,255,255,0]);return}source=Color.sourceFromHex(color);if(!source){source=Color.sourceFromRgb(color)}if(!source){source=Color.sourceFromHsl(color)}if(source){this.setSource(source)}},_rgbToHsl:function(r,g,b){r/=255,g/=255,b/=255;var h,s,l,max=fabric.util.array.max([r,g,b]),min=fabric.util.array.min([r,g,b]);l=(max+min)/2;if(max===min){h=s=0}else{var d=max-min;s=l>.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g1){t-=1}if(t<1/6){return p+(q-p)*6*t}if(t<1/2){return q}if(t<2/3){return p+(q-p)*(2/3-t)*6}return p}fabric.Color.fromRgb=function(color){return Color.fromSource(Color.sourceFromRgb(color))};fabric.Color.sourceFromRgb=function(color){var match=color.match(Color.reRGBa);if(match){var r=parseInt(match[1],10)/(/%$/.test(match[1])?100:1)*(/%$/.test(match[1])?255:1),g=parseInt(match[2],10)/(/%$/.test(match[2])?100:1)*(/%$/.test(match[2])?255:1),b=parseInt(match[3],10)/(/%$/.test(match[3])?100:1)*(/%$/.test(match[3])?255:1);return[parseInt(r,10),parseInt(g,10),parseInt(b,10),match[4]?parseFloat(match[4]):1]}};fabric.Color.fromRgba=Color.fromRgb;fabric.Color.fromHsl=function(color){return Color.fromSource(Color.sourceFromHsl(color))};fabric.Color.sourceFromHsl=function(color){var match=color.match(Color.reHSLa);if(!match)return;var h=(parseFloat(match[1])%360+360)%360/360,s=parseFloat(match[2])/(/%$/.test(match[2])?100:1),l=parseFloat(match[3])/(/%$/.test(match[3])?100:1),r,g,b;if(s===0){r=g=b=l}else{var q=l<=.5?l*(s+1):l+s-l*s,p=l*2-q;r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3)}return[Math.round(r*255),Math.round(g*255),Math.round(b*255),match[4]?parseFloat(match[4]):1]};fabric.Color.fromHsla=Color.fromHsl;fabric.Color.fromHex=function(color){return Color.fromSource(Color.sourceFromHex(color))};fabric.Color.sourceFromHex=function(color){if(color.match(Color.reHex)){var value=color.slice(color.indexOf("#")+1),isShortNotation=value.length===3,r=isShortNotation?value.charAt(0)+value.charAt(0):value.substring(0,2),g=isShortNotation?value.charAt(1)+value.charAt(1):value.substring(2,4),b=isShortNotation?value.charAt(2)+value.charAt(2):value.substring(4,6);return[parseInt(r,16),parseInt(g,16),parseInt(b,16),1]}};fabric.Color.fromSource=function(source){var oColor=new Color;oColor.setSource(source);return oColor}})(typeof exports!=="undefined"?exports:this);(function(){function getColorStop(el){var style=el.getAttribute("style"),offset=el.getAttribute("offset"),color,opacity;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")}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%"}}fabric.Gradient=fabric.util.createClass({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()},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},toObject:function(){return{type:this.type,coords:this.coords,gradientUnits:this.gradientUnits,colorStops:this.colorStops}},toSVG:function(object,normalize){var coords=fabric.util.object.clone(this.coords),markup;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')}markup.push(this.type==="linear"?"":"");return markup.join("")},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'+''+""},toLive:function(ctx){var source=typeof this.source==="function"?this.source():this.source;if(!source){return""}if(typeof source.src!=="undefined"){if(!source.complete){return""}if(source.naturalWidth===0||source.naturalHeight===0){return""}}return ctx.createPattern(source,this.repeat)}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Shadow){fabric.warn("fabric.Shadow is already defined.");return}fabric.Shadow=fabric.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:false,includeDefaultValues:true,initialize:function(options){if(typeof options==="string"){options=this._parseShadow(options)}for(var prop in options){this[prop]=options[prop]}this.id=fabric.Object.__uid++},_parseShadow:function(shadow){var shadowStr=shadow.trim(),offsetsAndBlur=fabric.Shadow.reOffsetsAndBlur.exec(shadowStr)||[],color=shadowStr.replace(fabric.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:color.trim(),offsetX:parseInt(offsetsAndBlur[1],10)||0,offsetY:parseInt(offsetsAndBlur[2],10)||0,blur:parseInt(offsetsAndBlur[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(object){var mode="SourceAlpha";if(object&&(object.fill===this.color||object.stroke===this.color)){mode="SourceGraphic"}return''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues){return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY}}var obj={},proto=fabric.Shadow.prototype;if(this.color!==proto.color){obj.color=this.color}if(this.blur!==proto.blur){obj.blur=this.blur}if(this.offsetX!==proto.offsetX){obj.offsetX=this.offsetX}if(this.offsetY!==proto.offsetY){obj.offsetY=this.offsetY}return obj}});fabric.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/})(typeof exports!=="undefined"?exports:this);(function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var extend=fabric.util.object.extend,getElementOffset=fabric.util.getElementOffset,removeFromArray=fabric.util.removeFromArray,CANVAS_INIT_ERROR=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(el,options){options||(options={});this._initStatic(el,options);fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:true,stateful:true,renderOnAddRemove:true,clipTo:null,controlsAboveOverlay:false,allowTouchScrolling:false,imageSmoothingEnabled:true,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(el,options){this._objects=[];this._createLowerCanvas(el);this._initOptions(options);this._setImageSmoothing();if(options.overlayImage){this.setOverlayImage(options.overlayImage,this.renderAll.bind(this))}if(options.backgroundImage){this.setBackgroundImage(options.backgroundImage,this.renderAll.bind(this))}if(options.backgroundColor){this.setBackgroundColor(options.backgroundColor,this.renderAll.bind(this))}if(options.overlayColor){this.setOverlayColor(options.overlayColor,this.renderAll.bind(this))}this.calcOffset()},calcOffset:function(){this._offset=getElementOffset(this.lowerCanvasEl);return this},setOverlayImage:function(image,callback,options){return this.__setBgOverlayImage("overlayImage",image,callback,options)},setBackgroundImage:function(image,callback,options){return this.__setBgOverlayImage("backgroundImage",image,callback,options)},setOverlayColor:function(overlayColor,callback){return this.__setBgOverlayColor("overlayColor",overlayColor,callback)},setBackgroundColor:function(backgroundColor,callback){return this.__setBgOverlayColor("backgroundColor",backgroundColor,callback)},_setImageSmoothing:function(){var ctx=this.getContext();ctx.imageSmoothingEnabled=this.imageSmoothingEnabled;ctx.webkitImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.mozImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.msImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(property,image,callback,options){if(typeof image==="string"){fabric.util.loadImage(image,function(img){this[property]=new fabric.Image(img,options);callback&&callback()},this)}else{this[property]=image;callback&&callback()}return this},__setBgOverlayColor:function(property,color,callback){if(color.source){var _this=this;fabric.util.loadImage(color.source,function(img){_this[property]=new fabric.Pattern({source:img,repeat:color.repeat,offsetX:color.offsetX,offsetY:color.offsetY});callback&&callback()})}else{this[property]=color;callback&&callback()}return this},_createCanvasElement:function(){var element=fabric.document.createElement("canvas");if(!element.style){element.style={}}if(!element){throw CANVAS_INIT_ERROR}this._initCanvasElement(element);return element},_initCanvasElement:function(element){fabric.util.createCanvasElement(element);if(typeof element.getContext==="undefined"){throw CANVAS_INIT_ERROR}},_initOptions:function(options){for(var prop in options){this[prop]=options[prop]}this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0;this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width;this.lowerCanvasEl.height=this.height;this.lowerCanvasEl.style.width=this.width+"px";this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(canvasEl){this.lowerCanvasEl=fabric.util.getById(canvasEl)||this._createCanvasElement();this._initCanvasElement(this.lowerCanvasEl);fabric.util.addClass(this.lowerCanvasEl,"lower-canvas");if(this.interactive){this._applyCanvasStyle(this.lowerCanvasEl)}this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(value){return this._setDimension("width",value)},setHeight:function(value){return this._setDimension("height",value)},setDimensions:function(dimensions){for(var prop in dimensions){this._setDimension(prop,dimensions[prop])}return this},_setDimension:function(prop,value){this.lowerCanvasEl[prop]=value;this.lowerCanvasEl.style[prop]=value+"px";if(this.upperCanvasEl){this.upperCanvasEl[prop]=value;this.upperCanvasEl.style[prop]=value+"px"}if(this.cacheCanvasEl){this.cacheCanvasEl[prop]=value}if(this.wrapperEl){this.wrapperEl.style[prop]=value+"px"}this[prop]=value;this.calcOffset();this.renderAll();return this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(vpt){this.viewportTransform=vpt;this.renderAll();for(var i=0,len=this._objects.length;i");return markup.join("")},_setSVGPreamble:function(markup,options){if(!options.suppressPreamble){markup.push('','\n')}},_setSVGHeader:function(markup,options){markup.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(markup,reviver){var activeGroup=this.getActiveGroup();if(activeGroup){this.discardActiveGroup()}for(var i=0,objects=this.getObjects(),len=objects.length;i")}else if(this[property]&&property==="overlayColor"){markup.push('")}},sendToBack:function(object){removeFromArray(this._objects,object);this._objects.unshift(object);return this.renderAll&&this.renderAll()},bringToFront:function(object){removeFromArray(this._objects,object);this._objects.push(object);return this.renderAll&&this.renderAll()},sendBackwards:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==0){var newIdx=this._findNewLowerIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx-1;i>=0;--i){var isIntersecting=object.intersectsWithObject(this._objects[i])||object.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(object);if(isIntersecting){newIdx=i;break}}}else{newIdx=idx-1}return newIdx},bringForward:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==this._objects.length-1){var newIdx=this._findNewUpperIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx+1;i"}});extend(fabric.StaticCanvas.prototype,fabric.Observable);extend(fabric.StaticCanvas.prototype,fabric.Collection);extend(fabric.StaticCanvas.prototype,fabric.DataURLExporter);extend(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(methodName){var el=fabric.util.createCanvasElement();if(!el||!el.getContext){return null}var ctx=el.getContext("2d");if(!ctx){return null}switch(methodName){case"getImageData":return typeof ctx.getImageData!=="undefined";case"setLineDash":return typeof ctx.setLineDash!=="undefined";case"toDataURL":return typeof el.toDataURL!=="undefined";case"toDataURLWithQuality":try{el.toDataURL("image/jpeg",0);return true}catch(e){}return false;default:return null}}});fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject})();fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(options){this.shadow=new fabric.Shadow(options);return this},_setBrushStyles:function(){var ctx=this.canvas.contextTop;ctx.strokeStyle=this.color;ctx.lineWidth=this.width;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var ctx=this.canvas.contextTop;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var ctx=this.canvas.contextTop;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0}});(function(){var utilMin=fabric.util.array.min,utilMax=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(canvas){this.canvas=canvas;this._points=[]},onMouseDown:function(pointer){this._prepareForDrawing(pointer);this._captureDrawingPath(pointer);this._render()},onMouseMove:function(pointer){this._captureDrawingPath(pointer);this.canvas.clearContext(this.canvas.contextTop);this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(pointer){var p=new fabric.Point(pointer.x,pointer.y);this._reset();this._addPoint(p);this.canvas.contextTop.moveTo(p.x,p.y)},_addPoint:function(point){this._points.push(point)},_reset:function(){this._points.length=0;this._setBrushStyles();this._setShadow()},_captureDrawingPath:function(pointer){var pointerPoint=new fabric.Point(pointer.x,pointer.y);this._addPoint(pointerPoint)},_render:function(){var ctx=this.canvas.contextTop;var v=this.canvas.viewportTransform;ctx.save();ctx.transform(v[0],v[1],v[2],v[3],v[4],v[5]);ctx.beginPath();var p1=this._points[0],p2=this._points[1];if(this._points.length===2&&p1.x===p2.x&&p1.y===p2.y){p1.x-=.5;p2.x+=.5}ctx.moveTo(p1.x,p1.y);for(var i=1,len=this._points.length;i'+''+""},toLive:function(ctx){var source=typeof this.source==="function"?this.source():this.source;if(!source){return""}if(typeof source.src!=="undefined"){if(!source.complete){return""}if(source.naturalWidth===0||source.naturalHeight===0){return""}}return ctx.createPattern(source,this.repeat)}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Shadow){fabric.warn("fabric.Shadow is already defined.");return}fabric.Shadow=fabric.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:false,includeDefaultValues:true,initialize:function(options){if(typeof options==="string"){options=this._parseShadow(options)}for(var prop in options){this[prop]=options[prop]}this.id=fabric.Object.__uid++},_parseShadow:function(shadow){var shadowStr=shadow.trim(),offsetsAndBlur=fabric.Shadow.reOffsetsAndBlur.exec(shadowStr)||[],color=shadowStr.replace(fabric.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:color.trim(),offsetX:parseInt(offsetsAndBlur[1],10)||0,offsetY:parseInt(offsetsAndBlur[2],10)||0,blur:parseInt(offsetsAndBlur[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(object){var mode="SourceAlpha";if(object&&(object.fill===this.color||object.stroke===this.color)){mode="SourceGraphic"}return''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues){return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY}}var obj={},proto=fabric.Shadow.prototype;if(this.color!==proto.color){obj.color=this.color}if(this.blur!==proto.blur){obj.blur=this.blur}if(this.offsetX!==proto.offsetX){obj.offsetX=this.offsetX}if(this.offsetY!==proto.offsetY){obj.offsetY=this.offsetY}return obj}});fabric.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/})(typeof exports!=="undefined"?exports:this);(function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var extend=fabric.util.object.extend,getElementOffset=fabric.util.getElementOffset,removeFromArray=fabric.util.removeFromArray,CANVAS_INIT_ERROR=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(el,options){options||(options={});this._initStatic(el,options);fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:true,stateful:true,renderOnAddRemove:true,clipTo:null,controlsAboveOverlay:false,allowTouchScrolling:false,imageSmoothingEnabled:true,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(el,options){this._objects=[];this._createLowerCanvas(el);this._initOptions(options);this._setImageSmoothing();if(options.overlayImage){this.setOverlayImage(options.overlayImage,this.renderAll.bind(this))}if(options.backgroundImage){this.setBackgroundImage(options.backgroundImage,this.renderAll.bind(this))}if(options.backgroundColor){this.setBackgroundColor(options.backgroundColor,this.renderAll.bind(this))}if(options.overlayColor){this.setOverlayColor(options.overlayColor,this.renderAll.bind(this))}this.calcOffset()},calcOffset:function(){this._offset=getElementOffset(this.lowerCanvasEl);return this},setOverlayImage:function(image,callback,options){return this.__setBgOverlayImage("overlayImage",image,callback,options)},setBackgroundImage:function(image,callback,options){return this.__setBgOverlayImage("backgroundImage",image,callback,options)},setOverlayColor:function(overlayColor,callback){return this.__setBgOverlayColor("overlayColor",overlayColor,callback)},setBackgroundColor:function(backgroundColor,callback){return this.__setBgOverlayColor("backgroundColor",backgroundColor,callback)},_setImageSmoothing:function(){var ctx=this.getContext();ctx.imageSmoothingEnabled=this.imageSmoothingEnabled;ctx.webkitImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.mozImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.msImageSmoothingEnabled=this.imageSmoothingEnabled;ctx.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(property,image,callback,options){if(typeof image==="string"){fabric.util.loadImage(image,function(img){this[property]=new fabric.Image(img,options);callback&&callback()},this)}else{this[property]=image;callback&&callback()}return this},__setBgOverlayColor:function(property,color,callback){if(color.source){var _this=this;fabric.util.loadImage(color.source,function(img){_this[property]=new fabric.Pattern({source:img,repeat:color.repeat,offsetX:color.offsetX,offsetY:color.offsetY});callback&&callback()})}else{this[property]=color;callback&&callback()}return this},_createCanvasElement:function(){var element=fabric.document.createElement("canvas");if(!element.style){element.style={}}if(!element){throw CANVAS_INIT_ERROR}this._initCanvasElement(element);return element},_initCanvasElement:function(element){fabric.util.createCanvasElement(element);if(typeof element.getContext==="undefined"){throw CANVAS_INIT_ERROR}},_initOptions:function(options){for(var prop in options){this[prop]=options[prop]}this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0;this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width;this.lowerCanvasEl.height=this.height;this.lowerCanvasEl.style.width=this.width+"px";this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(canvasEl){this.lowerCanvasEl=fabric.util.getById(canvasEl)||this._createCanvasElement();this._initCanvasElement(this.lowerCanvasEl);fabric.util.addClass(this.lowerCanvasEl,"lower-canvas");if(this.interactive){this._applyCanvasStyle(this.lowerCanvasEl)}this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(value){return this._setDimension("width",value)},setHeight:function(value){return this._setDimension("height",value)},setDimensions:function(dimensions){for(var prop in dimensions){this._setDimension(prop,dimensions[prop])}return this},_setDimension:function(prop,value){this.lowerCanvasEl[prop]=value;this.lowerCanvasEl.style[prop]=value+"px";if(this.upperCanvasEl){this.upperCanvasEl[prop]=value;this.upperCanvasEl.style[prop]=value+"px"}if(this.cacheCanvasEl){this.cacheCanvasEl[prop]=value}if(this.wrapperEl){this.wrapperEl.style[prop]=value+"px"}this[prop]=value;this.calcOffset();this.renderAll();return this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(vpt){this.viewportTransform=vpt;this.renderAll();for(var i=0,len=this._objects.length;i");return markup.join("")},_setSVGPreamble:function(markup,options){if(!options.suppressPreamble){markup.push('','\n')}},_setSVGHeader:function(markup,options){markup.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(markup,reviver){var activeGroup=this.getActiveGroup();if(activeGroup){this.discardActiveGroup()}for(var i=0,objects=this.getObjects(),len=objects.length;i")}else if(this[property]&&property==="overlayColor"){markup.push('")}},sendToBack:function(object){removeFromArray(this._objects,object);this._objects.unshift(object);return this.renderAll&&this.renderAll()},bringToFront:function(object){removeFromArray(this._objects,object);this._objects.push(object);return this.renderAll&&this.renderAll()},sendBackwards:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==0){var newIdx=this._findNewLowerIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx-1;i>=0;--i){var isIntersecting=object.intersectsWithObject(this._objects[i])||object.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(object);if(isIntersecting){newIdx=i;break}}}else{newIdx=idx-1}return newIdx},bringForward:function(object,intersecting){var idx=this._objects.indexOf(object);if(idx!==this._objects.length-1){var newIdx=this._findNewUpperIndex(object,idx,intersecting);removeFromArray(this._objects,object);this._objects.splice(newIdx,0,object);this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(object,idx,intersecting){var newIdx;if(intersecting){newIdx=idx;for(var i=idx+1;i"}});extend(fabric.StaticCanvas.prototype,fabric.Observable);extend(fabric.StaticCanvas.prototype,fabric.Collection);extend(fabric.StaticCanvas.prototype,fabric.DataURLExporter);extend(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(methodName){var el=fabric.util.createCanvasElement();if(!el||!el.getContext){return null}var ctx=el.getContext("2d");if(!ctx){return null}switch(methodName){case"getImageData":return typeof ctx.getImageData!=="undefined";case"setLineDash":return typeof ctx.setLineDash!=="undefined";case"toDataURL":return typeof el.toDataURL!=="undefined";case"toDataURLWithQuality":try{el.toDataURL("image/jpeg",0);return true}catch(e){}return false;default:return null}}});fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject})();fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(options){this.shadow=new fabric.Shadow(options);return this},_setBrushStyles:function(){var ctx=this.canvas.contextTop;ctx.strokeStyle=this.color;ctx.lineWidth=this.width;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var ctx=this.canvas.contextTop;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var ctx=this.canvas.contextTop;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0}});(function(){var utilMin=fabric.util.array.min,utilMax=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(canvas){this.canvas=canvas;this._points=[]},onMouseDown:function(pointer){this._prepareForDrawing(pointer);this._captureDrawingPath(pointer);this._render()},onMouseMove:function(pointer){this._captureDrawingPath(pointer);this.canvas.clearContext(this.canvas.contextTop);this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(pointer){var p=new fabric.Point(pointer.x,pointer.y);this._reset();this._addPoint(p);this.canvas.contextTop.moveTo(p.x,p.y)},_addPoint:function(point){this._points.push(point)},_reset:function(){this._points.length=0;this._setBrushStyles();this._setShadow()},_captureDrawingPath:function(pointer){var pointerPoint=new fabric.Point(pointer.x,pointer.y);this._addPoint(pointerPoint)},_render:function(){var ctx=this.canvas.contextTop;var v=this.canvas.viewportTransform;ctx.save();ctx.transform(v[0],v[1],v[2],v[3],v[4],v[5]);ctx.beginPath();var p1=this._points[0],p2=this._points[1];if(this._points.length===2&&p1.x===p2.x&&p1.y===p2.y){p1.x-=.5;p2.x+=.5}ctx.moveTo(p1.x,p1.y);for(var i=1,len=this._points.length;itarget.padding){if(localMouse.x<0){localMouse.x+=target.padding}else{localMouse.x-=target.padding}}else{localMouse.x=0}if(abs(localMouse.y)>target.padding){if(localMouse.y<0){localMouse.y+=target.padding}else{localMouse.y-=target.padding}}else{localMouse.y=0}},_rotateObject:function(x,y){var t=this._currentTransform;if(t.target.get("lockRotation"))return;var lastAngle=atan2(t.ey-t.top,t.ex-t.left),curAngle=atan2(y-t.top,x-t.left),angle=radiansToDegrees(curAngle-lastAngle+t.theta);if(angle<0){angle=360+angle}t.target.angle=angle},_setCursor:function(value){this.upperCanvasEl.style.cursor=value},_resetObjectTransform:function(target){target.scaleX=1;target.scaleY=1;target.setAngle(0)},_drawSelection:function(){var ctx=this.contextTop,groupSelector=this._groupSelector,left=groupSelector.left,top=groupSelector.top,aleft=abs(left),atop=abs(top);ctx.fillStyle=this.selectionColor;ctx.fillRect(groupSelector.ex-(left>0?0:-left),groupSelector.ey-(top>0?0:-top),aleft,atop);ctx.lineWidth=this.selectionLineWidth;ctx.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var px=groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),py=groupSelector.ey+STROKE_OFFSET-(top>0?0:atop);ctx.beginPath();fabric.util.drawDashedLine(ctx,px,py,px+aleft,py,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py+atop-1,px+aleft,py+atop-1,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py,px,py+atop,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px+aleft-1,py,px+aleft-1,py+atop,this.selectionDashArray);ctx.closePath();ctx.stroke()}else{ctx.strokeRect(groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),groupSelector.ey+STROKE_OFFSET-(top>0?0:atop),aleft,atop)}},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,true))},findTarget:function(e,skipGroup){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e)){return this.lastRenderedObjectWithControlsAboveOverlay}var activeGroup=this.getActiveGroup();if(activeGroup&&!skipGroup&&this.containsPoint(e,activeGroup)){return activeGroup}var target=this._searchPossibleTargets(e);this._fireOverOutEvents(target);return target},_fireOverOutEvents:function(target){if(target){if(this._hoveredTarget!==target){this.fire("mouse:over",{target:target});target.fire("mouseover");if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout")}this._hoveredTarget=target}}else if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout");this._hoveredTarget=null}},_checkTarget:function(e,obj,pointer){if(obj&&obj.visible&&obj.evented&&this.containsPoint(e,obj)){if((this.perPixelTargetFind||obj.perPixelTargetFind)&&!obj.isEditing){var isTransparent=this.isTargetTransparent(obj,pointer.x,pointer.y);if(!isTransparent){return true}}else{return true}}},_searchPossibleTargets:function(e){var target,pointer=this.getPointer(e,true);var i=this._objects.length;while(i--){if(this._checkTarget(e,this._objects[i],pointer)){this.relatedTarget=this._objects[i];target=this._objects[i];break}}return target},getPointer:function(e,ignoreZoom,upperCanvasEl){if(!upperCanvasEl){upperCanvasEl=this.upperCanvasEl}var pointer=getPointer(e,upperCanvasEl),bounds=upperCanvasEl.getBoundingClientRect(),cssScale;pointer.x=pointer.x-this._offset.left;pointer.y=pointer.y-this._offset.top;if(!ignoreZoom){pointer=fabric.util.transformPoint(pointer,fabric.util.invertTransform(this.viewportTransform))}if(bounds.width===0||bounds.height===0){cssScale={width:1,height:1}}else{cssScale={width:upperCanvasEl.width/bounds.width,height:upperCanvasEl.height/bounds.height}}return{x:pointer.x*cssScale.width,y:pointer.y*cssScale.height}},_createUpperCanvas:function(){var lowerCanvasClass=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement();fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+lowerCanvasClass);this.wrapperEl.appendChild(this.upperCanvasEl);this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl);this._applyCanvasStyle(this.upperCanvasEl);this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement();this.cacheCanvasEl.setAttribute("width",this.width);this.cacheCanvasEl.setAttribute("height",this.height);this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass});fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"});fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(element){var width=this.getWidth()||element.width,height=this.getHeight()||element.height;fabric.util.setStyle(element,{position:"absolute",width:width+"px",height:height+"px",left:0,top:0});element.width=width;element.height=height;fabric.util.makeElementUnselectable(element)},_copyCanvasStyle:function(fromEl,toEl){toEl.style.cssText=fromEl.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(object){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=object;object.set("active",true)},setActiveObject:function(object,e){this._setActiveObject(object);this.renderAll();this.fire("object:selected",{target:object,e:e});object.fire("selected",{e:e});return this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=null},discardActiveObject:function(e){this._discardActiveObject();this.renderAll();this.fire("selection:cleared",{e:e});return this},_setActiveGroup:function(group){this._activeGroup=group;if(group){group.canvas=this;group._calcBounds();group._updateObjectsCoords();group.setCoords();group.set("active",true)}},setActiveGroup:function(group,e){this._setActiveGroup(group);if(group){this.fire("object:selected",{target:group,e:e});group.fire("selected",{e:e})}return this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var g=this.getActiveGroup();if(g){g.destroy()}this.setActiveGroup(null)},discardActiveGroup:function(e){this._discardActiveGroup();this.fire("selection:cleared",{e:e});return this},deactivateAll:function(){var allObjects=this.getObjects(),i=0,len=allObjects.length;for(;i1){group=new fabric.Group(group.reverse(),{originX:"center",originY:"center"});this.setActiveGroup(group,e);group.saveCoords();this.fire("selection:created",{target:group});this.renderAll()}},_collectObjects:function(){var group=[],currentObject,x1=this._groupSelector.ex,y1=this._groupSelector.ey,x2=x1+this._groupSelector.left,y2=y1+this._groupSelector.top,selectionX1Y1=new fabric.Point(min(x1,x2),min(y1,y2)),selectionX2Y2=new fabric.Point(max(x1,x2),max(y1,y2)),isClick=x1===x2&&y1===y2;for(var i=this._objects.length;i--;){currentObject=this._objects[i];if(!currentObject||!currentObject.selectable||!currentObject.visible)continue;if(currentObject.intersectsWithRect(selectionX1Y1,selectionX2Y2)||currentObject.isContainedWithinRect(selectionX1Y1,selectionX2Y2)||currentObject.containsPoint(selectionX1Y1)||currentObject.containsPoint(selectionX2Y2)){currentObject.set("active",true);group.push(currentObject);if(isClick)break}}return group},_maybeGroupObjects:function(e){if(this.selection&&this._groupSelector){this._groupSelectedObjects(e)}var activeGroup=this.getActiveGroup();if(activeGroup){activeGroup.setObjectsCoords().setCoords();activeGroup.isMoving=false;this._setCursor(this.defaultCursor)}this._groupSelector=null;this._currentTransform=null}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(options){options||(options={});var format=options.format||"png",quality=options.quality||1,multiplier=options.multiplier||1,cropping={left:options.left,top:options.top,width:options.width,height:options.height};if(multiplier!==1){return this.__toDataURLWithMultiplier(format,quality,cropping,multiplier)}else{return this.__toDataURL(format,quality,cropping)}},__toDataURL:function(format,quality,cropping){this.renderAll(true);var canvasEl=this.upperCanvasEl||this.lowerCanvasEl,croppedCanvasEl=this.__getCroppedCanvas(canvasEl,cropping);if(format==="jpg"){format="jpeg"}var data=fabric.StaticCanvas.supports("toDataURLWithQuality")?(croppedCanvasEl||canvasEl).toDataURL("image/"+format,quality):(croppedCanvasEl||canvasEl).toDataURL("image/"+format);this.contextTop&&this.clearContext(this.contextTop);this.renderAll();if(croppedCanvasEl){croppedCanvasEl=null}return data},__getCroppedCanvas:function(canvasEl,cropping){var croppedCanvasEl,croppedCtx,shouldCrop="left"in cropping||"top"in cropping||"width"in cropping||"height"in cropping;if(shouldCrop){croppedCanvasEl=fabric.util.createCanvasElement();croppedCtx=croppedCanvasEl.getContext("2d");croppedCanvasEl.width=cropping.width||this.width;croppedCanvasEl.height=cropping.height||this.height;croppedCtx.drawImage(canvasEl,-cropping.left||0,-cropping.top||0)}return croppedCanvasEl},__toDataURLWithMultiplier:function(format,quality,cropping,multiplier){var origWidth=this.getWidth(),origHeight=this.getHeight(),scaledWidth=origWidth*multiplier,scaledHeight=origHeight*multiplier,activeObject=this.getActiveObject(),activeGroup=this.getActiveGroup(),ctx=this.contextTop||this.contextContainer;if(multiplier>1){this.setWidth(scaledWidth).setHeight(scaledHeight)}ctx.scale(multiplier,multiplier);if(cropping.left){cropping.left*=multiplier}if(cropping.top){cropping.top*=multiplier}if(cropping.width){cropping.width*=multiplier}else if(multiplier<1){cropping.width=scaledWidth}if(cropping.height){cropping.height*=multiplier}else if(multiplier<1){cropping.height=scaledHeight}if(activeGroup){this._tempRemoveBordersControlsFromGroup(activeGroup)}else if(activeObject&&this.deactivateAll){this.deactivateAll()}this.renderAll(true);var data=this.__toDataURL(format,quality,cropping);this.width=origWidth;this.height=origHeight;ctx.scale(1/multiplier,1/multiplier);this.setWidth(origWidth).setHeight(origHeight);if(activeGroup){this._restoreBordersControlsOnGroup(activeGroup)}else if(activeObject&&this.setActiveObject){this.setActiveObject(activeObject)}this.contextTop&&this.clearContext(this.contextTop);this.renderAll();return data},toDataURLWithMultiplier:function(format,multiplier,quality){return this.toDataURL({format:format,multiplier:multiplier,quality:quality})},_tempRemoveBordersControlsFromGroup:function(group){group.origHasControls=group.hasControls;group.origBorderColor=group.borderColor;group.hasControls=true;group.borderColor="rgba(0,0,0,0)";group.forEachObject(function(o){o.origBorderColor=o.borderColor;o.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(group){group.hideControls=group.origHideControls;group.borderColor=group.origBorderColor;group.forEachObject(function(o){o.borderColor=o.origBorderColor;delete o.origBorderColor})}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(json,callback,reviver){return this.loadFromJSON(json,callback,reviver)},loadFromJSON:function(json,callback,reviver){if(!json)return;var serialized=typeof json==="string"?JSON.parse(json):json;this.clear();var _this=this;this._enlivenObjects(serialized.objects,function(){_this._setBgOverlay(serialized,callback)},reviver);return this},_setBgOverlay:function(serialized,callback){var _this=this,loaded={backgroundColor:false,overlayColor:false,backgroundImage:false,overlayImage:false};if(!serialized.backgroundImage&&!serialized.overlayImage&&!serialized.background&&!serialized.overlay){callback&&callback(); return}var cbIfLoaded=function(){if(loaded.backgroundImage&&loaded.overlayImage&&loaded.backgroundColor&&loaded.overlayColor){_this.renderAll();callback&&callback()}};this.__setBgOverlay("backgroundImage",serialized.backgroundImage,loaded,cbIfLoaded);this.__setBgOverlay("overlayImage",serialized.overlayImage,loaded,cbIfLoaded);this.__setBgOverlay("backgroundColor",serialized.background,loaded,cbIfLoaded);this.__setBgOverlay("overlayColor",serialized.overlay,loaded,cbIfLoaded);cbIfLoaded()},__setBgOverlay:function(property,value,loaded,callback){var _this=this;if(!value){loaded[property]=true;return}if(property==="backgroundImage"||property==="overlayImage"){fabric.Image.fromObject(value,function(img){_this[property]=img;loaded[property]=true;callback&&callback()})}else{this["set"+fabric.util.string.capitalize(property,true)](value,function(){loaded[property]=true;callback&&callback()})}},_enlivenObjects:function(objects,callback,reviver){var _this=this;if(!objects||objects.length===0){callback&&callback();return}var renderOnAddRemove=this.renderOnAddRemove;this.renderOnAddRemove=false;fabric.util.enlivenObjects(objects,function(enlivenedObjects){enlivenedObjects.forEach(function(obj,index){_this.insertAt(obj,index,true)});_this.renderOnAddRemove=renderOnAddRemove;callback&&callback()},null,reviver)},_toDataURL:function(format,callback){this.clone(function(clone){callback(clone.toDataURL(format))})},_toDataURLWithMultiplier:function(format,multiplier,callback){this.clone(function(clone){callback(clone.toDataURLWithMultiplier(format,multiplier))})},clone:function(callback,properties){var data=JSON.stringify(this.toJSON(properties));this.cloneWithoutData(function(clone){clone.loadFromJSON(data,function(){callback&&callback(clone)})})},cloneWithoutData:function(callback){var el=fabric.document.createElement("canvas");el.width=this.getWidth();el.height=this.getHeight();var clone=new fabric.Canvas(el);clone.clipTo=this.clipTo;if(this.backgroundImage){clone.setBackgroundImage(this.backgroundImage.src,function(){clone.renderAll();callback&&callback(clone)});clone.backgroundImageOpacity=this.backgroundImageOpacity;clone.backgroundImageStretch=this.backgroundImageStretch}else{callback&&callback(clone)}}});(function(){var degreesToRadians=fabric.util.degreesToRadians,radiansToDegrees=fabric.util.radiansToDegrees;fabric.util.object.extend(fabric.Canvas.prototype,{__onTransformGesture:function(e,self){if(this.isDrawingMode||!e.touches||e.touches.length!==2||"gesture"!==self.gesture){return}var target=this.findTarget(e);if("undefined"!==typeof target){this.onBeforeScaleRotate(target);this._rotateObjectByAngle(self.rotation);this._scaleObjectBy(self.scale)}this.fire("touch:gesture",{target:target,e:e,self:self})},__onDrag:function(e,self){this.fire("touch:drag",{e:e,self:self})},__onOrientationChange:function(e,self){this.fire("touch:orientation",{e:e,self:self})},__onShake:function(e,self){this.fire("touch:shake",{e:e,self:self})},_scaleObjectBy:function(s,by){var t=this._currentTransform,target=t.target,lockScalingX=target.get("lockScalingX"),lockScalingY=target.get("lockScalingY");if(lockScalingX&&lockScalingY)return;target._scaling=true;if(!by){if(!lockScalingX){target.set("scaleX",t.scaleX*s)}if(!lockScalingY){target.set("scaleY",t.scaleY*s)}}else if(by==="x"&&!target.get("lockUniScaling")){lockScalingX||target.set("scaleX",t.scaleX*s)}else if(by==="y"&&!target.get("lockUniScaling")){lockScalingY||target.set("scaleY",t.scaleY*s)}},_rotateObjectByAngle:function(curAngle){var t=this._currentTransform;if(t.target.get("lockRotation"))return;t.target.angle=radiansToDegrees(degreesToRadians(curAngle)+t.theta)}})})();(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,toFixed=fabric.util.toFixed,capitalize=fabric.util.string.capitalize,degreesToRadians=fabric.util.degreesToRadians,supportsLineDash=fabric.StaticCanvas.supports("setLineDash");if(fabric.Object){return}fabric.Object=fabric.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:false,flipY:false,opacity:1,angle:0,cornerSize:12,transparentCorners:true,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:false,centeredRotation:true,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:true,evented:true,visible:true,hasControls:true,hasBorders:true,hasRotatingPoint:true,rotatingPointOffset:40,perPixelTargetFind:false,includeDefaultValues:true,clipTo:null,lockMovementX:false,lockMovementY:false,lockRotation:false,lockScalingX:false,lockScalingY:false,lockUniScaling:false,stateProperties:("top left width height scaleX scaleY flipX flipY originX originY transformMatrix "+"stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit "+"angle opacity fill fillRule shadow clipTo visible backgroundColor").split(" "),initialize:function(options){if(options){this.setOptions(options)}},_initGradient:function(options){if(options.fill&&options.fill.colorStops&&!(options.fill instanceof fabric.Gradient)){this.set("fill",new fabric.Gradient(options.fill))}},_initPattern:function(options){if(options.fill&&options.fill.source&&!(options.fill instanceof fabric.Pattern)){this.set("fill",new fabric.Pattern(options.fill))}if(options.stroke&&options.stroke.source&&!(options.stroke instanceof fabric.Pattern)){this.set("stroke",new fabric.Pattern(options.stroke))}},_initClipping:function(options){if(!options.clipTo||typeof options.clipTo!=="string")return;var functionBody=fabric.util.getFunctionBody(options.clipTo);if(typeof functionBody!=="undefined"){this.clipTo=new Function("ctx",functionBody)}},setOptions:function(options){for(var prop in options){this.set(prop,options[prop])}this._initGradient(options);this._initPattern(options);this._initClipping(options)},transform:function(ctx,fromLeft){if(this.group){this.group.transform(ctx,fromLeft)}ctx.globalAlpha=this.opacity;var center=fromLeft?this._getLeftTopCoords():this.getCenterPoint();ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));ctx.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(propertiesToInclude){var NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,object={type:this.type,originX:this.originX,originY:this.originY,left:toFixed(this.left,NUM_FRACTION_DIGITS),top:toFixed(this.top,NUM_FRACTION_DIGITS),width:toFixed(this.width,NUM_FRACTION_DIGITS),height:toFixed(this.height,NUM_FRACTION_DIGITS),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:toFixed(this.strokeWidth,NUM_FRACTION_DIGITS),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:toFixed(this.strokeMiterLimit,NUM_FRACTION_DIGITS),scaleX:toFixed(this.scaleX,NUM_FRACTION_DIGITS),scaleY:toFixed(this.scaleY,NUM_FRACTION_DIGITS),angle:toFixed(this.getAngle(),NUM_FRACTION_DIGITS),flipX:this.flipX,flipY:this.flipY,opacity:toFixed(this.opacity,NUM_FRACTION_DIGITS),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};if(!this.includeDefaultValues){object=this._removeDefaultValues(object)}fabric.util.populateWithProperties(this,object,propertiesToInclude);return object},toDatalessObject:function(propertiesToInclude){return this.toObject(propertiesToInclude)},_removeDefaultValues:function(object){var prototype=fabric.util.getKlass(object.type).prototype,stateProperties=prototype.stateProperties;stateProperties.forEach(function(prop){if(object[prop]===prototype[prop]){delete object[prop]}});return object},toString:function(){return"#"},get:function(property){return this[property]},_setObject:function(obj){for(var prop in obj){this._set(prop,obj[prop])}},set:function(key,value){if(typeof key==="object"){this._setObject(key)}else{if(typeof value==="function"&&key!=="clipTo"){this._set(key,value(this.get(key)))}else{this._set(key,value)}}return this},_set:function(key,value){var shouldConstrainValue=key==="scaleX"||key==="scaleY";if(shouldConstrainValue){value=this._constrainScale(value)}if(key==="scaleX"&&value<0){this.flipX=!this.flipX;value*=-1}else if(key==="scaleY"&&value<0){this.flipY=!this.flipY;value*=-1}else if(key==="width"||key==="height"){this.minScaleLimit=toFixed(Math.min(.1,1/Math.max(this.width,this.height)),2)}else if(key==="shadow"&&value&&!(value instanceof fabric.Shadow)){value=new fabric.Shadow(value)}this[key]=value;return this},toggle:function(property){var value=this.get(property);if(typeof value==="boolean"){this.set(property,!value)}return this},setSourcePath:function(value){this.sourcePath=value;return this},getViewportTransform:function(){if(this.canvas&&this.canvas.viewportTransform)return this.canvas.viewportTransform;return[1,0,0,1,0,0]},render:function(ctx,noTransform){if(this.width===0||this.height===0||!this.visible)return;ctx.save();this._setupFillRule(ctx);this._transform(ctx,noTransform);this._setStrokeStyles(ctx);this._setFillStyles(ctx);var m=this.transformMatrix;if(m&&this.group){ctx.translate(-this.group.width/2,-this.group.height/2);ctx.transform(m[0],m[1],m[2],m[3],m[4],m[5])}this._setShadow(ctx);this.clipTo&&fabric.util.clipContext(this,ctx);this._render(ctx,noTransform);this.clipTo&&ctx.restore();this._removeShadow(ctx);this._restoreFillRule(ctx);ctx.restore()},_transform:function(ctx,noTransform){var m=this.transformMatrix;if(m&&!this.group){ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5])}if(!noTransform){this.transform(ctx)}},_setStrokeStyles:function(ctx){if(this.stroke){ctx.lineWidth=this.strokeWidth;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin;ctx.miterLimit=this.strokeMiterLimit;ctx.strokeStyle=this.stroke.toLive?this.stroke.toLive(ctx):this.stroke}},_setFillStyles:function(ctx){if(this.fill){ctx.fillStyle=this.fill.toLive?this.fill.toLive(ctx):this.fill}},_renderControls:function(ctx,noTransform){var v=this.getViewportTransform();ctx.save();if(this.active&&!noTransform){var center;if(this.group){center=fabric.util.transformPoint(this.group.getCenterPoint(),v);ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.group.angle))}center=fabric.util.transformPoint(this.getCenterPoint(),v,null!=this.group);if(this.group){center.x*=this.group.scaleX;center.y*=this.group.scaleY}ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));this.drawBorders(ctx);this.drawControls(ctx)}ctx.restore()},_setShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0},_renderFill:function(ctx){if(!this.fill)return;if(this.fill.toLive){ctx.save();ctx.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)}if(this.fillRule==="destination-over"){ctx.fill("evenodd")}else{ctx.fill()}if(this.fill.toLive){ctx.restore()}if(this.shadow&&!this.shadow.affectStroke){this._removeShadow(ctx)}},_renderStroke:function(ctx){if(!this.stroke)return;ctx.save();if(this.strokeDashArray){if(1&this.strokeDashArray.length){this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray)}if(supportsLineDash){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);ctx.restore()},clone:function(callback,propertiesToInclude){if(this.constructor.fromObject){return this.constructor.fromObject(this.toObject(propertiesToInclude),callback)}return new fabric.Object(this.toObject(propertiesToInclude))},cloneAsImage:function(callback){var dataUrl=this.toDataURL();fabric.util.loadImage(dataUrl,function(img){if(callback){callback(new fabric.Image(img))}});return this},toDataURL:function(options){options||(options={});var el=fabric.util.createCanvasElement(),boundingRect=this.getBoundingRect();el.width=boundingRect.width;el.height=boundingRect.height;fabric.util.wrapElement(el,"div");var canvas=new fabric.Canvas(el);if(options.format==="jpg"){options.format="jpeg"}if(options.format==="jpeg"){canvas.backgroundColor="#fff"}var origParams={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",false);this.setPositionByOrigin(new fabric.Point(el.width/2,el.height/2),"center","center");var originalCanvas=this.canvas;canvas.add(this);var data=canvas.toDataURL(options);this.set(origParams).setCoords();this.canvas=originalCanvas;canvas.dispose();canvas=null;return data},isType:function(type){return this.type===type},complexity:function(){return 0},toJSON:function(propertiesToInclude){return this.toObject(propertiesToInclude)},setGradient:function(property,options){options||(options={});var gradient={colorStops:[]};gradient.type=options.type||(options.r1||options.r2?"radial":"linear");gradient.coords={x1:options.x1,y1:options.y1,x2:options.x2,y2:options.y2};if(options.r1||options.r2){gradient.coords.r1=options.r1;gradient.coords.r2=options.r2}for(var position in options.colorStops){var color=new fabric.Color(options.colorStops[position]);gradient.colorStops.push({offset:position,color:color.toRgb(),opacity:color.getAlpha()})}return this.set(property,fabric.Gradient.forObject(this,gradient))},setPatternFill:function(options){return this.set("fill",new fabric.Pattern(options))},setShadow:function(options){return this.set("shadow",new fabric.Shadow(options))},setColor:function(color){this.set("fill",color);return this},setAngle:function(angle){var shouldCenterOrigin=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;if(shouldCenterOrigin){this._setOriginToCenter()}this.set("angle",angle);if(shouldCenterOrigin){this._resetOrigin()}return this},centerH:function(){this.canvas.centerObjectH(this);return this},centerV:function(){this.canvas.centerObjectV(this);return this},center:function(){this.canvas.centerObject(this);return this},remove:function(){this.canvas.remove(this);return this},getLocalPointer:function(e,pointer){pointer=pointer||this.canvas.getPointer(e);var objectLeftTop=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:pointer.x-objectLeftTop.x,y:pointer.y-objectLeftTop.y}},_setupFillRule:function(ctx){if(this.fillRule){this._prevFillRule=ctx.globalCompositeOperation;ctx.globalCompositeOperation=this.fillRule}},_restoreFillRule:function(ctx){if(this.fillRule&&this._prevFillRule){ctx.globalCompositeOperation=this._prevFillRule}}});fabric.util.createAccessors(fabric.Object);fabric.Object.prototype.rotate=fabric.Object.prototype.setAngle;extend(fabric.Object.prototype,fabric.Observable);fabric.Object.NUM_FRACTION_DIGITS=2;fabric.Object.__uid=0})(typeof exports!=="undefined"?exports:this);(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(point,originX,originY){var cx=point.x,cy=point.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){cx=point.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){cx=point.x-(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){cy=point.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){cy=point.y-(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(cx,cy),point,degreesToRadians(this.angle))},translateToOriginPoint:function(center,originX,originY){var x=center.x,y=center.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(x,y),center,degreesToRadians(this.angle))},getCenterPoint:function(){var leftTop=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(leftTop,this.originX,this.originY)},getPointByOrigin:function(originX,originY){var center=this.getCenterPoint();return this.translateToOriginPoint(center,originX,originY)},toLocalPoint:function(point,originX,originY){var center=this.getCenterPoint(),strokeWidth=this.stroke?this.strokeWidth:0,x,y;if(originX&&originY){if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else{x=center.x}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else{y=center.y}}else{x=this.left;y=this.top}return fabric.util.rotatePoint(new fabric.Point(point.x,point.y),center,-degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x,y))},setPositionByOrigin:function(pos,originX,originY){var center=this.translateToCenterPoint(pos,originX,originY),position=this.translateToOriginPoint(center,this.originX,this.originY);this.set("left",position.x);this.set("top",position.y)},adjustPosition:function(to){var angle=degreesToRadians(this.angle),hypotHalf=this.getWidth()/2,xHalf=Math.cos(angle)*hypotHalf,yHalf=Math.sin(angle)*hypotHalf,hypotFull=this.getWidth(),xFull=Math.cos(angle)*hypotFull,yFull=Math.sin(angle)*hypotFull;if(this.originX==="center"&&to==="left"||this.originX==="right"&&to==="center"){this.left-=xHalf;this.top-=yHalf}else if(this.originX==="left"&&to==="center"||this.originX==="center"&&to==="right"){this.left+=xHalf;this.top+=yHalf}else if(this.originX==="left"&&to==="right"){this.left+=xFull;this.top+=yFull}else if(this.originX==="right"&&to==="left"){this.left-=xFull;this.top-=yFull}this.setCoords();this.originX=to},_setOriginToCenter:function(){this._originalOriginX=this.originX;this._originalOriginY=this.originY;var center=this.getCenterPoint();this.originX="center";this.originY="center";this.left=center.x;this.top=center.y},_resetOrigin:function(){var originPoint=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX;this.originY=this._originalOriginY;this.left=originPoint.x;this.top=originPoint.y;this._originalOriginX=null;this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})})();(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(pointTL,pointBR){var oCoords=this.oCoords,tl=new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr=new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl=new fabric.Point(oCoords.bl.x,oCoords.bl.y),br=new fabric.Point(oCoords.br.x,oCoords.br.y),intersection=fabric.Intersection.intersectPolygonRectangle([tl,tr,br,bl],pointTL,pointBR);return intersection.status==="Intersection"},intersectsWithObject:function(other){function getCoords(oCoords){return{tl:new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr:new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl:new fabric.Point(oCoords.bl.x,oCoords.bl.y),br:new fabric.Point(oCoords.br.x,oCoords.br.y)}}var thisCoords=getCoords(this.oCoords),otherCoords=getCoords(other.oCoords),intersection=fabric.Intersection.intersectPolygonPolygon([thisCoords.tl,thisCoords.tr,thisCoords.br,thisCoords.bl],[otherCoords.tl,otherCoords.tr,otherCoords.br,otherCoords.bl]);return intersection.status==="Intersection"},isContainedWithinObject:function(other){var boundingRect=other.getBoundingRect(),point1=new fabric.Point(boundingRect.left,boundingRect.top),point2=new fabric.Point(boundingRect.left+boundingRect.width,boundingRect.top+boundingRect.height);return this.isContainedWithinRect(point1,point2)},isContainedWithinRect:function(pointTL,pointBR){var boundingRect=this.getBoundingRect();return boundingRect.left>=pointTL.x&&boundingRect.left+boundingRect.width<=pointBR.x&&boundingRect.top>=pointTL.y&&boundingRect.top+boundingRect.height<=pointBR.y},containsPoint:function(point){var lines=this._getImageLines(this.oCoords),xPoints=this._findCrossPoints(point,lines);return xPoints!==0&&xPoints%2===1},_getImageLines:function(oCoords){return{topline:{o:oCoords.tl,d:oCoords.tr},rightline:{o:oCoords.tr,d:oCoords.br},bottomline:{o:oCoords.br,d:oCoords.bl},leftline:{o:oCoords.bl,d:oCoords.tl}}},_findCrossPoints:function(point,oCoords){var b1,b2,a1,a2,xi,yi,xcount=0,iLine;for(var lineKey in oCoords){iLine=oCoords[lineKey];if(iLine.o.y=point.y&&iLine.d.y>=point.y){continue}if(iLine.o.x===iLine.d.x&&iLine.o.x>=point.x){xi=iLine.o.x;yi=point.y}else{b1=0;b2=(iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);a1=point.y-b1*point.x;a2=iLine.o.y-b2*iLine.o.x;xi=-(a1-a2)/(b1-b2);yi=a1+b1*xi}if(xi>=point.x){xcount+=1}if(xcount===2){break}}return xcount},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var xCoords=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],minX=fabric.util.array.min(xCoords),maxX=fabric.util.array.max(xCoords),width=Math.abs(minX-maxX),yCoords=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],minY=fabric.util.array.min(yCoords),maxY=fabric.util.array.max(yCoords),height=Math.abs(minY-maxY);return{left:minX,top:minY,width:width,height:height}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(value){if(Math.abs(value)1?this.strokeWidth:0,theta=degreesToRadians(this.angle),vpt=this.getViewportTransform();var f=function(p){return fabric.util.transformPoint(p,vpt)};this.currentWidth=(this.width+strokeWidth)*this.scaleX;this.currentHeight=(this.height+strokeWidth)*this.scaleY;if(this.currentWidth<0){this.currentWidth=Math.abs(this.currentWidth)}var _hypotenuse=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),_angle=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),offsetX=Math.cos(_angle+theta)*_hypotenuse,offsetY=Math.sin(_angle+theta)*_hypotenuse,sinTh=Math.sin(theta),cosTh=Math.cos(theta),coords=this.getCenterPoint(),wh=new fabric.Point(this.currentWidth,this.currentHeight),_tl=new fabric.Point(coords.x-offsetX,coords.y-offsetY),_tr=new fabric.Point(_tl.x+wh.x*cosTh,_tl.y+wh.x*sinTh),_bl=new fabric.Point(_tl.x-wh.y*sinTh,_tl.y+wh.y*cosTh),_mt=new fabric.Point(_tl.x+wh.x/2*cosTh,_tl.y+wh.x/2*sinTh),tl=f(_tl),tr=f(_tr),br=f(new fabric.Point(_tr.x-wh.y*sinTh,_tr.y+wh.y*cosTh)),bl=f(_bl),ml=f(new fabric.Point(_tl.x-wh.y/2*sinTh,_tl.y+wh.y/2*cosTh)),mt=f(_mt),mr=f(new fabric.Point(_tr.x-wh.y/2*sinTh,_tr.y+wh.y/2*cosTh)),mb=f(new fabric.Point(_bl.x+wh.x/2*cosTh,_bl.y+wh.x/2*sinTh)),mtr=f(new fabric.Point(_mt.x,_mt.y));var padX=Math.cos(_angle+theta)*this.padding*Math.sqrt(2),padY=Math.sin(_angle+theta)*this.padding*Math.sqrt(2);tl=tl.add(new fabric.Point(-padX,-padY));tr=tr.add(new fabric.Point(padY,-padX));br=br.add(new fabric.Point(padX,padY));bl=bl.add(new fabric.Point(-padY,padX));ml=ml.add(new fabric.Point((-padX-padY)/2,(-padY+padX)/2));mt=mt.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));mr=mr.add(new fabric.Point((padY+padX)/2,(padY-padX)/2));mb=mb.add(new fabric.Point((padX-padY)/2,(padX+padY)/2));mtr=mtr.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));this.oCoords={tl:tl,tr:tr,br:br,bl:bl,ml:ml,mt:mt,mr:mr,mb:mb,mtr:mtr};this._setCornerCoords&&this._setCornerCoords();return this}})})();fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){if(this.group){fabric.StaticCanvas.prototype.sendToBack.call(this.group,this)}else{this.canvas.sendToBack(this)}return this},bringToFront:function(){if(this.group){fabric.StaticCanvas.prototype.bringToFront.call(this.group,this)}else{this.canvas.bringToFront(this)}return this},sendBackwards:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,intersecting)}else{this.canvas.sendBackwards(this,intersecting)}return this},bringForward:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.bringForward.call(this.group,this,intersecting)}else{this.canvas.bringForward(this,intersecting)}return this},moveTo:function(index){if(this.group){fabric.StaticCanvas.prototype.moveTo.call(this.group,this,index)}else{this.canvas.moveTo(this,index)}return this}});fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var fill=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",stroke=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",strokeWidth=this.strokeWidth?this.strokeWidth:"0",strokeDashArray=this.strokeDashArray?this.strokeDashArray.join(" "):"",strokeLineCap=this.strokeLineCap?this.strokeLineCap:"butt",strokeLineJoin=this.strokeLineJoin?this.strokeLineJoin:"miter",strokeMiterLimit=this.strokeMiterLimit?this.strokeMiterLimit:"4",opacity=typeof this.opacity!=="undefined"?this.opacity:"1",visibility=this.visible?"":" visibility: hidden;",filter=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",stroke,"; ","stroke-width: ",strokeWidth,"; ","stroke-dasharray: ",strokeDashArray,"; ","stroke-linecap: ",strokeLineCap,"; ","stroke-linejoin: ",strokeLineJoin,"; ","stroke-miterlimit: ",strokeMiterLimit,"; ","fill: ",fill,"; ","opacity: ",opacity,";",filter,visibility].join("")},getSvgTransform:function(){var toFixed=fabric.util.toFixed,angle=this.getAngle(),center=this.getCenterPoint(),NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,translatePart="translate("+toFixed(center.x,NUM_FRACTION_DIGITS)+" "+toFixed(center.y,NUM_FRACTION_DIGITS)+")",anglePart=angle!==0?" rotate("+toFixed(angle,NUM_FRACTION_DIGITS)+")":"",scalePart=this.scaleX===1&&this.scaleY===1?"":" scale("+toFixed(this.scaleX,NUM_FRACTION_DIGITS)+" "+toFixed(this.scaleY,NUM_FRACTION_DIGITS)+")",flipXPart=this.flipX?"matrix(-1 0 0 1 0 0) ":"",flipYPart=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[translatePart,anglePart,scalePart,flipXPart,flipYPart].join("")},_createBaseSVGMarkup:function(){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))}if(this.shadow){markup.push(this.shadow.toSVG(this))}return markup}});fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(prop){return this.get(prop)!==this.originalState[prop]},this)},saveState:function(options){this.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this);if(options&&options.stateProperties){options.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this)}return this},setupState:function(){this.originalState={};this.saveState();return this}});(function(){var degreesToRadians=fabric.util.degreesToRadians,isVML=typeof G_vmlCanvasManager!=="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(pointer){if(!this.hasControls||!this.active)return false;var ex=pointer.x,ey=pointer.y,xPoints,lines;for(var i in this.oCoords){if(!this.isControlVisible(i)){continue}if(i==="mtr"&&!this.hasRotatingPoint){continue}if(this.get("lockUniScaling")&&(i==="mt"||i==="mr"||i==="mb"||i==="ml")){continue}lines=this._getImageLines(this.oCoords[i].corner);xPoints=this._findCrossPoints({x:ex,y:ey},lines);if(xPoints!==0&&xPoints%2===1){this.__corner=i;return i}}return false},_setCornerCoords:function(){var coords=this.oCoords,theta=degreesToRadians(this.angle),newTheta=degreesToRadians(45-this.angle),cornerHypotenuse=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,cosHalfOffset=cornerHypotenuse*Math.cos(newTheta),sinHalfOffset=cornerHypotenuse*Math.sin(newTheta),sinTh=Math.sin(theta),cosTh=Math.cos(theta);coords.tl.corner={tl:{x:coords.tl.x-sinHalfOffset,y:coords.tl.y-cosHalfOffset},tr:{x:coords.tl.x+cosHalfOffset,y:coords.tl.y-sinHalfOffset},bl:{x:coords.tl.x-cosHalfOffset,y:coords.tl.y+sinHalfOffset},br:{x:coords.tl.x+sinHalfOffset,y:coords.tl.y+cosHalfOffset}};coords.tr.corner={tl:{x:coords.tr.x-sinHalfOffset,y:coords.tr.y-cosHalfOffset},tr:{x:coords.tr.x+cosHalfOffset,y:coords.tr.y-sinHalfOffset},br:{x:coords.tr.x+sinHalfOffset,y:coords.tr.y+cosHalfOffset},bl:{x:coords.tr.x-cosHalfOffset,y:coords.tr.y+sinHalfOffset}};coords.bl.corner={tl:{x:coords.bl.x-sinHalfOffset,y:coords.bl.y-cosHalfOffset},bl:{x:coords.bl.x-cosHalfOffset,y:coords.bl.y+sinHalfOffset},br:{x:coords.bl.x+sinHalfOffset,y:coords.bl.y+cosHalfOffset},tr:{x:coords.bl.x+cosHalfOffset,y:coords.bl.y-sinHalfOffset}};coords.br.corner={tr:{x:coords.br.x+cosHalfOffset,y:coords.br.y-sinHalfOffset},bl:{x:coords.br.x-cosHalfOffset,y:coords.br.y+sinHalfOffset},br:{x:coords.br.x+sinHalfOffset,y:coords.br.y+cosHalfOffset},tl:{x:coords.br.x-sinHalfOffset,y:coords.br.y-cosHalfOffset}};coords.ml.corner={tl:{x:coords.ml.x-sinHalfOffset,y:coords.ml.y-cosHalfOffset},tr:{x:coords.ml.x+cosHalfOffset,y:coords.ml.y-sinHalfOffset},bl:{x:coords.ml.x-cosHalfOffset,y:coords.ml.y+sinHalfOffset},br:{x:coords.ml.x+sinHalfOffset,y:coords.ml.y+cosHalfOffset}};coords.mt.corner={tl:{x:coords.mt.x-sinHalfOffset,y:coords.mt.y-cosHalfOffset},tr:{x:coords.mt.x+cosHalfOffset,y:coords.mt.y-sinHalfOffset},bl:{x:coords.mt.x-cosHalfOffset,y:coords.mt.y+sinHalfOffset},br:{x:coords.mt.x+sinHalfOffset,y:coords.mt.y+cosHalfOffset}};coords.mr.corner={tl:{x:coords.mr.x-sinHalfOffset,y:coords.mr.y-cosHalfOffset},tr:{x:coords.mr.x+cosHalfOffset,y:coords.mr.y-sinHalfOffset},bl:{x:coords.mr.x-cosHalfOffset,y:coords.mr.y+sinHalfOffset},br:{x:coords.mr.x+sinHalfOffset,y:coords.mr.y+cosHalfOffset}};coords.mb.corner={tl:{x:coords.mb.x-sinHalfOffset,y:coords.mb.y-cosHalfOffset},tr:{x:coords.mb.x+cosHalfOffset,y:coords.mb.y-sinHalfOffset},bl:{x:coords.mb.x-cosHalfOffset,y:coords.mb.y+sinHalfOffset},br:{x:coords.mb.x+sinHalfOffset,y:coords.mb.y+cosHalfOffset}};coords.mtr.corner={tl:{x:coords.mtr.x-sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-cosHalfOffset-cosTh*this.rotatingPointOffset},tr:{x:coords.mtr.x+cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-sinHalfOffset-cosTh*this.rotatingPointOffset},bl:{x:coords.mtr.x-cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+sinHalfOffset-cosTh*this.rotatingPointOffset},br:{x:coords.mtr.x+sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+cosHalfOffset-cosTh*this.rotatingPointOffset}}},drawBorders:function(ctx){if(!this.hasBorders)return this;var padding=this.padding,padding2=padding*2,strokeWidth=~~(this.strokeWidth/2)*2; ctx.save();ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=this.borderColor;var scaleX=1/this._constrainScale(this.scaleX),scaleY=1/this._constrainScale(this.scaleY);ctx.lineWidth=1/this.borderScaleFactor;var vpt=this.getViewportTransform(),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),vpt,true),sxy=fabric.util.transformPoint(new fabric.Point(scaleX,scaleY),vpt,true),w=wh.x,h=wh.y,sx=sxy.x,sy=sxy.y;if(this.group){w=w*this.group.scaleX;h=h*this.group.scaleY}ctx.strokeRect(~~(-(w/2)-padding-strokeWidth/2*sx)-.5,~~(-(h/2)-padding-strokeWidth/2*sy)-.5,~~(w+padding2+strokeWidth*sx)+1,~~(h+padding2+strokeWidth*sy)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var rotateHeight=(this.flipY?h+strokeWidth*sx+padding*2:-h-strokeWidth*sy-padding*2)/2;ctx.beginPath();ctx.moveTo(0,rotateHeight);ctx.lineTo(0,rotateHeight+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset));ctx.closePath();ctx.stroke()}ctx.restore();return this},drawControls:function(ctx){if(!this.hasControls)return this;var size=this.cornerSize,size2=size/2,strokeWidth2=~~(this.strokeWidth/2),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),this.getViewportTransform(),true),width=wh.x,height=wh.y,left=-(width/2),top=-(height/2),padding=this.padding,scaleOffset=size2,scaleOffsetSize=size2-size,methodName=this.transparentCorners?"strokeRect":"fillRect";ctx.save();ctx.lineWidth=1;ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=ctx.fillStyle=this.cornerColor;this._drawControl("tl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("tr",ctx,methodName,left+width-scaleOffset+strokeWidth2+padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("bl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("br",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height+scaleOffsetSize+strokeWidth2+padding);if(!this.get("lockUniScaling")){this._drawControl("mt",ctx,methodName,left+width/2-scaleOffset,top-scaleOffset-strokeWidth2-padding);this._drawControl("mb",ctx,methodName,left+width/2-scaleOffset,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("mr",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height/2-scaleOffset);this._drawControl("ml",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height/2-scaleOffset)}if(this.hasRotatingPoint){this._drawControl("mtr",ctx,methodName,left+width/2-scaleOffset,this.flipY?top+height+this.rotatingPointOffset-this.cornerSize/2+strokeWidth2+padding:top-this.rotatingPointOffset-this.cornerSize/2-strokeWidth2-padding)}ctx.restore();return this},_drawControl:function(control,ctx,methodName,left,top){var size=this.cornerSize;if(this.isControlVisible(control)){isVML||this.transparentCorners||ctx.clearRect(left,top,size,size);ctx[methodName](left,top,size,size)}},isControlVisible:function(controlName){return this._getControlsVisibility()[controlName]},setControlVisible:function(controlName,visible){this._getControlsVisibility()[controlName]=visible;return this},setControlsVisibility:function(options){options||(options={});for(var p in options){this.setControlVisible(p,options[p])}return this},_getControlsVisibility:function(){if(!this._controlsVisibility){this._controlsVisibility={tl:true,tr:true,br:true,bl:true,ml:true,mt:true,mr:true,mb:true,mtr:true}}return this._controlsVisibility}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(value){object.set("left",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxCenterObjectV:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(value){object.set("top",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxRemove:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){object.set("active",false)},onChange:function(value){object.set("opacity",value);_this.renderAll();onChange()},onComplete:function(){_this.remove(object);onComplete()}});return this}});fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]==="object"){var propsToAnimate=[],prop,skipCallbacks;for(prop in arguments[0]){propsToAnimate.push(prop)}for(var i=0,len=propsToAnimate.length;i');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Line.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" "));fabric.Line.fromElement=function(element,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Line.ATTRIBUTE_NAMES),points=[parsedAttributes.x1||0,parsedAttributes.y1||0,parsedAttributes.x2||0,parsedAttributes.y2||0];return new fabric.Line(points,extend(parsedAttributes,options))};fabric.Line.fromObject=function(object){var points=[object.x1,object.y1,object.x2,object.y2];return new fabric.Line(points,object)};function makeEdgeToOriginGetter(propertyNames,originValues){var origin=propertyNames.origin,axis1=propertyNames.axis1,axis2=propertyNames.axis2,dimension=propertyNames.dimension,nearest=originValues.nearest,center=originValues.center,farthest=originValues.farthest;return function(){switch(this.get(origin)){case nearest:return Math.min(this.get(axis1),this.get(axis2));case center:return Math.min(this.get(axis1),this.get(axis2))+.5*this.get(dimension);case farthest:return Math.max(this.get(axis1),this.get(axis2))}}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Circle){fabric.warn("fabric.Circle is already defined.");return}fabric.Circle=fabric.util.createClass(fabric.Object,{type:"circle",radius:0,initialize:function(options){options=options||{};this.set("radius",options.radius||0);this.callSuper("initialize",options)},_set:function(key,value){this.callSuper("_set",key,value);if(key==="radius"){this.setRadius(value)}return this},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{radius:this.get("radius")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx,noTransform){ctx.beginPath();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.stroke&&this._renderStroke(ctx)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(value){this.radius=value;this.set("width",value*2).set("height",value*2)},complexity:function(){return 1}});fabric.Circle.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" "));fabric.Circle.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Circle.ATTRIBUTE_NAMES);if(!isValidRadius(parsedAttributes)){throw new Error("value of `r` attribute is required and can not be negative")}if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var obj=new fabric.Circle(extend(parsedAttributes,options));obj.cx=parseFloat(element.getAttribute("cx"))||0;obj.cy=parseFloat(element.getAttribute("cy"))||0;return obj};function isValidRadius(attributes){return"radius"in attributes&&attributes.radius>0}fabric.Circle.fromObject=function(object){return new fabric.Circle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Triangle){fabric.warn("fabric.Triangle is already defined");return}fabric.Triangle=fabric.util.createClass(fabric.Object,{type:"triangle",initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("width",options.width||100).set("height",options.height||100)},_render:function(ctx){var widthBy2=this.width/2,heightBy2=this.height/2;ctx.beginPath();ctx.moveTo(-widthBy2,heightBy2);ctx.lineTo(0,-heightBy2);ctx.lineTo(widthBy2,heightBy2);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_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()},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),widthBy2=this.width/2,heightBy2=this.height/2,points=[-widthBy2+" "+heightBy2,"0 "+-heightBy2,widthBy2+" "+heightBy2].join(",");markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Triangle.fromObject=function(object){return new fabric.Triangle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Ellipse){fabric.warn("fabric.Ellipse is already defined.");return}fabric.Ellipse=fabric.util.createClass(fabric.Object,{type:"ellipse",rx:0,ry:0,initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("rx",options.rx||0);this.set("ry",options.ry||0);this.set("width",this.get("rx")*2);this.set("height",this.get("ry")*2)},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},render:function(ctx,noTransform){if(this.rx===0||this.ry===0)return;return this.callSuper("render",ctx,noTransform)},_render:function(ctx,noTransform){ctx.beginPath();ctx.save();ctx.globalAlpha=this.group?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&this.group){ctx.translate(this.cx,this.cy)}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);ctx.restore();this._renderFill(ctx);this._renderStroke(ctx)},complexity:function(){return 1}});fabric.Ellipse.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" "));fabric.Ellipse.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Ellipse.ATTRIBUTE_NAMES),cx=parsedAttributes.left,cy=parsedAttributes.top;if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var ellipse=new fabric.Ellipse(extend(parsedAttributes,options));ellipse.cx=cx||0;ellipse.cy=cy||0;return ellipse};fabric.Ellipse.fromObject=function(object){return new fabric.Ellipse(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;if(fabric.Rect){console.warn("fabric.Rect is already defined");return}var stateProperties=fabric.Object.prototype.stateProperties.concat();stateProperties.push("rx","ry","x","y");fabric.Rect=fabric.util.createClass(fabric.Object,{stateProperties:stateProperties,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(options){options=options||{};this.callSuper("initialize",options);this._initRxRy();this.x=options.x||0;this.y=options.y||0},_initRxRy:function(){if(this.rx&&!this.ry){this.ry=this.rx}else if(this.ry&&!this.rx){this.rx=this.ry}},_render:function(ctx){if(this.width===1&&this.height===1){ctx.fillRect(0,0,1,1);return}var rx=this.rx?Math.min(this.rx,this.width/2):0,ry=this.ry?Math.min(this.ry,this.height/2):0,w=this.width,h=this.height,x=-w/2,y=-h/2,isInPathGroup=this.group&&this.group.type==="path-group",isRounded=rx!==0||ry!==0,k=1-.5522847498;ctx.beginPath();ctx.globalAlpha=isInPathGroup?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&isInPathGroup){ctx.translate(this.width/2+this.x,this.height/2+this.y)}if(!this.transformMatrix&&isInPathGroup){ctx.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y)}ctx.moveTo(x+rx,y);ctx.lineTo(x+w-rx,y);isRounded&&ctx.bezierCurveTo(x+w-k*rx,y,x+w,y+k*ry,x+w,y+ry);ctx.lineTo(x+w,y+h-ry);isRounded&&ctx.bezierCurveTo(x+w,y+h-k*ry,x+w-k*rx,y+h,x+w-rx,y+h);ctx.lineTo(x+rx,y+h);isRounded&&ctx.bezierCurveTo(x+k*rx,y+h,x,y+h-k*ry,x,y+h-ry);ctx.lineTo(x,y+ry);isRounded&&ctx.bezierCurveTo(x,y+k*ry,x+k*rx,y,x+rx,y);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var x=-this.width/2,y=-this.height/2,w=this.width,h=this.height;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()},_normalizeLeftTopProperties:function(parsedAttributes){if("left"in parsedAttributes){this.set("left",parsedAttributes.left+this.getWidth()/2)}this.set("x",parsedAttributes.left||0);if("top"in parsedAttributes){this.set("top",parsedAttributes.top+this.getHeight()/2)}this.set("y",parsedAttributes.top||0);return this},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Rect.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" "));function _setDefaultLeftTopValues(attributes){attributes.left=attributes.left||0;attributes.top=attributes.top||0;return attributes}fabric.Rect.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Rect.ATTRIBUTE_NAMES);parsedAttributes=_setDefaultLeftTopValues(parsedAttributes);var rect=new fabric.Rect(extend(options?fabric.util.object.clone(options):{},parsedAttributes));rect._normalizeLeftTopProperties(parsedAttributes);return rect};fabric.Rect.fromObject=function(object){return new fabric.Rect(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),toFixed=fabric.util.toFixed;if(fabric.Polyline){fabric.warn("fabric.Polyline is already defined");return}fabric.Polyline=fabric.util.createClass(fabric.Object,{type:"polyline",points:null,initialize:function(points,options,skipOffset){options=options||{};this.set("points",points);this.callSuper("initialize",options);this._calcDimensions(skipOffset)},_calcDimensions:function(skipOffset){return fabric.Polygon.prototype._calcDimensions.call(this,skipOffset)},toObject:function(propertiesToInclude){return fabric.Polygon.prototype.toObject.call(this,propertiesToInclude)},toSVG:function(reviver){var points=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i"},toObject:function(propertiesToInclude){var o=extend(this.callSuper("toObject",propertiesToInclude),{path:this.path.map(function(item){return item.slice()}),pathOffset:this.pathOffset});if(this.sourcePath){o.sourcePath=this.sourcePath}if(this.transformMatrix){o.transformMatrix=this.transformMatrix}return o},toDatalessObject:function(propertiesToInclude){var o=this.toObject(propertiesToInclude);if(this.sourcePath){o.path=this.sourcePath}delete o.sourcePath;return o},toSVG:function(reviver){var chunks=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.path.length;i',"","");return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return this.path.length},_parsePath:function(){var result=[],coords=[],currentPath,parsed,re=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,match,coordsStr;for(var i=0,coordsParsed,len=this.path.length;icommandLength){for(var k=1,klen=coordsParsed.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 delta 44082 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≪Go82z=2j6EH*bq0r`D7UOkzCVwom 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 Date: Fri, 13 Jun 2014 11:23:13 +0100 Subject: [PATCH 14/27] Fixes for group coordinates (coordinates now also generated before group is added to canvas) --- dist/fabric.js | 11 +++++------ dist/fabric.min.js | 2 +- dist/fabric.min.js.gz | Bin 79868 -> 79863 bytes dist/fabric.require.js | 9 ++++----- src/shapes/group.class.js | 11 +++++------ 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/dist/fabric.js b/dist/fabric.js index 2aead10f..087d2c4e 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -19314,7 +19314,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot extend(this, options); } this._setOpacityIfSame(); - //this.setCoords(); + this._calcBounds(); + this._updateObjectsCoords(); + this.setCoords(); this.saveCoords(); }, @@ -19699,11 +19701,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt; - if (this.canvas) { - ivt = fabric.util.invertTransform(this.getViewportTransform()); - } - var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), + var ivt = fabric.util.invertTransform(this.getViewportTransform()), + minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { width: (maxXY.x - minXY.x) || 0, diff --git a/dist/fabric.min.js b/dist/fabric.min.js index 3f694a1d..8c97a57a 100644 --- a/dist/fabric.min.js +++ b/dist/fabric.min.js @@ -6,7 +6,7 @@ var id=getID(target)+"."+getID(listener)+"."+(useCapture?1:0);if(root.Gesture&&r return target.containsPoint(xy)||target._findTargetCorner(pointer)},_normalizePointer:function(object,pointer){var activeGroup=this.getActiveGroup(),x=pointer.x,y=pointer.y,isObjectInGroup=activeGroup&&object.type!=="group"&&activeGroup.contains(object),lt;if(isObjectInGroup){lt=new fabric.Point(activeGroup.left,activeGroup.top);lt=fabric.util.transformPoint(lt,this.viewportTransform,true);x-=lt.x;y-=lt.y}return{x:x,y:y}},isTargetTransparent:function(target,x,y){var hasBorders=target.hasBorders,transparentCorners=target.transparentCorners;target.hasBorders=target.transparentCorners=false;this._draw(this.contextCache,target);target.hasBorders=hasBorders;target.transparentCorners=transparentCorners;var isTransparent=fabric.util.isTransparent(this.contextCache,x,y,this.targetFindTolerance);this.clearContext(this.contextCache);return isTransparent},_shouldClearSelection:function(e,target){var activeGroup=this.getActiveGroup(),activeObject=this.getActiveObject();return!target||target&&activeGroup&&!activeGroup.contains(target)&&activeGroup!==target&&!e.shiftKey||target&&!target.evented||target&&!target.selectable&&activeObject&&activeObject!==target},_shouldCenterTransform:function(e,target){if(!target)return;var t=this._currentTransform,centerTransform;if(t.action==="scale"||t.action==="scaleX"||t.action==="scaleY"){centerTransform=this.centeredScaling||target.centeredScaling}else if(t.action==="rotate"){centerTransform=this.centeredRotation||target.centeredRotation}return centerTransform?!e.altKey:e.altKey},_getOriginFromCorner:function(target,corner){var origin={x:target.originX,y:target.originY};if(corner==="ml"||corner==="tl"||corner==="bl"){origin.x="right"}else if(corner==="mr"||corner==="tr"||corner==="br"){origin.x="left"}if(corner==="tl"||corner==="mt"||corner==="tr"){origin.y="bottom"}else if(corner==="bl"||corner==="mb"||corner==="br"){origin.y="top"}return origin},_getActionFromCorner:function(target,corner){var action="drag";if(corner){action=corner==="ml"||corner==="mr"?"scaleX":corner==="mt"||corner==="mb"?"scaleY":corner==="mtr"?"rotate":"scale"}return action},_setupCurrentTransform:function(e,target){if(!target)return;var pointer=this.getPointer(e),corner=target._findTargetCorner(this.getPointer(e,true)),action=this._getActionFromCorner(target,corner),origin=this._getOriginFromCorner(target,corner);this._currentTransform={target:target,action:action,scaleX:target.scaleX,scaleY:target.scaleY,offsetX:pointer.x-target.left,offsetY:pointer.y-target.top,originX:origin.x,originY:origin.y,ex:pointer.x,ey:pointer.y,left:target.left,top:target.top,theta:degreesToRadians(target.angle),width:target.width*target.scaleX,mouseXSign:1,mouseYSign:1};this._currentTransform.original={left:target.left,top:target.top,scaleX:target.scaleX,scaleY:target.scaleY,originX:origin.x,originY:origin.y};this._resetCurrentTransform(e)},_translateObject:function(x,y){var target=this._currentTransform.target;if(!target.get("lockMovementX")){target.set("left",x-this._currentTransform.offsetX)}if(!target.get("lockMovementY")){target.set("top",y-this._currentTransform.offsetY)}},_scaleObject:function(x,y,by){var t=this._currentTransform,target=t.target,lockScalingX=target.get("lockScalingX"),lockScalingY=target.get("lockScalingY");if(lockScalingX&&lockScalingY)return;var constraintPosition=target.translateToOriginPoint(target.getCenterPoint(),t.originX,t.originY),localMouse=target.toLocalPoint(new fabric.Point(x,y),t.originX,t.originY);this._setLocalMouse(localMouse,t);this._setObjectScale(localMouse,t,lockScalingX,lockScalingY,by);target.setPositionByOrigin(constraintPosition,t.originX,t.originY)},_setObjectScale:function(localMouse,transform,lockScalingX,lockScalingY,by){var target=transform.target;transform.newScaleX=target.scaleX;transform.newScaleY=target.scaleY;if(by==="equally"&&!lockScalingX&&!lockScalingY){this._scaleObjectEqually(localMouse,target,transform)}else if(!by){transform.newScaleX=localMouse.x/(target.width+target.strokeWidth);transform.newScaleY=localMouse.y/(target.height+target.strokeWidth);lockScalingX||target.set("scaleX",transform.newScaleX);lockScalingY||target.set("scaleY",transform.newScaleY)}else if(by==="x"&&!target.get("lockUniScaling")){transform.newScaleX=localMouse.x/(target.width+target.strokeWidth);lockScalingX||target.set("scaleX",transform.newScaleX)}else if(by==="y"&&!target.get("lockUniScaling")){transform.newScaleY=localMouse.y/(target.height+target.strokeWidth);lockScalingY||target.set("scaleY",transform.newScaleY)}this._flipObject(transform)},_scaleObjectEqually:function(localMouse,target,transform){var dist=localMouse.y+localMouse.x,lastDist=(target.height+target.strokeWidth)*transform.original.scaleY+(target.width+target.strokeWidth)*transform.original.scaleX;transform.newScaleX=transform.original.scaleX*dist/lastDist;transform.newScaleY=transform.original.scaleY*dist/lastDist;target.set("scaleX",transform.newScaleX);target.set("scaleY",transform.newScaleY)},_flipObject:function(transform){if(transform.newScaleX<0){if(transform.originX==="left"){transform.originX="right"}else if(transform.originX==="right"){transform.originX="left"}}if(transform.newScaleY<0){if(transform.originY==="top"){transform.originY="bottom"}else if(transform.originY==="bottom"){transform.originY="top"}}},_setLocalMouse:function(localMouse,t){var target=t.target;if(t.originX==="right"){localMouse.x*=-1}else if(t.originX==="center"){localMouse.x*=t.mouseXSign*2;if(localMouse.x<0){t.mouseXSign=-t.mouseXSign}}if(t.originY==="bottom"){localMouse.y*=-1}else if(t.originY==="center"){localMouse.y*=t.mouseYSign*2;if(localMouse.y<0){t.mouseYSign=-t.mouseYSign}}if(abs(localMouse.x)>target.padding){if(localMouse.x<0){localMouse.x+=target.padding}else{localMouse.x-=target.padding}}else{localMouse.x=0}if(abs(localMouse.y)>target.padding){if(localMouse.y<0){localMouse.y+=target.padding}else{localMouse.y-=target.padding}}else{localMouse.y=0}},_rotateObject:function(x,y){var t=this._currentTransform;if(t.target.get("lockRotation"))return;var lastAngle=atan2(t.ey-t.top,t.ex-t.left),curAngle=atan2(y-t.top,x-t.left),angle=radiansToDegrees(curAngle-lastAngle+t.theta);if(angle<0){angle=360+angle}t.target.angle=angle},_setCursor:function(value){this.upperCanvasEl.style.cursor=value},_resetObjectTransform:function(target){target.scaleX=1;target.scaleY=1;target.setAngle(0)},_drawSelection:function(){var ctx=this.contextTop,groupSelector=this._groupSelector,left=groupSelector.left,top=groupSelector.top,aleft=abs(left),atop=abs(top);ctx.fillStyle=this.selectionColor;ctx.fillRect(groupSelector.ex-(left>0?0:-left),groupSelector.ey-(top>0?0:-top),aleft,atop);ctx.lineWidth=this.selectionLineWidth;ctx.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var px=groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),py=groupSelector.ey+STROKE_OFFSET-(top>0?0:atop);ctx.beginPath();fabric.util.drawDashedLine(ctx,px,py,px+aleft,py,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py+atop-1,px+aleft,py+atop-1,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px,py,px,py+atop,this.selectionDashArray);fabric.util.drawDashedLine(ctx,px+aleft-1,py,px+aleft-1,py+atop,this.selectionDashArray);ctx.closePath();ctx.stroke()}else{ctx.strokeRect(groupSelector.ex+STROKE_OFFSET-(left>0?0:aleft),groupSelector.ey+STROKE_OFFSET-(top>0?0:atop),aleft,atop)}},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,true))},findTarget:function(e,skipGroup){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e)){return this.lastRenderedObjectWithControlsAboveOverlay}var activeGroup=this.getActiveGroup();if(activeGroup&&!skipGroup&&this.containsPoint(e,activeGroup)){return activeGroup}var target=this._searchPossibleTargets(e);this._fireOverOutEvents(target);return target},_fireOverOutEvents:function(target){if(target){if(this._hoveredTarget!==target){this.fire("mouse:over",{target:target});target.fire("mouseover");if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout")}this._hoveredTarget=target}}else if(this._hoveredTarget){this.fire("mouse:out",{target:this._hoveredTarget});this._hoveredTarget.fire("mouseout");this._hoveredTarget=null}},_checkTarget:function(e,obj,pointer){if(obj&&obj.visible&&obj.evented&&this.containsPoint(e,obj)){if((this.perPixelTargetFind||obj.perPixelTargetFind)&&!obj.isEditing){var isTransparent=this.isTargetTransparent(obj,pointer.x,pointer.y);if(!isTransparent){return true}}else{return true}}},_searchPossibleTargets:function(e){var target,pointer=this.getPointer(e,true);var i=this._objects.length;while(i--){if(this._checkTarget(e,this._objects[i],pointer)){this.relatedTarget=this._objects[i];target=this._objects[i];break}}return target},getPointer:function(e,ignoreZoom,upperCanvasEl){if(!upperCanvasEl){upperCanvasEl=this.upperCanvasEl}var pointer=getPointer(e,upperCanvasEl),bounds=upperCanvasEl.getBoundingClientRect(),cssScale;pointer.x=pointer.x-this._offset.left;pointer.y=pointer.y-this._offset.top;if(!ignoreZoom){pointer=fabric.util.transformPoint(pointer,fabric.util.invertTransform(this.viewportTransform))}if(bounds.width===0||bounds.height===0){cssScale={width:1,height:1}}else{cssScale={width:upperCanvasEl.width/bounds.width,height:upperCanvasEl.height/bounds.height}}return{x:pointer.x*cssScale.width,y:pointer.y*cssScale.height}},_createUpperCanvas:function(){var lowerCanvasClass=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement();fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+lowerCanvasClass);this.wrapperEl.appendChild(this.upperCanvasEl);this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl);this._applyCanvasStyle(this.upperCanvasEl);this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement();this.cacheCanvasEl.setAttribute("width",this.width);this.cacheCanvasEl.setAttribute("height",this.height);this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass});fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"});fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(element){var width=this.getWidth()||element.width,height=this.getHeight()||element.height;fabric.util.setStyle(element,{position:"absolute",width:width+"px",height:height+"px",left:0,top:0});element.width=width;element.height=height;fabric.util.makeElementUnselectable(element)},_copyCanvasStyle:function(fromEl,toEl){toEl.style.cssText=fromEl.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(object){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=object;object.set("active",true)},setActiveObject:function(object,e){this._setActiveObject(object);this.renderAll();this.fire("object:selected",{target:object,e:e});object.fire("selected",{e:e});return this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){if(this._activeObject){this._activeObject.set("active",false)}this._activeObject=null},discardActiveObject:function(e){this._discardActiveObject();this.renderAll();this.fire("selection:cleared",{e:e});return this},_setActiveGroup:function(group){this._activeGroup=group;if(group){group.canvas=this;group._calcBounds();group._updateObjectsCoords();group.setCoords();group.set("active",true)}},setActiveGroup:function(group,e){this._setActiveGroup(group);if(group){this.fire("object:selected",{target:group,e:e});group.fire("selected",{e:e})}return this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var g=this.getActiveGroup();if(g){g.destroy()}this.setActiveGroup(null)},discardActiveGroup:function(e){this._discardActiveGroup();this.fire("selection:cleared",{e:e});return this},deactivateAll:function(){var allObjects=this.getObjects(),i=0,len=allObjects.length;for(;i1){group=new fabric.Group(group.reverse(),{originX:"center",originY:"center"});this.setActiveGroup(group,e);group.saveCoords();this.fire("selection:created",{target:group});this.renderAll()}},_collectObjects:function(){var group=[],currentObject,x1=this._groupSelector.ex,y1=this._groupSelector.ey,x2=x1+this._groupSelector.left,y2=y1+this._groupSelector.top,selectionX1Y1=new fabric.Point(min(x1,x2),min(y1,y2)),selectionX2Y2=new fabric.Point(max(x1,x2),max(y1,y2)),isClick=x1===x2&&y1===y2;for(var i=this._objects.length;i--;){currentObject=this._objects[i];if(!currentObject||!currentObject.selectable||!currentObject.visible)continue;if(currentObject.intersectsWithRect(selectionX1Y1,selectionX2Y2)||currentObject.isContainedWithinRect(selectionX1Y1,selectionX2Y2)||currentObject.containsPoint(selectionX1Y1)||currentObject.containsPoint(selectionX2Y2)){currentObject.set("active",true);group.push(currentObject);if(isClick)break}}return group},_maybeGroupObjects:function(e){if(this.selection&&this._groupSelector){this._groupSelectedObjects(e)}var activeGroup=this.getActiveGroup();if(activeGroup){activeGroup.setObjectsCoords().setCoords();activeGroup.isMoving=false;this._setCursor(this.defaultCursor)}this._groupSelector=null;this._currentTransform=null}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(options){options||(options={});var format=options.format||"png",quality=options.quality||1,multiplier=options.multiplier||1,cropping={left:options.left,top:options.top,width:options.width,height:options.height};if(multiplier!==1){return this.__toDataURLWithMultiplier(format,quality,cropping,multiplier)}else{return this.__toDataURL(format,quality,cropping)}},__toDataURL:function(format,quality,cropping){this.renderAll(true);var canvasEl=this.upperCanvasEl||this.lowerCanvasEl,croppedCanvasEl=this.__getCroppedCanvas(canvasEl,cropping);if(format==="jpg"){format="jpeg"}var data=fabric.StaticCanvas.supports("toDataURLWithQuality")?(croppedCanvasEl||canvasEl).toDataURL("image/"+format,quality):(croppedCanvasEl||canvasEl).toDataURL("image/"+format);this.contextTop&&this.clearContext(this.contextTop);this.renderAll();if(croppedCanvasEl){croppedCanvasEl=null}return data},__getCroppedCanvas:function(canvasEl,cropping){var croppedCanvasEl,croppedCtx,shouldCrop="left"in cropping||"top"in cropping||"width"in cropping||"height"in cropping;if(shouldCrop){croppedCanvasEl=fabric.util.createCanvasElement();croppedCtx=croppedCanvasEl.getContext("2d");croppedCanvasEl.width=cropping.width||this.width;croppedCanvasEl.height=cropping.height||this.height;croppedCtx.drawImage(canvasEl,-cropping.left||0,-cropping.top||0)}return croppedCanvasEl},__toDataURLWithMultiplier:function(format,quality,cropping,multiplier){var origWidth=this.getWidth(),origHeight=this.getHeight(),scaledWidth=origWidth*multiplier,scaledHeight=origHeight*multiplier,activeObject=this.getActiveObject(),activeGroup=this.getActiveGroup(),ctx=this.contextTop||this.contextContainer;if(multiplier>1){this.setWidth(scaledWidth).setHeight(scaledHeight)}ctx.scale(multiplier,multiplier);if(cropping.left){cropping.left*=multiplier}if(cropping.top){cropping.top*=multiplier}if(cropping.width){cropping.width*=multiplier}else if(multiplier<1){cropping.width=scaledWidth}if(cropping.height){cropping.height*=multiplier}else if(multiplier<1){cropping.height=scaledHeight}if(activeGroup){this._tempRemoveBordersControlsFromGroup(activeGroup)}else if(activeObject&&this.deactivateAll){this.deactivateAll()}this.renderAll(true);var data=this.__toDataURL(format,quality,cropping);this.width=origWidth;this.height=origHeight;ctx.scale(1/multiplier,1/multiplier);this.setWidth(origWidth).setHeight(origHeight);if(activeGroup){this._restoreBordersControlsOnGroup(activeGroup)}else if(activeObject&&this.setActiveObject){this.setActiveObject(activeObject)}this.contextTop&&this.clearContext(this.contextTop);this.renderAll();return data},toDataURLWithMultiplier:function(format,multiplier,quality){return this.toDataURL({format:format,multiplier:multiplier,quality:quality})},_tempRemoveBordersControlsFromGroup:function(group){group.origHasControls=group.hasControls;group.origBorderColor=group.borderColor;group.hasControls=true;group.borderColor="rgba(0,0,0,0)";group.forEachObject(function(o){o.origBorderColor=o.borderColor;o.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(group){group.hideControls=group.origHideControls;group.borderColor=group.origBorderColor;group.forEachObject(function(o){o.borderColor=o.origBorderColor;delete o.origBorderColor})}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(json,callback,reviver){return this.loadFromJSON(json,callback,reviver)},loadFromJSON:function(json,callback,reviver){if(!json)return;var serialized=typeof json==="string"?JSON.parse(json):json;this.clear();var _this=this;this._enlivenObjects(serialized.objects,function(){_this._setBgOverlay(serialized,callback)},reviver);return this},_setBgOverlay:function(serialized,callback){var _this=this,loaded={backgroundColor:false,overlayColor:false,backgroundImage:false,overlayImage:false};if(!serialized.backgroundImage&&!serialized.overlayImage&&!serialized.background&&!serialized.overlay){callback&&callback(); return}var cbIfLoaded=function(){if(loaded.backgroundImage&&loaded.overlayImage&&loaded.backgroundColor&&loaded.overlayColor){_this.renderAll();callback&&callback()}};this.__setBgOverlay("backgroundImage",serialized.backgroundImage,loaded,cbIfLoaded);this.__setBgOverlay("overlayImage",serialized.overlayImage,loaded,cbIfLoaded);this.__setBgOverlay("backgroundColor",serialized.background,loaded,cbIfLoaded);this.__setBgOverlay("overlayColor",serialized.overlay,loaded,cbIfLoaded);cbIfLoaded()},__setBgOverlay:function(property,value,loaded,callback){var _this=this;if(!value){loaded[property]=true;return}if(property==="backgroundImage"||property==="overlayImage"){fabric.Image.fromObject(value,function(img){_this[property]=img;loaded[property]=true;callback&&callback()})}else{this["set"+fabric.util.string.capitalize(property,true)](value,function(){loaded[property]=true;callback&&callback()})}},_enlivenObjects:function(objects,callback,reviver){var _this=this;if(!objects||objects.length===0){callback&&callback();return}var renderOnAddRemove=this.renderOnAddRemove;this.renderOnAddRemove=false;fabric.util.enlivenObjects(objects,function(enlivenedObjects){enlivenedObjects.forEach(function(obj,index){_this.insertAt(obj,index,true)});_this.renderOnAddRemove=renderOnAddRemove;callback&&callback()},null,reviver)},_toDataURL:function(format,callback){this.clone(function(clone){callback(clone.toDataURL(format))})},_toDataURLWithMultiplier:function(format,multiplier,callback){this.clone(function(clone){callback(clone.toDataURLWithMultiplier(format,multiplier))})},clone:function(callback,properties){var data=JSON.stringify(this.toJSON(properties));this.cloneWithoutData(function(clone){clone.loadFromJSON(data,function(){callback&&callback(clone)})})},cloneWithoutData:function(callback){var el=fabric.document.createElement("canvas");el.width=this.getWidth();el.height=this.getHeight();var clone=new fabric.Canvas(el);clone.clipTo=this.clipTo;if(this.backgroundImage){clone.setBackgroundImage(this.backgroundImage.src,function(){clone.renderAll();callback&&callback(clone)});clone.backgroundImageOpacity=this.backgroundImageOpacity;clone.backgroundImageStretch=this.backgroundImageStretch}else{callback&&callback(clone)}}});(function(){var degreesToRadians=fabric.util.degreesToRadians,radiansToDegrees=fabric.util.radiansToDegrees;fabric.util.object.extend(fabric.Canvas.prototype,{__onTransformGesture:function(e,self){if(this.isDrawingMode||!e.touches||e.touches.length!==2||"gesture"!==self.gesture){return}var target=this.findTarget(e);if("undefined"!==typeof target){this.onBeforeScaleRotate(target);this._rotateObjectByAngle(self.rotation);this._scaleObjectBy(self.scale)}this.fire("touch:gesture",{target:target,e:e,self:self})},__onDrag:function(e,self){this.fire("touch:drag",{e:e,self:self})},__onOrientationChange:function(e,self){this.fire("touch:orientation",{e:e,self:self})},__onShake:function(e,self){this.fire("touch:shake",{e:e,self:self})},_scaleObjectBy:function(s,by){var t=this._currentTransform,target=t.target,lockScalingX=target.get("lockScalingX"),lockScalingY=target.get("lockScalingY");if(lockScalingX&&lockScalingY)return;target._scaling=true;if(!by){if(!lockScalingX){target.set("scaleX",t.scaleX*s)}if(!lockScalingY){target.set("scaleY",t.scaleY*s)}}else if(by==="x"&&!target.get("lockUniScaling")){lockScalingX||target.set("scaleX",t.scaleX*s)}else if(by==="y"&&!target.get("lockUniScaling")){lockScalingY||target.set("scaleY",t.scaleY*s)}},_rotateObjectByAngle:function(curAngle){var t=this._currentTransform;if(t.target.get("lockRotation"))return;t.target.angle=radiansToDegrees(degreesToRadians(curAngle)+t.theta)}})})();(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,toFixed=fabric.util.toFixed,capitalize=fabric.util.string.capitalize,degreesToRadians=fabric.util.degreesToRadians,supportsLineDash=fabric.StaticCanvas.supports("setLineDash");if(fabric.Object){return}fabric.Object=fabric.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:false,flipY:false,opacity:1,angle:0,cornerSize:12,transparentCorners:true,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:false,centeredRotation:true,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:true,evented:true,visible:true,hasControls:true,hasBorders:true,hasRotatingPoint:true,rotatingPointOffset:40,perPixelTargetFind:false,includeDefaultValues:true,clipTo:null,lockMovementX:false,lockMovementY:false,lockRotation:false,lockScalingX:false,lockScalingY:false,lockUniScaling:false,stateProperties:("top left width height scaleX scaleY flipX flipY originX originY transformMatrix "+"stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit "+"angle opacity fill fillRule shadow clipTo visible backgroundColor").split(" "),initialize:function(options){if(options){this.setOptions(options)}},_initGradient:function(options){if(options.fill&&options.fill.colorStops&&!(options.fill instanceof fabric.Gradient)){this.set("fill",new fabric.Gradient(options.fill))}},_initPattern:function(options){if(options.fill&&options.fill.source&&!(options.fill instanceof fabric.Pattern)){this.set("fill",new fabric.Pattern(options.fill))}if(options.stroke&&options.stroke.source&&!(options.stroke instanceof fabric.Pattern)){this.set("stroke",new fabric.Pattern(options.stroke))}},_initClipping:function(options){if(!options.clipTo||typeof options.clipTo!=="string")return;var functionBody=fabric.util.getFunctionBody(options.clipTo);if(typeof functionBody!=="undefined"){this.clipTo=new Function("ctx",functionBody)}},setOptions:function(options){for(var prop in options){this.set(prop,options[prop])}this._initGradient(options);this._initPattern(options);this._initClipping(options)},transform:function(ctx,fromLeft){if(this.group){this.group.transform(ctx,fromLeft)}ctx.globalAlpha=this.opacity;var center=fromLeft?this._getLeftTopCoords():this.getCenterPoint();ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));ctx.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(propertiesToInclude){var NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,object={type:this.type,originX:this.originX,originY:this.originY,left:toFixed(this.left,NUM_FRACTION_DIGITS),top:toFixed(this.top,NUM_FRACTION_DIGITS),width:toFixed(this.width,NUM_FRACTION_DIGITS),height:toFixed(this.height,NUM_FRACTION_DIGITS),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:toFixed(this.strokeWidth,NUM_FRACTION_DIGITS),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:toFixed(this.strokeMiterLimit,NUM_FRACTION_DIGITS),scaleX:toFixed(this.scaleX,NUM_FRACTION_DIGITS),scaleY:toFixed(this.scaleY,NUM_FRACTION_DIGITS),angle:toFixed(this.getAngle(),NUM_FRACTION_DIGITS),flipX:this.flipX,flipY:this.flipY,opacity:toFixed(this.opacity,NUM_FRACTION_DIGITS),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};if(!this.includeDefaultValues){object=this._removeDefaultValues(object)}fabric.util.populateWithProperties(this,object,propertiesToInclude);return object},toDatalessObject:function(propertiesToInclude){return this.toObject(propertiesToInclude)},_removeDefaultValues:function(object){var prototype=fabric.util.getKlass(object.type).prototype,stateProperties=prototype.stateProperties;stateProperties.forEach(function(prop){if(object[prop]===prototype[prop]){delete object[prop]}});return object},toString:function(){return"#"},get:function(property){return this[property]},_setObject:function(obj){for(var prop in obj){this._set(prop,obj[prop])}},set:function(key,value){if(typeof key==="object"){this._setObject(key)}else{if(typeof value==="function"&&key!=="clipTo"){this._set(key,value(this.get(key)))}else{this._set(key,value)}}return this},_set:function(key,value){var shouldConstrainValue=key==="scaleX"||key==="scaleY";if(shouldConstrainValue){value=this._constrainScale(value)}if(key==="scaleX"&&value<0){this.flipX=!this.flipX;value*=-1}else if(key==="scaleY"&&value<0){this.flipY=!this.flipY;value*=-1}else if(key==="width"||key==="height"){this.minScaleLimit=toFixed(Math.min(.1,1/Math.max(this.width,this.height)),2)}else if(key==="shadow"&&value&&!(value instanceof fabric.Shadow)){value=new fabric.Shadow(value)}this[key]=value;return this},toggle:function(property){var value=this.get(property);if(typeof value==="boolean"){this.set(property,!value)}return this},setSourcePath:function(value){this.sourcePath=value;return this},getViewportTransform:function(){if(this.canvas&&this.canvas.viewportTransform)return this.canvas.viewportTransform;return[1,0,0,1,0,0]},render:function(ctx,noTransform){if(this.width===0||this.height===0||!this.visible)return;ctx.save();this._setupFillRule(ctx);this._transform(ctx,noTransform);this._setStrokeStyles(ctx);this._setFillStyles(ctx);var m=this.transformMatrix;if(m&&this.group){ctx.translate(-this.group.width/2,-this.group.height/2);ctx.transform(m[0],m[1],m[2],m[3],m[4],m[5])}this._setShadow(ctx);this.clipTo&&fabric.util.clipContext(this,ctx);this._render(ctx,noTransform);this.clipTo&&ctx.restore();this._removeShadow(ctx);this._restoreFillRule(ctx);ctx.restore()},_transform:function(ctx,noTransform){var m=this.transformMatrix;if(m&&!this.group){ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5])}if(!noTransform){this.transform(ctx)}},_setStrokeStyles:function(ctx){if(this.stroke){ctx.lineWidth=this.strokeWidth;ctx.lineCap=this.strokeLineCap;ctx.lineJoin=this.strokeLineJoin;ctx.miterLimit=this.strokeMiterLimit;ctx.strokeStyle=this.stroke.toLive?this.stroke.toLive(ctx):this.stroke}},_setFillStyles:function(ctx){if(this.fill){ctx.fillStyle=this.fill.toLive?this.fill.toLive(ctx):this.fill}},_renderControls:function(ctx,noTransform){var v=this.getViewportTransform();ctx.save();if(this.active&&!noTransform){var center;if(this.group){center=fabric.util.transformPoint(this.group.getCenterPoint(),v);ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.group.angle))}center=fabric.util.transformPoint(this.getCenterPoint(),v,null!=this.group);if(this.group){center.x*=this.group.scaleX;center.y*=this.group.scaleY}ctx.translate(center.x,center.y);ctx.rotate(degreesToRadians(this.angle));this.drawBorders(ctx);this.drawControls(ctx)}ctx.restore()},_setShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor=this.shadow.color;ctx.shadowBlur=this.shadow.blur;ctx.shadowOffsetX=this.shadow.offsetX;ctx.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(ctx){if(!this.shadow)return;ctx.shadowColor="";ctx.shadowBlur=ctx.shadowOffsetX=ctx.shadowOffsetY=0},_renderFill:function(ctx){if(!this.fill)return;if(this.fill.toLive){ctx.save();ctx.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)}if(this.fillRule==="destination-over"){ctx.fill("evenodd")}else{ctx.fill()}if(this.fill.toLive){ctx.restore()}if(this.shadow&&!this.shadow.affectStroke){this._removeShadow(ctx)}},_renderStroke:function(ctx){if(!this.stroke)return;ctx.save();if(this.strokeDashArray){if(1&this.strokeDashArray.length){this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray)}if(supportsLineDash){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);ctx.restore()},clone:function(callback,propertiesToInclude){if(this.constructor.fromObject){return this.constructor.fromObject(this.toObject(propertiesToInclude),callback)}return new fabric.Object(this.toObject(propertiesToInclude))},cloneAsImage:function(callback){var dataUrl=this.toDataURL();fabric.util.loadImage(dataUrl,function(img){if(callback){callback(new fabric.Image(img))}});return this},toDataURL:function(options){options||(options={});var el=fabric.util.createCanvasElement(),boundingRect=this.getBoundingRect();el.width=boundingRect.width;el.height=boundingRect.height;fabric.util.wrapElement(el,"div");var canvas=new fabric.Canvas(el);if(options.format==="jpg"){options.format="jpeg"}if(options.format==="jpeg"){canvas.backgroundColor="#fff"}var origParams={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",false);this.setPositionByOrigin(new fabric.Point(el.width/2,el.height/2),"center","center");var originalCanvas=this.canvas;canvas.add(this);var data=canvas.toDataURL(options);this.set(origParams).setCoords();this.canvas=originalCanvas;canvas.dispose();canvas=null;return data},isType:function(type){return this.type===type},complexity:function(){return 0},toJSON:function(propertiesToInclude){return this.toObject(propertiesToInclude)},setGradient:function(property,options){options||(options={});var gradient={colorStops:[]};gradient.type=options.type||(options.r1||options.r2?"radial":"linear");gradient.coords={x1:options.x1,y1:options.y1,x2:options.x2,y2:options.y2};if(options.r1||options.r2){gradient.coords.r1=options.r1;gradient.coords.r2=options.r2}for(var position in options.colorStops){var color=new fabric.Color(options.colorStops[position]);gradient.colorStops.push({offset:position,color:color.toRgb(),opacity:color.getAlpha()})}return this.set(property,fabric.Gradient.forObject(this,gradient))},setPatternFill:function(options){return this.set("fill",new fabric.Pattern(options))},setShadow:function(options){return this.set("shadow",new fabric.Shadow(options))},setColor:function(color){this.set("fill",color);return this},setAngle:function(angle){var shouldCenterOrigin=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;if(shouldCenterOrigin){this._setOriginToCenter()}this.set("angle",angle);if(shouldCenterOrigin){this._resetOrigin()}return this},centerH:function(){this.canvas.centerObjectH(this);return this},centerV:function(){this.canvas.centerObjectV(this);return this},center:function(){this.canvas.centerObject(this);return this},remove:function(){this.canvas.remove(this);return this},getLocalPointer:function(e,pointer){pointer=pointer||this.canvas.getPointer(e);var objectLeftTop=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:pointer.x-objectLeftTop.x,y:pointer.y-objectLeftTop.y}},_setupFillRule:function(ctx){if(this.fillRule){this._prevFillRule=ctx.globalCompositeOperation;ctx.globalCompositeOperation=this.fillRule}},_restoreFillRule:function(ctx){if(this.fillRule&&this._prevFillRule){ctx.globalCompositeOperation=this._prevFillRule}}});fabric.util.createAccessors(fabric.Object);fabric.Object.prototype.rotate=fabric.Object.prototype.setAngle;extend(fabric.Object.prototype,fabric.Observable);fabric.Object.NUM_FRACTION_DIGITS=2;fabric.Object.__uid=0})(typeof exports!=="undefined"?exports:this);(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(point,originX,originY){var cx=point.x,cy=point.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){cx=point.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){cx=point.x-(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){cy=point.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){cy=point.y-(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(cx,cy),point,degreesToRadians(this.angle))},translateToOriginPoint:function(center,originX,originY){var x=center.x,y=center.y,strokeWidth=this.stroke?this.strokeWidth:0;if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}return fabric.util.rotatePoint(new fabric.Point(x,y),center,degreesToRadians(this.angle))},getCenterPoint:function(){var leftTop=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(leftTop,this.originX,this.originY)},getPointByOrigin:function(originX,originY){var center=this.getCenterPoint();return this.translateToOriginPoint(center,originX,originY)},toLocalPoint:function(point,originX,originY){var center=this.getCenterPoint(),strokeWidth=this.stroke?this.strokeWidth:0,x,y;if(originX&&originY){if(originX==="left"){x=center.x-(this.getWidth()+strokeWidth*this.scaleX)/2}else if(originX==="right"){x=center.x+(this.getWidth()+strokeWidth*this.scaleX)/2}else{x=center.x}if(originY==="top"){y=center.y-(this.getHeight()+strokeWidth*this.scaleY)/2}else if(originY==="bottom"){y=center.y+(this.getHeight()+strokeWidth*this.scaleY)/2}else{y=center.y}}else{x=this.left;y=this.top}return fabric.util.rotatePoint(new fabric.Point(point.x,point.y),center,-degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x,y))},setPositionByOrigin:function(pos,originX,originY){var center=this.translateToCenterPoint(pos,originX,originY),position=this.translateToOriginPoint(center,this.originX,this.originY);this.set("left",position.x);this.set("top",position.y)},adjustPosition:function(to){var angle=degreesToRadians(this.angle),hypotHalf=this.getWidth()/2,xHalf=Math.cos(angle)*hypotHalf,yHalf=Math.sin(angle)*hypotHalf,hypotFull=this.getWidth(),xFull=Math.cos(angle)*hypotFull,yFull=Math.sin(angle)*hypotFull;if(this.originX==="center"&&to==="left"||this.originX==="right"&&to==="center"){this.left-=xHalf;this.top-=yHalf}else if(this.originX==="left"&&to==="center"||this.originX==="center"&&to==="right"){this.left+=xHalf;this.top+=yHalf}else if(this.originX==="left"&&to==="right"){this.left+=xFull;this.top+=yFull}else if(this.originX==="right"&&to==="left"){this.left-=xFull;this.top-=yFull}this.setCoords();this.originX=to},_setOriginToCenter:function(){this._originalOriginX=this.originX;this._originalOriginY=this.originY;var center=this.getCenterPoint();this.originX="center";this.originY="center";this.left=center.x;this.top=center.y},_resetOrigin:function(){var originPoint=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX;this.originY=this._originalOriginY;this.left=originPoint.x;this.top=originPoint.y;this._originalOriginX=null;this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})})();(function(){var degreesToRadians=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(pointTL,pointBR){var oCoords=this.oCoords,tl=new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr=new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl=new fabric.Point(oCoords.bl.x,oCoords.bl.y),br=new fabric.Point(oCoords.br.x,oCoords.br.y),intersection=fabric.Intersection.intersectPolygonRectangle([tl,tr,br,bl],pointTL,pointBR);return intersection.status==="Intersection"},intersectsWithObject:function(other){function getCoords(oCoords){return{tl:new fabric.Point(oCoords.tl.x,oCoords.tl.y),tr:new fabric.Point(oCoords.tr.x,oCoords.tr.y),bl:new fabric.Point(oCoords.bl.x,oCoords.bl.y),br:new fabric.Point(oCoords.br.x,oCoords.br.y)}}var thisCoords=getCoords(this.oCoords),otherCoords=getCoords(other.oCoords),intersection=fabric.Intersection.intersectPolygonPolygon([thisCoords.tl,thisCoords.tr,thisCoords.br,thisCoords.bl],[otherCoords.tl,otherCoords.tr,otherCoords.br,otherCoords.bl]);return intersection.status==="Intersection"},isContainedWithinObject:function(other){var boundingRect=other.getBoundingRect(),point1=new fabric.Point(boundingRect.left,boundingRect.top),point2=new fabric.Point(boundingRect.left+boundingRect.width,boundingRect.top+boundingRect.height);return this.isContainedWithinRect(point1,point2)},isContainedWithinRect:function(pointTL,pointBR){var boundingRect=this.getBoundingRect();return boundingRect.left>=pointTL.x&&boundingRect.left+boundingRect.width<=pointBR.x&&boundingRect.top>=pointTL.y&&boundingRect.top+boundingRect.height<=pointBR.y},containsPoint:function(point){var lines=this._getImageLines(this.oCoords),xPoints=this._findCrossPoints(point,lines);return xPoints!==0&&xPoints%2===1},_getImageLines:function(oCoords){return{topline:{o:oCoords.tl,d:oCoords.tr},rightline:{o:oCoords.tr,d:oCoords.br},bottomline:{o:oCoords.br,d:oCoords.bl},leftline:{o:oCoords.bl,d:oCoords.tl}}},_findCrossPoints:function(point,oCoords){var b1,b2,a1,a2,xi,yi,xcount=0,iLine;for(var lineKey in oCoords){iLine=oCoords[lineKey];if(iLine.o.y=point.y&&iLine.d.y>=point.y){continue}if(iLine.o.x===iLine.d.x&&iLine.o.x>=point.x){xi=iLine.o.x;yi=point.y}else{b1=0;b2=(iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);a1=point.y-b1*point.x;a2=iLine.o.y-b2*iLine.o.x;xi=-(a1-a2)/(b1-b2);yi=a1+b1*xi}if(xi>=point.x){xcount+=1}if(xcount===2){break}}return xcount},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var xCoords=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],minX=fabric.util.array.min(xCoords),maxX=fabric.util.array.max(xCoords),width=Math.abs(minX-maxX),yCoords=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],minY=fabric.util.array.min(yCoords),maxY=fabric.util.array.max(yCoords),height=Math.abs(minY-maxY);return{left:minX,top:minY,width:width,height:height}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(value){if(Math.abs(value)1?this.strokeWidth:0,theta=degreesToRadians(this.angle),vpt=this.getViewportTransform();var f=function(p){return fabric.util.transformPoint(p,vpt)};this.currentWidth=(this.width+strokeWidth)*this.scaleX;this.currentHeight=(this.height+strokeWidth)*this.scaleY;if(this.currentWidth<0){this.currentWidth=Math.abs(this.currentWidth)}var _hypotenuse=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),_angle=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),offsetX=Math.cos(_angle+theta)*_hypotenuse,offsetY=Math.sin(_angle+theta)*_hypotenuse,sinTh=Math.sin(theta),cosTh=Math.cos(theta),coords=this.getCenterPoint(),wh=new fabric.Point(this.currentWidth,this.currentHeight),_tl=new fabric.Point(coords.x-offsetX,coords.y-offsetY),_tr=new fabric.Point(_tl.x+wh.x*cosTh,_tl.y+wh.x*sinTh),_bl=new fabric.Point(_tl.x-wh.y*sinTh,_tl.y+wh.y*cosTh),_mt=new fabric.Point(_tl.x+wh.x/2*cosTh,_tl.y+wh.x/2*sinTh),tl=f(_tl),tr=f(_tr),br=f(new fabric.Point(_tr.x-wh.y*sinTh,_tr.y+wh.y*cosTh)),bl=f(_bl),ml=f(new fabric.Point(_tl.x-wh.y/2*sinTh,_tl.y+wh.y/2*cosTh)),mt=f(_mt),mr=f(new fabric.Point(_tr.x-wh.y/2*sinTh,_tr.y+wh.y/2*cosTh)),mb=f(new fabric.Point(_bl.x+wh.x/2*cosTh,_bl.y+wh.x/2*sinTh)),mtr=f(new fabric.Point(_mt.x,_mt.y));var padX=Math.cos(_angle+theta)*this.padding*Math.sqrt(2),padY=Math.sin(_angle+theta)*this.padding*Math.sqrt(2);tl=tl.add(new fabric.Point(-padX,-padY));tr=tr.add(new fabric.Point(padY,-padX));br=br.add(new fabric.Point(padX,padY));bl=bl.add(new fabric.Point(-padY,padX));ml=ml.add(new fabric.Point((-padX-padY)/2,(-padY+padX)/2));mt=mt.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));mr=mr.add(new fabric.Point((padY+padX)/2,(padY-padX)/2));mb=mb.add(new fabric.Point((padX-padY)/2,(padX+padY)/2));mtr=mtr.add(new fabric.Point((padY-padX)/2,-(padY+padX)/2));this.oCoords={tl:tl,tr:tr,br:br,bl:bl,ml:ml,mt:mt,mr:mr,mb:mb,mtr:mtr};this._setCornerCoords&&this._setCornerCoords();return this}})})();fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){if(this.group){fabric.StaticCanvas.prototype.sendToBack.call(this.group,this)}else{this.canvas.sendToBack(this)}return this},bringToFront:function(){if(this.group){fabric.StaticCanvas.prototype.bringToFront.call(this.group,this)}else{this.canvas.bringToFront(this)}return this},sendBackwards:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,intersecting)}else{this.canvas.sendBackwards(this,intersecting)}return this},bringForward:function(intersecting){if(this.group){fabric.StaticCanvas.prototype.bringForward.call(this.group,this,intersecting)}else{this.canvas.bringForward(this,intersecting)}return this},moveTo:function(index){if(this.group){fabric.StaticCanvas.prototype.moveTo.call(this.group,this,index)}else{this.canvas.moveTo(this,index)}return this}});fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var fill=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",stroke=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",strokeWidth=this.strokeWidth?this.strokeWidth:"0",strokeDashArray=this.strokeDashArray?this.strokeDashArray.join(" "):"",strokeLineCap=this.strokeLineCap?this.strokeLineCap:"butt",strokeLineJoin=this.strokeLineJoin?this.strokeLineJoin:"miter",strokeMiterLimit=this.strokeMiterLimit?this.strokeMiterLimit:"4",opacity=typeof this.opacity!=="undefined"?this.opacity:"1",visibility=this.visible?"":" visibility: hidden;",filter=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",stroke,"; ","stroke-width: ",strokeWidth,"; ","stroke-dasharray: ",strokeDashArray,"; ","stroke-linecap: ",strokeLineCap,"; ","stroke-linejoin: ",strokeLineJoin,"; ","stroke-miterlimit: ",strokeMiterLimit,"; ","fill: ",fill,"; ","opacity: ",opacity,";",filter,visibility].join("")},getSvgTransform:function(){var toFixed=fabric.util.toFixed,angle=this.getAngle(),center=this.getCenterPoint(),NUM_FRACTION_DIGITS=fabric.Object.NUM_FRACTION_DIGITS,translatePart="translate("+toFixed(center.x,NUM_FRACTION_DIGITS)+" "+toFixed(center.y,NUM_FRACTION_DIGITS)+")",anglePart=angle!==0?" rotate("+toFixed(angle,NUM_FRACTION_DIGITS)+")":"",scalePart=this.scaleX===1&&this.scaleY===1?"":" scale("+toFixed(this.scaleX,NUM_FRACTION_DIGITS)+" "+toFixed(this.scaleY,NUM_FRACTION_DIGITS)+")",flipXPart=this.flipX?"matrix(-1 0 0 1 0 0) ":"",flipYPart=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[translatePart,anglePart,scalePart,flipXPart,flipYPart].join("")},_createBaseSVGMarkup:function(){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))}if(this.shadow){markup.push(this.shadow.toSVG(this))}return markup}});fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(prop){return this.get(prop)!==this.originalState[prop]},this)},saveState:function(options){this.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this);if(options&&options.stateProperties){options.stateProperties.forEach(function(prop){this.originalState[prop]=this.get(prop)},this)}return this},setupState:function(){this.originalState={};this.saveState();return this}});(function(){var degreesToRadians=fabric.util.degreesToRadians,isVML=typeof G_vmlCanvasManager!=="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(pointer){if(!this.hasControls||!this.active)return false;var ex=pointer.x,ey=pointer.y,xPoints,lines;for(var i in this.oCoords){if(!this.isControlVisible(i)){continue}if(i==="mtr"&&!this.hasRotatingPoint){continue}if(this.get("lockUniScaling")&&(i==="mt"||i==="mr"||i==="mb"||i==="ml")){continue}lines=this._getImageLines(this.oCoords[i].corner);xPoints=this._findCrossPoints({x:ex,y:ey},lines);if(xPoints!==0&&xPoints%2===1){this.__corner=i;return i}}return false},_setCornerCoords:function(){var coords=this.oCoords,theta=degreesToRadians(this.angle),newTheta=degreesToRadians(45-this.angle),cornerHypotenuse=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,cosHalfOffset=cornerHypotenuse*Math.cos(newTheta),sinHalfOffset=cornerHypotenuse*Math.sin(newTheta),sinTh=Math.sin(theta),cosTh=Math.cos(theta);coords.tl.corner={tl:{x:coords.tl.x-sinHalfOffset,y:coords.tl.y-cosHalfOffset},tr:{x:coords.tl.x+cosHalfOffset,y:coords.tl.y-sinHalfOffset},bl:{x:coords.tl.x-cosHalfOffset,y:coords.tl.y+sinHalfOffset},br:{x:coords.tl.x+sinHalfOffset,y:coords.tl.y+cosHalfOffset}};coords.tr.corner={tl:{x:coords.tr.x-sinHalfOffset,y:coords.tr.y-cosHalfOffset},tr:{x:coords.tr.x+cosHalfOffset,y:coords.tr.y-sinHalfOffset},br:{x:coords.tr.x+sinHalfOffset,y:coords.tr.y+cosHalfOffset},bl:{x:coords.tr.x-cosHalfOffset,y:coords.tr.y+sinHalfOffset}};coords.bl.corner={tl:{x:coords.bl.x-sinHalfOffset,y:coords.bl.y-cosHalfOffset},bl:{x:coords.bl.x-cosHalfOffset,y:coords.bl.y+sinHalfOffset},br:{x:coords.bl.x+sinHalfOffset,y:coords.bl.y+cosHalfOffset},tr:{x:coords.bl.x+cosHalfOffset,y:coords.bl.y-sinHalfOffset}};coords.br.corner={tr:{x:coords.br.x+cosHalfOffset,y:coords.br.y-sinHalfOffset},bl:{x:coords.br.x-cosHalfOffset,y:coords.br.y+sinHalfOffset},br:{x:coords.br.x+sinHalfOffset,y:coords.br.y+cosHalfOffset},tl:{x:coords.br.x-sinHalfOffset,y:coords.br.y-cosHalfOffset}};coords.ml.corner={tl:{x:coords.ml.x-sinHalfOffset,y:coords.ml.y-cosHalfOffset},tr:{x:coords.ml.x+cosHalfOffset,y:coords.ml.y-sinHalfOffset},bl:{x:coords.ml.x-cosHalfOffset,y:coords.ml.y+sinHalfOffset},br:{x:coords.ml.x+sinHalfOffset,y:coords.ml.y+cosHalfOffset}};coords.mt.corner={tl:{x:coords.mt.x-sinHalfOffset,y:coords.mt.y-cosHalfOffset},tr:{x:coords.mt.x+cosHalfOffset,y:coords.mt.y-sinHalfOffset},bl:{x:coords.mt.x-cosHalfOffset,y:coords.mt.y+sinHalfOffset},br:{x:coords.mt.x+sinHalfOffset,y:coords.mt.y+cosHalfOffset}};coords.mr.corner={tl:{x:coords.mr.x-sinHalfOffset,y:coords.mr.y-cosHalfOffset},tr:{x:coords.mr.x+cosHalfOffset,y:coords.mr.y-sinHalfOffset},bl:{x:coords.mr.x-cosHalfOffset,y:coords.mr.y+sinHalfOffset},br:{x:coords.mr.x+sinHalfOffset,y:coords.mr.y+cosHalfOffset}};coords.mb.corner={tl:{x:coords.mb.x-sinHalfOffset,y:coords.mb.y-cosHalfOffset},tr:{x:coords.mb.x+cosHalfOffset,y:coords.mb.y-sinHalfOffset},bl:{x:coords.mb.x-cosHalfOffset,y:coords.mb.y+sinHalfOffset},br:{x:coords.mb.x+sinHalfOffset,y:coords.mb.y+cosHalfOffset}};coords.mtr.corner={tl:{x:coords.mtr.x-sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-cosHalfOffset-cosTh*this.rotatingPointOffset},tr:{x:coords.mtr.x+cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y-sinHalfOffset-cosTh*this.rotatingPointOffset},bl:{x:coords.mtr.x-cosHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+sinHalfOffset-cosTh*this.rotatingPointOffset},br:{x:coords.mtr.x+sinHalfOffset+sinTh*this.rotatingPointOffset,y:coords.mtr.y+cosHalfOffset-cosTh*this.rotatingPointOffset}}},drawBorders:function(ctx){if(!this.hasBorders)return this;var padding=this.padding,padding2=padding*2,strokeWidth=~~(this.strokeWidth/2)*2; ctx.save();ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=this.borderColor;var scaleX=1/this._constrainScale(this.scaleX),scaleY=1/this._constrainScale(this.scaleY);ctx.lineWidth=1/this.borderScaleFactor;var vpt=this.getViewportTransform(),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),vpt,true),sxy=fabric.util.transformPoint(new fabric.Point(scaleX,scaleY),vpt,true),w=wh.x,h=wh.y,sx=sxy.x,sy=sxy.y;if(this.group){w=w*this.group.scaleX;h=h*this.group.scaleY}ctx.strokeRect(~~(-(w/2)-padding-strokeWidth/2*sx)-.5,~~(-(h/2)-padding-strokeWidth/2*sy)-.5,~~(w+padding2+strokeWidth*sx)+1,~~(h+padding2+strokeWidth*sy)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var rotateHeight=(this.flipY?h+strokeWidth*sx+padding*2:-h-strokeWidth*sy-padding*2)/2;ctx.beginPath();ctx.moveTo(0,rotateHeight);ctx.lineTo(0,rotateHeight+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset));ctx.closePath();ctx.stroke()}ctx.restore();return this},drawControls:function(ctx){if(!this.hasControls)return this;var size=this.cornerSize,size2=size/2,strokeWidth2=~~(this.strokeWidth/2),wh=fabric.util.transformPoint(new fabric.Point(this.getWidth(),this.getHeight()),this.getViewportTransform(),true),width=wh.x,height=wh.y,left=-(width/2),top=-(height/2),padding=this.padding,scaleOffset=size2,scaleOffsetSize=size2-size,methodName=this.transparentCorners?"strokeRect":"fillRect";ctx.save();ctx.lineWidth=1;ctx.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1;ctx.strokeStyle=ctx.fillStyle=this.cornerColor;this._drawControl("tl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("tr",ctx,methodName,left+width-scaleOffset+strokeWidth2+padding,top-scaleOffset-strokeWidth2-padding);this._drawControl("bl",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("br",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height+scaleOffsetSize+strokeWidth2+padding);if(!this.get("lockUniScaling")){this._drawControl("mt",ctx,methodName,left+width/2-scaleOffset,top-scaleOffset-strokeWidth2-padding);this._drawControl("mb",ctx,methodName,left+width/2-scaleOffset,top+height+scaleOffsetSize+strokeWidth2+padding);this._drawControl("mr",ctx,methodName,left+width+scaleOffsetSize+strokeWidth2+padding,top+height/2-scaleOffset);this._drawControl("ml",ctx,methodName,left-scaleOffset-strokeWidth2-padding,top+height/2-scaleOffset)}if(this.hasRotatingPoint){this._drawControl("mtr",ctx,methodName,left+width/2-scaleOffset,this.flipY?top+height+this.rotatingPointOffset-this.cornerSize/2+strokeWidth2+padding:top-this.rotatingPointOffset-this.cornerSize/2-strokeWidth2-padding)}ctx.restore();return this},_drawControl:function(control,ctx,methodName,left,top){var size=this.cornerSize;if(this.isControlVisible(control)){isVML||this.transparentCorners||ctx.clearRect(left,top,size,size);ctx[methodName](left,top,size,size)}},isControlVisible:function(controlName){return this._getControlsVisibility()[controlName]},setControlVisible:function(controlName,visible){this._getControlsVisibility()[controlName]=visible;return this},setControlsVisibility:function(options){options||(options={});for(var p in options){this.setControlVisible(p,options[p])}return this},_getControlsVisibility:function(){if(!this._controlsVisibility){this._controlsVisibility={tl:true,tr:true,br:true,bl:true,ml:true,mt:true,mr:true,mb:true,mtr:true}}return this._controlsVisibility}})})();fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(value){object.set("left",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxCenterObjectV:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(value){object.set("top",value);_this.renderAll();onChange()},onComplete:function(){object.setCoords();onComplete()}});return this},fxRemove:function(object,callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:object.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){object.set("active",false)},onChange:function(value){object.set("opacity",value);_this.renderAll();onChange()},onComplete:function(){_this.remove(object);onComplete()}});return this}});fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]==="object"){var propsToAnimate=[],prop,skipCallbacks;for(prop in arguments[0]){propsToAnimate.push(prop)}for(var i=0,len=propsToAnimate.length;i');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Line.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" "));fabric.Line.fromElement=function(element,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Line.ATTRIBUTE_NAMES),points=[parsedAttributes.x1||0,parsedAttributes.y1||0,parsedAttributes.x2||0,parsedAttributes.y2||0];return new fabric.Line(points,extend(parsedAttributes,options))};fabric.Line.fromObject=function(object){var points=[object.x1,object.y1,object.x2,object.y2];return new fabric.Line(points,object)};function makeEdgeToOriginGetter(propertyNames,originValues){var origin=propertyNames.origin,axis1=propertyNames.axis1,axis2=propertyNames.axis2,dimension=propertyNames.dimension,nearest=originValues.nearest,center=originValues.center,farthest=originValues.farthest;return function(){switch(this.get(origin)){case nearest:return Math.min(this.get(axis1),this.get(axis2));case center:return Math.min(this.get(axis1),this.get(axis2))+.5*this.get(dimension);case farthest:return Math.max(this.get(axis1),this.get(axis2))}}}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Circle){fabric.warn("fabric.Circle is already defined.");return}fabric.Circle=fabric.util.createClass(fabric.Object,{type:"circle",radius:0,initialize:function(options){options=options||{};this.set("radius",options.radius||0);this.callSuper("initialize",options)},_set:function(key,value){this.callSuper("_set",key,value);if(key==="radius"){this.setRadius(value)}return this},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{radius:this.get("radius")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx,noTransform){ctx.beginPath();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.stroke&&this._renderStroke(ctx)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(value){this.radius=value;this.set("width",value*2).set("height",value*2)},complexity:function(){return 1}});fabric.Circle.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" "));fabric.Circle.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Circle.ATTRIBUTE_NAMES);if(!isValidRadius(parsedAttributes)){throw new Error("value of `r` attribute is required and can not be negative")}if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var obj=new fabric.Circle(extend(parsedAttributes,options));obj.cx=parseFloat(element.getAttribute("cx"))||0;obj.cy=parseFloat(element.getAttribute("cy"))||0;return obj};function isValidRadius(attributes){return"radius"in attributes&&attributes.radius>0}fabric.Circle.fromObject=function(object){return new fabric.Circle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={});if(fabric.Triangle){fabric.warn("fabric.Triangle is already defined");return}fabric.Triangle=fabric.util.createClass(fabric.Object,{type:"triangle",initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("width",options.width||100).set("height",options.height||100)},_render:function(ctx){var widthBy2=this.width/2,heightBy2=this.height/2;ctx.beginPath();ctx.moveTo(-widthBy2,heightBy2);ctx.lineTo(0,-heightBy2);ctx.lineTo(widthBy2,heightBy2);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_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()},toSVG:function(reviver){var markup=this._createBaseSVGMarkup(),widthBy2=this.width/2,heightBy2=this.height/2,points=[-widthBy2+" "+heightBy2,"0 "+-heightBy2,widthBy2+" "+heightBy2].join(",");markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Triangle.fromObject=function(object){return new fabric.Triangle(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),piBy2=Math.PI*2,extend=fabric.util.object.extend;if(fabric.Ellipse){fabric.warn("fabric.Ellipse is already defined.");return}fabric.Ellipse=fabric.util.createClass(fabric.Object,{type:"ellipse",rx:0,ry:0,initialize:function(options){options=options||{};this.callSuper("initialize",options);this.set("rx",options.rx||0);this.set("ry",options.ry||0);this.set("width",this.get("rx")*2);this.set("height",this.get("ry")*2)},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},render:function(ctx,noTransform){if(this.rx===0||this.ry===0)return;return this.callSuper("render",ctx,noTransform)},_render:function(ctx,noTransform){ctx.beginPath();ctx.save();ctx.globalAlpha=this.group?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&this.group){ctx.translate(this.cx,this.cy)}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);ctx.restore();this._renderFill(ctx);this._renderStroke(ctx)},complexity:function(){return 1}});fabric.Ellipse.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" "));fabric.Ellipse.fromElement=function(element,options){options||(options={});var parsedAttributes=fabric.parseAttributes(element,fabric.Ellipse.ATTRIBUTE_NAMES),cx=parsedAttributes.left,cy=parsedAttributes.top;if("left"in parsedAttributes){parsedAttributes.left-=options.width/2||0}if("top"in parsedAttributes){parsedAttributes.top-=options.height/2||0}var ellipse=new fabric.Ellipse(extend(parsedAttributes,options));ellipse.cx=cx||0;ellipse.cy=cy||0;return ellipse};fabric.Ellipse.fromObject=function(object){return new fabric.Ellipse(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;if(fabric.Rect){console.warn("fabric.Rect is already defined");return}var stateProperties=fabric.Object.prototype.stateProperties.concat();stateProperties.push("rx","ry","x","y");fabric.Rect=fabric.util.createClass(fabric.Object,{stateProperties:stateProperties,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(options){options=options||{};this.callSuper("initialize",options);this._initRxRy();this.x=options.x||0;this.y=options.y||0},_initRxRy:function(){if(this.rx&&!this.ry){this.ry=this.rx}else if(this.ry&&!this.rx){this.rx=this.ry}},_render:function(ctx){if(this.width===1&&this.height===1){ctx.fillRect(0,0,1,1);return}var rx=this.rx?Math.min(this.rx,this.width/2):0,ry=this.ry?Math.min(this.ry,this.height/2):0,w=this.width,h=this.height,x=-w/2,y=-h/2,isInPathGroup=this.group&&this.group.type==="path-group",isRounded=rx!==0||ry!==0,k=1-.5522847498;ctx.beginPath();ctx.globalAlpha=isInPathGroup?ctx.globalAlpha*this.opacity:this.opacity;if(this.transformMatrix&&isInPathGroup){ctx.translate(this.width/2+this.x,this.height/2+this.y)}if(!this.transformMatrix&&isInPathGroup){ctx.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y)}ctx.moveTo(x+rx,y);ctx.lineTo(x+w-rx,y);isRounded&&ctx.bezierCurveTo(x+w-k*rx,y,x+w,y+k*ry,x+w,y+ry);ctx.lineTo(x+w,y+h-ry);isRounded&&ctx.bezierCurveTo(x+w,y+h-k*ry,x+w-k*rx,y+h,x+w-rx,y+h);ctx.lineTo(x+rx,y+h);isRounded&&ctx.bezierCurveTo(x+k*rx,y+h,x,y+h-k*ry,x,y+h-ry);ctx.lineTo(x,y+ry);isRounded&&ctx.bezierCurveTo(x,y+k*ry,x+k*rx,y,x+rx,y);ctx.closePath();this._renderFill(ctx);this._renderStroke(ctx)},_renderDashedStroke:function(ctx){var x=-this.width/2,y=-this.height/2,w=this.width,h=this.height;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()},_normalizeLeftTopProperties:function(parsedAttributes){if("left"in parsedAttributes){this.set("left",parsedAttributes.left+this.getWidth()/2)}this.set("x",parsedAttributes.left||0);if("top"in parsedAttributes){this.set("top",parsedAttributes.top+this.getHeight()/2)}this.set("y",parsedAttributes.top||0);return this},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=this._createBaseSVGMarkup();markup.push("');return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return 1}});fabric.Rect.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" "));function _setDefaultLeftTopValues(attributes){attributes.left=attributes.left||0;attributes.top=attributes.top||0;return attributes}fabric.Rect.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Rect.ATTRIBUTE_NAMES);parsedAttributes=_setDefaultLeftTopValues(parsedAttributes);var rect=new fabric.Rect(extend(options?fabric.util.object.clone(options):{},parsedAttributes));rect._normalizeLeftTopProperties(parsedAttributes);return rect};fabric.Rect.fromObject=function(object){return new fabric.Rect(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),toFixed=fabric.util.toFixed;if(fabric.Polyline){fabric.warn("fabric.Polyline is already defined");return}fabric.Polyline=fabric.util.createClass(fabric.Object,{type:"polyline",points:null,initialize:function(points,options,skipOffset){options=options||{};this.set("points",points);this.callSuper("initialize",options);this._calcDimensions(skipOffset)},_calcDimensions:function(skipOffset){return fabric.Polygon.prototype._calcDimensions.call(this,skipOffset)},toObject:function(propertiesToInclude){return fabric.Polygon.prototype.toObject.call(this,propertiesToInclude)},toSVG:function(reviver){var points=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i');return reviver?reviver(markup.join("")):markup.join("")},_render:function(ctx){var point;ctx.beginPath();ctx.moveTo(this.points[0].x,this.points[0].y);for(var i=0,len=this.points.length;i"},toObject:function(propertiesToInclude){var o=extend(this.callSuper("toObject",propertiesToInclude),{path:this.path.map(function(item){return item.slice()}),pathOffset:this.pathOffset});if(this.sourcePath){o.sourcePath=this.sourcePath}if(this.transformMatrix){o.transformMatrix=this.transformMatrix}return o},toDatalessObject:function(propertiesToInclude){var o=this.toObject(propertiesToInclude);if(this.sourcePath){o.path=this.sourcePath}delete o.sourcePath;return o},toSVG:function(reviver){var chunks=[],markup=this._createBaseSVGMarkup();for(var i=0,len=this.path.length;i',"","");return reviver?reviver(markup.join("")):markup.join("")},complexity:function(){return this.path.length},_parsePath:function(){var result=[],coords=[],currentPath,parsed,re=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,match,coordsStr;for(var i=0,coordsParsed,len=this.path.length;icommandLength){for(var k=1,klen=coordsParsed.length;k"];for(var i=0,len=objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},toString:function(){return"#"},isSameColor:function(){var firstPathFill=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(path){return(path.get("fill")||"").toLowerCase()===firstPathFill})},complexity:function(){return this.paths.reduce(function(total,path){return total+(path&&path.complexity?path.complexity():0)},0)},getObjects:function(){return this.paths}});fabric.PathGroup.fromObject=function(object,callback){if(typeof object.paths==="string"){fabric.loadSVGFromURL(object.paths,function(elements){var pathUrl=object.paths;delete object.paths;var pathGroup=fabric.util.groupSVGElements(elements,object,pathUrl);callback(pathGroup)})}else{fabric.util.enlivenObjects(object.paths,function(enlivenedObjects){delete object.paths;callback(new fabric.PathGroup(enlivenedObjects,object))})}};fabric.PathGroup.async=true})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,min=fabric.util.array.min,max=fabric.util.array.max,invoke=fabric.util.array.invoke,degreesToRadians=fabric.util.degreesToRadians;if(fabric.Group){return}var _lockProperties={lockMovementX:true,lockMovementY:true,lockRotation:true,lockScalingX:true,lockScalingY:true,lockUniScaling:true};fabric.Group=fabric.util.createClass(fabric.Object,fabric.Collection,{type:"group",initialize:function(objects,options){options=options||{};this._objects=objects||[];for(var i=this._objects.length;i--;){this._objects[i].group=this}this.originalState={};this.callSuper("initialize");if(options){extend(this,options)}this._setOpacityIfSame();this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(object){var objectLeft=object.getLeft(),objectTop=object.getTop();object.set({originalLeft:objectLeft,originalTop:objectTop,left:objectLeft-this.left,top:objectTop-this.top});object.setCoords();object.__origHasControls=object.hasControls;object.hasControls=false},toString:function(){return"#"},addWithUpdate:function(object){this._restoreObjectsState();this._objects.push(object);object.group=this;this.forEachObject(this._setObjectActive,this);this._calcBounds();this._updateObjectsCoords();return this},_setObjectActive:function(object){object.set("active",true);object.group=this},removeWithUpdate:function(object){this._moveFlippedObject(object);this._restoreObjectsState();this.forEachObject(this._setObjectActive,this);this.remove(object);this._calcBounds();this._updateObjectsCoords();return this},_onObjectAdded:function(object){object.group=this},_onObjectRemoved:function(object){delete object.group;object.set("active",false)},delegatedProperties:{fill:true,opacity:true,fontFamily:true,fontWeight:true,fontSize:true,fontStyle:true,lineHeight:true,textDecoration:true,textAlign:true,backgroundColor:true},_set:function(key,value){if(key in this.delegatedProperties){var i=this._objects.length;this[key]=value;while(i--){this._objects[i].set(key,value)}}else{this[key]=value}},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{objects:invoke(this._objects,"toObject",propertiesToInclude)})},render:function(ctx,noTransform){if(!this.visible)return;ctx.save();this.clipTo&&fabric.util.clipContext(this,ctx);for(var i=0,len=this._objects.length;i'];for(var i=0,len=this._objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},get:function(prop){if(prop in _lockProperties){if(this[prop]){return this[prop]}else{for(var i=0,len=this._objects.length;i','");if(this.stroke||this.strokeDashArray){var origFill=this.fill;this.fill=null;markup.push("');this.fill=origFill}markup.push("");return reviver?reviver(markup.join("")):markup.join("")},getSrc:function(){if(this.getElement()){return this.getElement().src||this.getElement()._src}},toString:function(){return'#'},clone:function(callback,propertiesToInclude){this.constructor.fromObject(this.toObject(propertiesToInclude),callback)},applyFilters:function(callback){if(!this._originalElement){return}if(this.filters.length===0){this._element=this._originalElement;callback&&callback();return}var imgEl=this._originalElement,canvasEl=fabric.util.createCanvasElement(),replacement=fabric.util.createImage(),_this=this;canvasEl.width=imgEl.width;canvasEl.height=imgEl.height;canvasEl.getContext("2d").drawImage(imgEl,0,0,imgEl.width,imgEl.height);this.filters.forEach(function(filter){filter&&filter.applyTo(canvasEl)});replacement.width=imgEl.width;replacement.height=imgEl.height;if(fabric.isLikelyNode){replacement.src=canvasEl.toBuffer(undefined,fabric.Image.pngCompression);_this._element=replacement;callback&&callback()}else{replacement.onload=function(){_this._element=replacement;callback&&callback();replacement.onload=canvasEl=imgEl=null};replacement.src=canvasEl.toDataURL("image/png")}return this},_render:function(ctx){this._element&&ctx.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var element=this.getElement();this.set("width",element.width);this.set("height",element.height)},_initElement:function(element){this.setElement(fabric.util.getById(element));fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(options){options||(options={});this.setOptions(options);this._setWidthHeight(options);if(this._element&&this.crossOrigin){this._element.crossOrigin=this.crossOrigin}},_initFilters:function(object,callback){if(object.filters&&object.filters.length){fabric.util.enlivenObjects(object.filters,function(enlivenedObjects){callback&&callback(enlivenedObjects)},"fabric.Image.filters")}else{callback&&callback()}},_setWidthHeight:function(options){this.width="width"in options?options.width:this.getElement()?this.getElement().width||0:0;this.height="height"in options?options.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}});fabric.Image.CSS_CANVAS="canvas-img";fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc;fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){fabric.Image.prototype._initFilters.call(object,object,function(filters){object.filters=filters||[];var instance=new fabric.Image(img,object);callback&&callback(instance)})},null,object.crossOrigin)};fabric.Image.fromURL=function(url,callback,imgOptions){fabric.util.loadImage(url,function(img){callback(new fabric.Image(img,imgOptions))},null,imgOptions&&imgOptions.crossOrigin)};fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" "));fabric.Image.fromElement=function(element,callback,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(parsedAttributes["xlink:href"],callback,extend(options?fabric.util.object.clone(options):{},parsedAttributes))};fabric.Image.async=true;fabric.Image.pngCompression=1})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var angle=this.getAngle()%360;if(angle>0){return Math.round((angle-1)/90)*90}return Math.round(angle/90)*90},straighten:function(){this.setAngle(this._getAngleValueForStraighten());return this},fxStraighten:function(callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(value){_this.setAngle(value);onChange()},onComplete:function(){_this.setCoords();onComplete()},onStart:function(){_this.set("active",false)}});return this}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(object){object.straighten();this.renderAll();return this},fxStraightenObject:function(object){object.fxStraighten({onChange:this.renderAll.bind(this)});return this}});fabric.Image.filters=fabric.Image.filters||{};fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.Brightness=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"Brightness",initialize:function(options){options=options||{};this.brightness=options.brightness||0},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,brightness=this.brightness;for(var i=0,len=data.length;ish||scx<0||scx>sw)continue;var srcOff=(scy*sw+scx)*4,wt=weights[cy*side+cx];r+=src[srcOff]*wt;g+=src[srcOff+1]*wt;b+=src[srcOff+2]*wt;a+=src[srcOff+3]*wt}}dst[dstOff]=r;dst[dstOff+1]=g;dst[dstOff+2]=b;dst[dstOff+3]=a+alphaFac*(255-a)}}context.putImageData(output,0,0)},toObject:function(){return extend(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}});fabric.Image.filters.Convolute.fromObject=function(object){return new fabric.Image.filters.Convolute(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.GradientTransparency=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(options){options=options||{};this.threshold=options.threshold||100},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,threshold=this.threshold,total=data.length;for(var i=0,len=data.length;i-1?options.channel:0},applyTo:function(canvasEl){if(!this.mask)return;var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,maskEl=this.mask.getElement(),maskCanvasEl=fabric.util.createCanvasElement(),channel=this.channel,i,iLen=imageData.width*imageData.height*4;maskCanvasEl.width=maskEl.width;maskCanvasEl.height=maskEl.height;maskCanvasEl.getContext("2d").drawImage(maskEl,0,0,maskEl.width,maskEl.height);var maskImageData=maskCanvasEl.getContext("2d").getImageData(0,0,maskEl.width,maskEl.height),maskData=maskImageData.data;for(i=0;ilimit&&g>limit&&b>limit&&abs(r-g)"];for(var i=0,len=objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},toString:function(){return"#"},isSameColor:function(){var firstPathFill=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(path){return(path.get("fill")||"").toLowerCase()===firstPathFill})},complexity:function(){return this.paths.reduce(function(total,path){return total+(path&&path.complexity?path.complexity():0)},0)},getObjects:function(){return this.paths}});fabric.PathGroup.fromObject=function(object,callback){if(typeof object.paths==="string"){fabric.loadSVGFromURL(object.paths,function(elements){var pathUrl=object.paths;delete object.paths;var pathGroup=fabric.util.groupSVGElements(elements,object,pathUrl);callback(pathGroup)})}else{fabric.util.enlivenObjects(object.paths,function(enlivenedObjects){delete object.paths;callback(new fabric.PathGroup(enlivenedObjects,object))})}};fabric.PathGroup.async=true})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend,min=fabric.util.array.min,max=fabric.util.array.max,invoke=fabric.util.array.invoke,degreesToRadians=fabric.util.degreesToRadians;if(fabric.Group){return}var _lockProperties={lockMovementX:true,lockMovementY:true,lockRotation:true,lockScalingX:true,lockScalingY:true,lockUniScaling:true};fabric.Group=fabric.util.createClass(fabric.Object,fabric.Collection,{type:"group",initialize:function(objects,options){options=options||{};this._objects=objects||[];for(var i=this._objects.length;i--;){this._objects[i].group=this}this.originalState={};this.callSuper("initialize");if(options){extend(this,options)}this._setOpacityIfSame();this._calcBounds();this._updateObjectsCoords();this.setCoords();this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(object){var objectLeft=object.getLeft(),objectTop=object.getTop();object.set({originalLeft:objectLeft,originalTop:objectTop,left:objectLeft-this.left,top:objectTop-this.top});object.setCoords();object.__origHasControls=object.hasControls;object.hasControls=false},toString:function(){return"#"},addWithUpdate:function(object){this._restoreObjectsState();this._objects.push(object);object.group=this;this.forEachObject(this._setObjectActive,this);this._calcBounds();this._updateObjectsCoords();return this},_setObjectActive:function(object){object.set("active",true);object.group=this},removeWithUpdate:function(object){this._moveFlippedObject(object);this._restoreObjectsState();this.forEachObject(this._setObjectActive,this);this.remove(object);this._calcBounds();this._updateObjectsCoords();return this},_onObjectAdded:function(object){object.group=this},_onObjectRemoved:function(object){delete object.group;object.set("active",false)},delegatedProperties:{fill:true,opacity:true,fontFamily:true,fontWeight:true,fontSize:true,fontStyle:true,lineHeight:true,textDecoration:true,textAlign:true,backgroundColor:true},_set:function(key,value){if(key in this.delegatedProperties){var i=this._objects.length;this[key]=value;while(i--){this._objects[i].set(key,value)}}else{this[key]=value}},toObject:function(propertiesToInclude){return extend(this.callSuper("toObject",propertiesToInclude),{objects:invoke(this._objects,"toObject",propertiesToInclude)})},render:function(ctx,noTransform){if(!this.visible)return;ctx.save();this.clipTo&&fabric.util.clipContext(this,ctx);for(var i=0,len=this._objects.length;i'];for(var i=0,len=this._objects.length;i");return reviver?reviver(markup.join("")):markup.join("")},get:function(prop){if(prop in _lockProperties){if(this[prop]){return this[prop]}else{for(var i=0,len=this._objects.length;i','");if(this.stroke||this.strokeDashArray){var origFill=this.fill;this.fill=null;markup.push("');this.fill=origFill}markup.push("");return reviver?reviver(markup.join("")):markup.join("")},getSrc:function(){if(this.getElement()){return this.getElement().src||this.getElement()._src}},toString:function(){return'#'},clone:function(callback,propertiesToInclude){this.constructor.fromObject(this.toObject(propertiesToInclude),callback)},applyFilters:function(callback){if(!this._originalElement){return}if(this.filters.length===0){this._element=this._originalElement;callback&&callback();return}var imgEl=this._originalElement,canvasEl=fabric.util.createCanvasElement(),replacement=fabric.util.createImage(),_this=this;canvasEl.width=imgEl.width;canvasEl.height=imgEl.height;canvasEl.getContext("2d").drawImage(imgEl,0,0,imgEl.width,imgEl.height);this.filters.forEach(function(filter){filter&&filter.applyTo(canvasEl)});replacement.width=imgEl.width;replacement.height=imgEl.height;if(fabric.isLikelyNode){replacement.src=canvasEl.toBuffer(undefined,fabric.Image.pngCompression);_this._element=replacement;callback&&callback()}else{replacement.onload=function(){_this._element=replacement;callback&&callback();replacement.onload=canvasEl=imgEl=null};replacement.src=canvasEl.toDataURL("image/png")}return this},_render:function(ctx){this._element&&ctx.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var element=this.getElement();this.set("width",element.width);this.set("height",element.height)},_initElement:function(element){this.setElement(fabric.util.getById(element));fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(options){options||(options={});this.setOptions(options);this._setWidthHeight(options);if(this._element&&this.crossOrigin){this._element.crossOrigin=this.crossOrigin}},_initFilters:function(object,callback){if(object.filters&&object.filters.length){fabric.util.enlivenObjects(object.filters,function(enlivenedObjects){callback&&callback(enlivenedObjects)},"fabric.Image.filters")}else{callback&&callback()}},_setWidthHeight:function(options){this.width="width"in options?options.width:this.getElement()?this.getElement().width||0:0;this.height="height"in options?options.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}});fabric.Image.CSS_CANVAS="canvas-img";fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc;fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){fabric.Image.prototype._initFilters.call(object,object,function(filters){object.filters=filters||[];var instance=new fabric.Image(img,object);callback&&callback(instance)})},null,object.crossOrigin)};fabric.Image.fromURL=function(url,callback,imgOptions){fabric.util.loadImage(url,function(img){callback(new fabric.Image(img,imgOptions))},null,imgOptions&&imgOptions.crossOrigin)};fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" "));fabric.Image.fromElement=function(element,callback,options){var parsedAttributes=fabric.parseAttributes(element,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(parsedAttributes["xlink:href"],callback,extend(options?fabric.util.object.clone(options):{},parsedAttributes))};fabric.Image.async=true;fabric.Image.pngCompression=1})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var angle=this.getAngle()%360;if(angle>0){return Math.round((angle-1)/90)*90}return Math.round(angle/90)*90},straighten:function(){this.setAngle(this._getAngleValueForStraighten());return this},fxStraighten:function(callbacks){callbacks=callbacks||{};var empty=function(){},onComplete=callbacks.onComplete||empty,onChange=callbacks.onChange||empty,_this=this;fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(value){_this.setAngle(value);onChange()},onComplete:function(){_this.setCoords();onComplete()},onStart:function(){_this.set("active",false)}});return this}});fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(object){object.straighten();this.renderAll();return this},fxStraightenObject:function(object){object.fxStraighten({onChange:this.renderAll.bind(this)});return this}});fabric.Image.filters=fabric.Image.filters||{};fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}});(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.Brightness=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"Brightness",initialize:function(options){options=options||{};this.brightness=options.brightness||0},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,brightness=this.brightness;for(var i=0,len=data.length;ish||scx<0||scx>sw)continue;var srcOff=(scy*sw+scx)*4,wt=weights[cy*side+cx];r+=src[srcOff]*wt;g+=src[srcOff+1]*wt;b+=src[srcOff+2]*wt;a+=src[srcOff+3]*wt}}dst[dstOff]=r;dst[dstOff+1]=g;dst[dstOff+2]=b;dst[dstOff+3]=a+alphaFac*(255-a)}}context.putImageData(output,0,0)},toObject:function(){return extend(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}});fabric.Image.filters.Convolute.fromObject=function(object){return new fabric.Image.filters.Convolute(object)}})(typeof exports!=="undefined"?exports:this);(function(global){"use strict";var fabric=global.fabric||(global.fabric={}),extend=fabric.util.object.extend;fabric.Image.filters.GradientTransparency=fabric.util.createClass(fabric.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(options){options=options||{};this.threshold=options.threshold||100},applyTo:function(canvasEl){var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,threshold=this.threshold,total=data.length;for(var i=0,len=data.length;i-1?options.channel:0},applyTo:function(canvasEl){if(!this.mask)return;var context=canvasEl.getContext("2d"),imageData=context.getImageData(0,0,canvasEl.width,canvasEl.height),data=imageData.data,maskEl=this.mask.getElement(),maskCanvasEl=fabric.util.createCanvasElement(),channel=this.channel,i,iLen=imageData.width*imageData.height*4;maskCanvasEl.width=maskEl.width;maskCanvasEl.height=maskEl.height;maskCanvasEl.getContext("2d").drawImage(maskEl,0,0,maskEl.width,maskEl.height);var maskImageData=maskCanvasEl.getContext("2d").getImageData(0,0,maskEl.width,maskEl.height),maskData=maskImageData.data;for(i=0;ilimit&&g>limit&&b>limit&&abs(r-g)'},_render:function(ctx){var isInPathGroup=this.group&&this.group.type==="path-group";if(isInPathGroup&&!this.transformMatrix){ctx.translate(-this.group.width/2+this.left,-this.group.height/2+this.top)}else if(isInPathGroup&&this.transformMatrix){ctx.translate(-this.group.width/2,-this.group.height/2)}if(typeof Cufon==="undefined"||this.useNative===true){this._renderViaNative(ctx)}else{this._renderViaCufon(ctx)}},_renderViaNative:function(ctx){var textLines=this.text.split(this._reNewline);this.transform(ctx,fabric.isLikelyNode);this._setTextStyles(ctx);this.width=this._getTextWidth(ctx,textLines);this.height=this._getTextHeight(ctx,textLines);this.clipTo&&fabric.util.clipContext(this,ctx);this._renderTextBackground(ctx,textLines);this._translateForTextAlign(ctx);this._renderText(ctx,textLines);if(this.textAlign!=="left"&&this.textAlign!=="justify"){ctx.restore()}this._renderTextDecoration(ctx,textLines);this.clipTo&&ctx.restore();this._setBoundaries(ctx,textLines);this._totalLineHeight=0},_renderText:function(ctx,textLines){ctx.save();this._setShadow(ctx);this._renderTextFill(ctx,textLines);this._renderTextStroke(ctx,textLines);this._removeShadow(ctx);ctx.restore()},_translateForTextAlign:function(ctx){if(this.textAlign!=="left"&&this.textAlign!=="justify"){ctx.save();ctx.translate(this.textAlign==="center"?this.width/2:this.width,0)}},_setBoundaries:function(ctx,textLines){this._boundaries=[];for(var i=0,len=textLines.length;imaxWidth){maxWidth=currentLineWidth}}return maxWidth},_renderChars:function(method,ctx,chars,left,top){ctx[method](chars,left,top)},_renderTextLine:function(method,ctx,line,left,top,lineIndex){top-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(method,ctx,line,left,top,lineIndex);return}var lineWidth=ctx.measureText(line).width,totalWidth=this.width;if(totalWidth>lineWidth){var words=line.split(/\s+/),wordsWidth=ctx.measureText(line.replace(/\s+/g,"")).width,widthDiff=totalWidth-wordsWidth,numSpaces=words.length-1,spaceWidth=widthDiff/numSpaces,leftOffset=0;for(var i=0,len=words.length;i-1){renderLinesAtOffset(this.fontSize*this.lineHeight)}if(this.textDecoration.indexOf("line-through")>-1){renderLinesAtOffset(this.fontSize*this.lineHeight-this.fontSize/2)}if(this.textDecoration.indexOf("overline")>-1){renderLinesAtOffset(this.fontSize*this.lineHeight-this.fontSize)}},_getFontDeclaration:function(){return[fabric.isLikelyNode?this.fontWeight:this.fontStyle,fabric.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",fabric.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(ctx,noTransform){if(!this.visible)return;ctx.save();var m=this.transformMatrix;if(m&&(!this.group||this.group.type==="path-group")){ctx.transform(m[0],m[1],m[2],m[3],m[4],m[5])}this._render(ctx);ctx.restore()},toObject:function(propertiesToInclude){var object=extend(this.callSuper("toObject",propertiesToInclude),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});if(!this.includeDefaultValues){this._removeDefaultValues(object)}return object},toSVG:function(reviver){var markup=[],textLines=this.text.split(this._reNewline),offsets=this._getSVGLeftTopOffsets(textLines),textAndBg=this._getSVGTextAndBg(offsets.lineTop,offsets.textLeft,textLines),shadowSpans=this._getSVGShadows(offsets.lineTop,textLines);offsets.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0;this._wrapSVGTextAndBg(markup,textAndBg,shadowSpans,offsets);return reviver?reviver(markup.join("")):markup.join("")},_getSVGLeftTopOffsets:function(textLines){var lineTop=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,textLeft=-(this.width/2),textTop=this.useNative?this.fontSize-1:this.height/2-textLines.length*this.fontSize-this._totalLineHeight;return{textLeft:textLeft,textTop:textTop,lineTop:lineTop}},_wrapSVGTextAndBg:function(markup,textAndBg,shadowSpans,offsets){markup.push('',textAndBg.textBgRects.join(""),"',shadowSpans.join(""),textAndBg.textSpans.join(""),"","")},_getSVGShadows:function(lineHeight,textLines){var shadowSpans=[],i,len,lineTopOffsetMultiplier=1;if(!this.shadow||!this._boundaries){return shadowSpans}for(i=0,len=textLines.length;i",fabric.util.string.escapeXml(textLines[i]),"");lineTopOffsetMultiplier=1}else{lineTopOffsetMultiplier++}}return shadowSpans},_getSVGTextAndBg:function(lineHeight,textLeftOffset,textLines){var textSpans=[],textBgRects=[],lineTopOffsetMultiplier=1;this._setSVGBg(textBgRects);for(var i=0,len=textLines.length;i",fabric.util.string.escapeXml(textLine),"")},_setSVGTextLineBg:function(textBgRects,i,textLeftOffset,lineHeight){textBgRects.push("')},_setSVGBg:function(textBgRects){if(this.backgroundColor&&this._boundaries){textBgRects.push("')}},_getFillAttributes:function(value){var fillColor=value&&typeof value==="string"?new fabric.Color(value):"";if(!fillColor||!fillColor.getSource()||fillColor.getAlpha()===1){return'fill="'+value+'"'}return'opacity="'+fillColor.getAlpha()+'" fill="'+fillColor.setAlpha(1).toRgb()+'"'},_set:function(key,value){if(key==="fontFamily"&&this.path){this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+value+"$3")}this.callSuper("_set",key,value);if(key in this._dimensionAffectingProps){this._initDimensions();this.setCoords()}},complexity:function(){return 1}});fabric.Text.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" "));fabric.Text.DEFAULT_SVG_FONT_SIZE=16;fabric.Text.fromElement=function(element,options){if(!element){return null}var parsedAttributes=fabric.parseAttributes(element,fabric.Text.ATTRIBUTE_NAMES);options=fabric.util.object.extend(options?fabric.util.object.clone(options):{},parsedAttributes);if("dx"in parsedAttributes){options.left+=parsedAttributes.dx}if("dy"in parsedAttributes){options.top+=parsedAttributes.dy}if(!("fontSize"in options)){options.fontSize=fabric.Text.DEFAULT_SVG_FONT_SIZE}if(!options.originX){options.originX="center"}var text=new fabric.Text(element.textContent,options);text.set({left:text.getLeft()+text.getWidth()/2,top:text.getTop()-text.getHeight()/2});return text};fabric.Text.fromObject=function(object){return new fabric.Text(object.text,clone(object))};fabric.util.createAccessors(fabric.Text)})(typeof exports!=="undefined"?exports:this);fabric.util.object.extend(fabric.Text.prototype,{_renderViaCufon:function(ctx){var o=Cufon.textOptions||(Cufon.textOptions={});o.left=this.left;o.top=this.top;o.context=ctx;o.color=this.fill;var el=this._initDummyElementForCufon();this.transform(ctx);Cufon.replaceElement(el,{engine:"canvas",separate:"none",fontFamily:this.fontFamily,fontWeight:this.fontWeight,textDecoration:this.textDecoration,textShadow:this.shadow&&this.shadow.toString(),textAlign:this.textAlign,fontStyle:this.fontStyle,lineHeight:this.lineHeight,stroke:this.stroke,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor});this.width=o.width;this.height=o.height;this._totalLineHeight=o.totalLineHeight;this._fontAscent=o.fontAscent;this._boundaries=o.boundaries;el=null;this.setCoords()},_initDummyElementForCufon:function(){var el=fabric.document.createElement("pre"),container=fabric.document.createElement("div");container.appendChild(el);if(typeof G_vmlCanvasManager==="undefined"){el.innerHTML=this.text}else{el.innerText=this.text.replace(/\r?\n/gi,"\r")}el.style.fontSize=this.fontSize+"px";el.style.letterSpacing="normal";return el}});(function(){var clone=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:false,editable:true,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:true,_skipFillStrokeCheck:true,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:false,_charWidthsCache:{},initialize:function(text,options){this.styles=options?options.styles||{}:{};this.callSuper("initialize",text,options);this.initBehavior();fabric.IText.instances.push(this);this.__lineWidths={};this.__lineHeights={};this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return true;var obj=this.styles;for(var p1 in obj){for(var p2 in obj[p1]){for(var p3 in obj[p1][p2]){return false}}}return true},setSelectionStart:function(index){if(this.selectionStart!==index){this.canvas&&this.canvas.fire("text:selection:changed",{target:this})}this.selectionStart=index;this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=index)},setSelectionEnd:function(index){if(this.selectionEnd!==index){this.canvas&&this.canvas.fire("text:selection:changed",{target:this})}this.selectionEnd=index;this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=index)},getSelectionStyles:function(startIndex,endIndex){if(arguments.length===2){var styles=[];for(var i=startIndex;i=start.charIndex&&(i!==endLine||jstartLine&&i-1){this._renderCharDecorationAtOffset(ctx,left,top+this.fontSize/this._fontSizeFraction,charWidth,0,this.fontSize/20)}if(textDecoration.indexOf("line-through")>-1){this._renderCharDecorationAtOffset(ctx,left,top+this.fontSize/this._fontSizeFraction,charWidth,charHeight/2,fontSize/20)}if(textDecoration.indexOf("overline")>-1){this._renderCharDecorationAtOffset(ctx,left,top,charWidth,lineHeight-this.fontSize/this._fontSizeFraction,this.fontSize/20)}},_renderCharDecorationAtOffset:function(ctx,left,top,charWidth,offset,thickness){ctx.fillRect(left,top-offset,charWidth,thickness)},_renderTextLine:function(method,ctx,line,left,top,lineIndex){top+=this.fontSize/4;this.callSuper("_renderTextLine",method,ctx,line,left,top,lineIndex)},_renderTextDecoration:function(ctx,textLines){if(this.isEmptyStyles()){return this.callSuper("_renderTextDecoration",ctx,textLines)}},_renderTextLinesBackground:function(ctx,textLines){if(!this.textBackgroundColor&&!this.styles)return;ctx.save();if(this.textBackgroundColor){ctx.fillStyle=this.textBackgroundColor}var lineHeights=0,fractionOfFontSize=this.fontSize/this._fontSizeFraction;for(var i=0,len=textLines.length;imaxWidth){maxWidth=currentLineWidth}}return maxWidth},_getHeightOfLine:function(ctx,lineIndex,textLines){textLines=textLines||this.text.split(this._reNewline);var maxHeight=this._getHeightOfChar(ctx,textLines[lineIndex][0],lineIndex,0),line=textLines[lineIndex],chars=line.split("");for(var i=1,len=chars.length;imaxHeight){maxHeight=currentCharHeight}}return maxHeight*this.lineHeight},_getTextHeight:function(ctx,textLines){var height=0;for(var i=0,len=textLines.length;i-1){offset++;index--}return startFrom-offset},findWordBoundaryRight:function(startFrom){var offset=0,index=startFrom;if(this._reSpace.test(this.text.charAt(index))){while(this._reSpace.test(this.text.charAt(index))){offset++;index++}}while(/\S/.test(this.text.charAt(index))&&index-1){offset++;index--}return startFrom-offset},findLineBoundaryRight:function(startFrom){var offset=0,index=startFrom;while(!/\n/.test(this.text.charAt(index))&&index0&&indexprevIndex;if(isNewline){this.removeStyleObject(isNewline,i+1)}else{this.removeStyleObject(this.get2DCursorLocation(i).charIndex===0,i)}}this.text=this.text.slice(0,start)+this.text.slice(end)},insertChars:function(_chars){var isEndOfLine=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+_chars+this.text.slice(this.selectionEnd);if(this.selectionStart===this.selectionEnd){this.insertStyleObjects(_chars,isEndOfLine,this.copiedStyles)}this.selectionStart+=_chars.length;this.selectionEnd=this.selectionStart;if(this.canvas){this.canvas.renderAll().renderAll()}this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(lineIndex,charIndex,isEndOfLine){this.shiftLineStyles(lineIndex,+1);if(!this.styles[lineIndex+1]){this.styles[lineIndex+1]={}}var currentCharStyle=this.styles[lineIndex][charIndex-1],newLineStyles={};if(isEndOfLine){newLineStyles[0]=clone(currentCharStyle);this.styles[lineIndex+1]=newLineStyles}else{for(var index in this.styles[lineIndex]){if(parseInt(index,10)>=charIndex){newLineStyles[parseInt(index,10)-charIndex]=this.styles[lineIndex][index];delete this.styles[lineIndex][index]}}this.styles[lineIndex+1]=newLineStyles}},insertCharStyleObject:function(lineIndex,charIndex,style){var currentLineStyles=this.styles[lineIndex],currentLineStylesCloned=clone(currentLineStyles);if(charIndex===0&&!style){charIndex=1}for(var index in currentLineStylesCloned){var numericIndex=parseInt(index,10);if(numericIndex>=charIndex){currentLineStyles[numericIndex+1]=currentLineStylesCloned[numericIndex]}}this.styles[lineIndex][charIndex]=style||clone(currentLineStyles[charIndex-1])},insertStyleObjects:function(_chars,isEndOfLine,styles){if(this.isEmptyStyles())return;var cursorLocation=this.get2DCursorLocation(),lineIndex=cursorLocation.lineIndex,charIndex=cursorLocation.charIndex;if(!this.styles[lineIndex]){this.styles[lineIndex]={}}if(_chars==="\n"){this.insertNewlineStyleObject(lineIndex,charIndex,isEndOfLine)}else{if(styles){this._insertStyles(styles)}else{this.insertCharStyleObject(lineIndex,charIndex)}}},_insertStyles:function(styles){for(var i=0,len=styles.length;ilineIndex){this.styles[numericLine+offset]=clonedStyles[numericLine]}}},removeStyleObject:function(isBeginningOfLine,index){var cursorLocation=this.get2DCursorLocation(index),lineIndex=cursorLocation.lineIndex,charIndex=cursorLocation.charIndex;if(isBeginningOfLine){var textLines=this.text.split(this._reNewline),textOnPreviousLine=textLines[lineIndex-1],newCharIndexOnPrevLine=textOnPreviousLine?textOnPreviousLine.length:0;if(!this.styles[lineIndex-1]){this.styles[lineIndex-1]={}}for(charIndex in this.styles[lineIndex]){this.styles[lineIndex-1][parseInt(charIndex,10)+newCharIndexOnPrevLine]=this.styles[lineIndex][charIndex]}this.shiftLineStyles(lineIndex,-1)}else{var currentLineStyles=this.styles[lineIndex];if(currentLineStyles){var offset=this.selectionStart===this.selectionEnd?-1:0;delete currentLineStyles[charIndex+offset]}var currentLineStylesCloned=clone(currentLineStyles);for(var i in currentLineStylesCloned){var numericIndex=parseInt(i,10);if(numericIndex>=charIndex&&numericIndex!==0){currentLineStyles[numericIndex-1]=currentLineStylesCloned[numericIndex];delete currentLineStyles[numericIndex]}}}},insertNewline:function(){this.insertChars("\n")}})})();fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date;this.__lastLastClickTime=+new Date;this.__lastPointer={};this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(options){this.__newClickTime=+new Date;var newPointer=this.canvas.getPointer(options.e);if(this.isTripleClick(newPointer)){this.fire("tripleclick",options);this._stopEvent(options.e)}else if(this.isDoubleClick(newPointer)){this.fire("dblclick",options);this._stopEvent(options.e)}this.__lastLastClickTime=this.__lastClickTime;this.__lastClickTime=this.__newClickTime;this.__lastPointer=newPointer;this.__lastIsEditing=this.isEditing;this.__lastSelected=this.selected},isDoubleClick:function(newPointer){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===newPointer.x&&this.__lastPointer.y===newPointer.y&&this.__lastIsEditing},isTripleClick:function(newPointer){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===newPointer.x&&this.__lastPointer.y===newPointer.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault();e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler();this.initMousedownHandler();this.initMousemoveHandler();this.initMouseupHandler();this.initClicks()},initClicks:function(){this.on("dblclick",function(options){this.selectWord(this.getSelectionStartFromPointer(options.e))});this.on("tripleclick",function(options){this.selectLine(this.getSelectionStartFromPointer(options.e))})},initMousedownHandler:function(){this.on("mousedown",function(options){var pointer=this.canvas.getPointer(options.e);this.__mousedownX=pointer.x;this.__mousedownY=pointer.y;this.__isMousedown=true;if(this.hiddenTextarea&&this.canvas){this.canvas.wrapperEl.appendChild(this.hiddenTextarea)}if(this.selected){this.setCursorByClick(options.e)}if(this.isEditing){this.__selectionStartOnMouseDown=this.selectionStart;this.initDelayedCursor(true)}})},initMousemoveHandler:function(){this.on("mousemove",function(options){if(!this.__isMousedown||!this.isEditing)return;var newSelectionStart=this.getSelectionStartFromPointer(options.e);if(newSelectionStart>=this.__selectionStartOnMouseDown){this.setSelectionStart(this.__selectionStartOnMouseDown);this.setSelectionEnd(newSelectionStart)}else{this.setSelectionStart(newSelectionStart);this.setSelectionEnd(this.__selectionStartOnMouseDown)}})},_isObjectMoved:function(e){var pointer=this.canvas.getPointer(e);return this.__mousedownX!==pointer.x||this.__mousedownY!==pointer.y},initMouseupHandler:function(){this.on("mouseup",function(options){this.__isMousedown=false;if(this._isObjectMoved(options.e))return;if(this.__lastSelected){this.enterEditing();this.initDelayedCursor(true)}this.selected=true})},setCursorByClick:function(e){var newSelectionStart=this.getSelectionStartFromPointer(e);if(e.shiftKey){if(newSelectionStartdistanceBtwLastCharAndCursor?0:1,newSelectionStart=index+offset;if(this.flipX){newSelectionStart=jlen-newSelectionStart}if(newSelectionStart>this.text.length){newSelectionStart=this.text.length}if(j===jlen){newSelectionStart--}return newSelectionStart}});fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea");this.hiddenTextarea.setAttribute("autocapitalize","off");this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px";fabric.document.body.appendChild(this.hiddenTextarea);fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this));fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this));if(!this._clickHandlerInitialized&&this.canvas){fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this));this._clickHandlerInitialized=true}},_keysMap:{8:"removeChars",13:"insertNewline",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",67:"copy",86:"paste",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap){this[this._keysMap[e.keyCode]](e)}else if(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)){this[this._ctrlKeysMap[e.keyCode]](e)}else{return}e.preventDefault();e.stopPropagation();this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){if(this.selectionStart===this.selectionEnd){this.moveCursorRight(e)}this.removeChars(e)},copy:function(){var selectedText=this.getSelectedText();this.copiedText=selectedText;this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(){if(this.copiedText){this.insertChars(this.copiedText)}},cut:function(e){this.copy();this.removeChars(e)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey||e.keyCode===8||e.keyCode===13){return}this.insertChars(String.fromCharCode(e.which));e.preventDefault();e.stopPropagation()},getDownCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,textLines=this.text.split(this._reNewline),_char,lineLeftOffset,textBeforeCursor=this.text.slice(0,selectionProp),textAfterCursor=this.text.slice(selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnSameLineAfterCursor=textAfterCursor.match(/(.*)\n?/)[1],textOnNextLine=(textAfterCursor.match(/.*\n(.*)\n?/)||{})[1]||"",cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===textLines.length-1||e.metaKey){return this.text.length-selectionProp}var widthOfSameLineBeforeCursor=this._getWidthOfLine(this.ctx,cursorLocation.lineIndex,textLines);lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor);var widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnNextLine-widthOfChar,rightEdge=widthOfCharsOnNextLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnNextLine=offsetFromRightEdgethis.text.length){this.selectionStart=this.text.length}this.selectionEnd=this.selectionStart},moveCursorDownWithShift:function(offset){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this.selectionStart+=offset;this._selectionDirection="left";return}else{this._selectionDirection="right";this.selectionEnd+=offset;if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}}},getUpCursorOffset:function(e,isRight){var selectionProp=isRight?this.selectionEnd:this.selectionStart,cursorLocation=this.get2DCursorLocation(selectionProp);if(cursorLocation.lineIndex===0||e.metaKey){return selectionProp}var textBeforeCursor=this.text.slice(0,selectionProp),textOnSameLineBeforeCursor=textBeforeCursor.slice(textBeforeCursor.lastIndexOf("\n")+1),textOnPreviousLine=(textBeforeCursor.match(/\n?(.*)\n.*$/)||{})[1]||"",textLines=this.text.split(this._reNewline),_char,widthOfSameLineBeforeCursor=this._getWidthOfLine(this.ctx,cursorLocation.lineIndex,textLines),lineLeftOffset=this._getLineLeftOffset(widthOfSameLineBeforeCursor),widthOfCharsOnSameLineBeforeCursor=lineLeftOffset,lineIndex=cursorLocation.lineIndex;for(var i=0,len=textOnSameLineBeforeCursor.length;iwidthOfCharsOnSameLineBeforeCursor){foundMatch=true;var leftEdge=widthOfCharsOnPreviousLine-widthOfChar,rightEdge=widthOfCharsOnPreviousLine,offsetFromLeftEdge=Math.abs(leftEdge-widthOfCharsOnSameLineBeforeCursor),offsetFromRightEdge=Math.abs(rightEdge-widthOfCharsOnSameLineBeforeCursor);indexOnPrevLine=offsetFromRightEdge=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation();this._currentCursorOpacity=1;if(e.shiftKey){this.moveCursorRightWithShift(e)}else{this.moveCursorRightWithoutShift(e)}this.initDelayedCursor()},moveCursorRightWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this._moveRight(e,"selectionStart")}else{this._selectionDirection="right";this._moveRight(e,"selectionEnd");if(this.text.charAt(this.selectionEnd-1)==="\n"){this.selectionEnd++}if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}}},moveCursorRightWithoutShift:function(e){this._selectionDirection="right";if(this.selectionStart===this.selectionEnd){this._moveRight(e,"selectionStart");this.selectionEnd=this.selectionStart}else{this.selectionEnd+=this.getNumNewLinesInSelectedText();if(this.selectionEnd>this.text.length){this.selectionEnd=this.text.length}this.selectionStart=this.selectionEnd}},removeChars:function(e){if(this.selectionStart===this.selectionEnd){this._removeCharsNearCursor(e)}else{this._removeCharsFromTo(this.selectionStart,this.selectionEnd)}this.selectionEnd=this.selectionStart;this._removeExtraneousStyles();if(this.canvas){this.canvas.renderAll().renderAll()}this.setCoords();this.fire("changed");this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0){if(e.metaKey){var leftLineBoundary=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftLineBoundary,this.selectionStart);this.selectionStart=leftLineBoundary}else if(e.altKey){var leftWordBoundary=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(leftWordBoundary,this.selectionStart);this.selectionStart=leftWordBoundary}else{var isBeginningOfLine=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(isBeginningOfLine);this.selectionStart--;this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}});fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects){if(!this.styles[lineIndex]){this.callSuper("_setSVGTextLineText",textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier)}else{this._setSVGTextLineChars(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects)}},_setSVGTextLineChars:function(textLine,lineIndex,textSpans,lineHeight,lineTopOffsetMultiplier,textBgRects){var yProp=lineIndex===0||this.useNative?"y":"dy",chars=textLine.split(""),charOffset=0,lineLeftOffset=this._getSVGLineLeftOffset(lineIndex),lineTopOffset=this._getSVGLineTopOffset(lineIndex),heightOfLine=this._getHeightOfLine(this.ctx,lineIndex);for(var i=0,len=chars.length;i'].join("")},_createTextCharSpan:function(_char,styleDecl,lineLeftOffset,lineTopOffset,yProp,charOffset){var fillStyles=this.getSvgStyles.call(fabric.util.object.extend({visible:true,fill:this.fill,stroke:this.stroke,type:"text"},styleDecl));return['',fabric.util.string.escapeXml(_char),""].join("")}});(function(){if(typeof document!=="undefined"&&typeof window!=="undefined"){return}var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;function request(url,encoding,callback){var oURL=URL.parse(url);if(!oURL.port){oURL.port=oURL.protocol.indexOf("https:")===0?443:80}var reqHandler=oURL.port===443?HTTPS:HTTP,req=reqHandler.request({hostname:oURL.hostname,port:oURL.port,path:oURL.path,method:"GET"},function(response){var body="";if(encoding){response.setEncoding(encoding)}response.on("end",function(){callback(body)});response.on("data",function(chunk){if(response.statusCode===200){body+=chunk}})});req.on("error",function(err){if(err.errno===process.ECONNREFUSED){fabric.log("ECONNREFUSED: connection refused to "+oURL.hostname+":"+oURL.port)}else{fabric.log(err.message)}});req.end()}function requestFs(path,callback){var fs=require("fs");fs.readFile(path,function(err,data){if(err){fabric.log(err);throw err}else{callback(data)}})}fabric.util.loadImage=function(url,callback,context){function createImageAndCallBack(data){img.src=new Buffer(data,"binary");img._src=url;callback&&callback.call(context,img)}var img=new Image;if(url&&(url instanceof Buffer||url.indexOf("data")===0)){img.src=img._src=url;callback&&callback.call(context,img)}else if(url&&url.indexOf("http")!==0){requestFs(url,createImageAndCallBack)}else if(url){request(url,"binary",createImageAndCallBack)}else{callback&&callback.call(context,url)}};fabric.loadSVGFromURL=function(url,callback,reviver){url=url.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim();if(url.indexOf("http")!==0){requestFs(url,function(body){fabric.loadSVGFromString(body.toString(),callback,reviver)})}else{request(url,"",function(body){fabric.loadSVGFromString(body,callback,reviver)})}};fabric.loadSVGFromString=function(string,callback,reviver){var doc=(new DOMParser).parseFromString(string);fabric.parseSVGDocument(doc.documentElement,function(results,options){callback&&callback(results,options)},reviver)};fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body);callback&&callback()})};fabric.Image.fromObject=function(object,callback){fabric.util.loadImage(object.src,function(img){var oImg=new fabric.Image(img);oImg._initConfig(object);oImg._initFilters(object,function(filters){oImg.filters=filters||[];callback&&callback(oImg)})})};fabric.createCanvasForNode=function(width,height,options,nodeCanvasOptions){nodeCanvasOptions=nodeCanvasOptions||options;var canvasEl=fabric.document.createElement("canvas"),nodeCanvas=new Canvas(width||600,height||600,nodeCanvasOptions);canvasEl.style={};canvasEl.width=nodeCanvas.width;canvasEl.height=nodeCanvas.height;var FabricCanvas=fabric.Canvas||fabric.StaticCanvas,fabricCanvas=new FabricCanvas(canvasEl,options);fabricCanvas.contextContainer=nodeCanvas.getContext("2d");fabricCanvas.nodeCanvas=nodeCanvas;fabricCanvas.Font=Canvas.Font;return fabricCanvas};fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()};fabric.StaticCanvas.prototype.createJPEGStream=function(opts){return this.nodeCanvas.createJPEGStream(opts)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(width){origSetWidth.call(this,width);this.nodeCanvas.width=width;return this};if(fabric.Canvas){fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth}var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(height){origSetHeight.call(this,height); this.nodeCanvas.height=height;return this};if(fabric.Canvas){fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight}})(); \ No newline at end of file diff --git a/dist/fabric.min.js.gz b/dist/fabric.min.js.gz index 1147878524e869b6365960e3bab14b9c5d109fc6..10815dfed141c143d9d9cdded74b7bd140f5e7dd 100644 GIT binary patch delta 18052 zcmV(xKcqK_S3>|}l3z>@_JG4iNk9L?`S6f-TfT;3_=g8zSe z-~QFcu_XNa{uOw3KakK!NI1?-B#$|MCf;@I9NW2xKg@E3G{6uc>1u?51KPiRyQ=z8 z{g}~6;5^Rv-Fsp*{qE}O>gwuxN{d71?hlP;h@X%G><1hl1?BgBl z_Cg>hzs(DHjezJk>5%!FyFpP69uC@NC43xdWU1tjw5 z)9yu^A0Yrr)3bcvavE6tK^D|3iZ6yMLzdN`B4hG4yh}aBPiToKjaIWl*AyPjR{tH#j~)J5hGT zS?TyNCG(smE~p`&0c9@Njua=)|8~$rnBDZP{7cPhB7of z<63Gu47}iKRDn-{0-rJb#Y-xL&A3W$Oj^o)irPs`K*~G$w2r{3)gnxdtbt0`rcJ&om{jM*o?>JeR)`Yn^#$+nQ?SENPKW5u)anmXDt6~0 zy@T^MYomFQpC@_7^M6aY+f$#f^%!19eBtL8a}A-MLwrX8XQ(C;-i288jWz`|cM8n- zs4NN9g(98-IZ21+MgKh|NSBHMXw_~oMo`yV8SRQzeiLTv0X1Y-=F*{tq;o`KOpioe zjvFcrWgs#o760TH{}c{FStmsSdy*xP#Hl3C#I|cPF40csJAds*my}SNn3D_oV5--* zom*UBV2z(h^p_XZqeTn>006n9a(So4gDvcI0gNvw6JA$6YP>VVKWPfC8H?E5*#o_? zJ8)tKO4O@om*L1S368zT#J>N@z82UbR;#xMiPxX9r)3>lrLaqkgSv{uq2vmvB^jY> zBp08!@^>W-@_*k+Q1*>}U6rV4zkRT0e#$eO8jtGTjui9D?)1BdBZZ%6NKTv=K~hIkCvMY7j07aD$q( zCX{D1n{?7X6gp@LZck}2xmn;_P>HW%o3+~mNJiAUk&IG1V^D)F7K+$Mgr<}#BDX_35ef@`YV7H5g=f3vFIq~JQTa_v zR#>89w)}0&p3PT$)~XWg>4EjgRljlHyx@>Ci&p%^~Q>bJI`@;5>x?`{VK*rj>5^Mrux(A z==v3<)PThlyG3?h$dJiFAy4I%b=5Hy&nVNCI;+Daz6|EFX&Sv1IebZSAncLs!kUWi zet%){Xuv+OHP)k+(pXw0Q$R{ptr-OJtuBDEQ;%~N=Fs`0;GoVLwbJ@VLkFtrOva_5 zvs5K2*EL_+sB`TARm4uN7N{v0lnJFAU&XF;*$AF=-jc9aR6gbl7q7h>fbS zX!O>O{PTA zzX=kdQUn1|KaDab*erAY!aZv$##0u&LI@SW8`x-u14AT_wMN|J$98bXMcm* zs*Ze_No>Q~XBYzB-f0bK61!^fq^UrhboM2Enor>!8D3wLXgnFG5h#K_-oa@-iK=?a zRS!<62dP$gqh;n~|z%-1`kuph76NC+AqapbI*OZ7(Ysw$`Fd z6J<+lq979w%CE48H=->q=SCcDY=42e>((O1z(tJaD7r zJ=76a!06zt*_bzDU%Y&aTFR*3&|gKT>FNLuY~&zFwyS3zmgP}a<*_sJM^ ziW9a4S8TEuXiZ}lp&xpz?thic8B82NpP4S;J5x}3wr^}S}NP={Mq&Ep)PFmasA>i=^Bzhn`A?cMQ9h;l>x1`TiWPh?ond~pHoxDPe*phsyW+TTG*V^Rt2o=Oyy`4A`j<{UESSs{QIoDTGO?2)kQYoXl9T zjPta1BZ$$AbQ4&lYJ^x+kHzA$tWto)!waR>4P{^bcKG6#_fJ2*e)m?7(t9sBw4qL^ zJyjgUTxQB$kJGl4;B+~v^#NuTtm3J|J9;RK3WyF`K7ZMO?s^>E4QdtXoT_7s0Z!Q% z0Gs|)oJ%E`wWL*Ew05JRZnxh$%A_RF)xdE%0DbZW5)pSp87=T~N9Sze6QX=HG$o^e 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 X3)ctCY7<saw&)U_u1d5G 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 diff --git a/dist/fabric.require.js b/dist/fabric.require.js index 851fff3d..896c393b 100644 --- a/dist/fabric.require.js +++ b/dist/fabric.require.js @@ -9899,6 +9899,9 @@ fabric.util.object.extend(fabric.Object.prototype, { extend(this, options); } this._setOpacityIfSame(); + this._calcBounds(); + this._updateObjectsCoords(); + this.setCoords(); this.saveCoords(); }, _updateObjectsCoords: function() { @@ -10099,11 +10102,7 @@ fabric.util.object.extend(fabric.Object.prototype, { this.set(this._getBounds(aX, aY, onlyWidthHeight)); }, _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt; - if (this.canvas) { - ivt = fabric.util.invertTransform(this.getViewportTransform()); - } - var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { + var ivt = fabric.util.invertTransform(this.getViewportTransform()), minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { width: maxXY.x - minXY.x || 0, height: maxXY.y - minXY.y || 0 }; diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index b7e0c570..53bc4940 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -63,7 +63,9 @@ extend(this, options); } this._setOpacityIfSame(); - //this.setCoords(); + this._calcBounds(); + this._updateObjectsCoords(); + this.setCoords(); this.saveCoords(); }, @@ -448,11 +450,8 @@ * @private */ _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt; - if (this.canvas) { - ivt = fabric.util.invertTransform(this.getViewportTransform()); - } - var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), + var ivt = fabric.util.invertTransform(this.getViewportTransform()), + minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { width: (maxXY.x - minXY.x) || 0, From 6eae0994dbb255205b947f3f64671b2871ca972d Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 09:05:48 +0100 Subject: [PATCH 15/27] Fix group coordinates --- src/shapes/group.class.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 53bc4940..e80bbc85 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -63,9 +63,7 @@ extend(this, options); } this._setOpacityIfSame(); - this._calcBounds(); this._updateObjectsCoords(); - this.setCoords(); this.saveCoords(); }, From 03926af729ae094e10901525891e03d3f7f13298 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 09:37:32 +0100 Subject: [PATCH 16/27] Fix group coords --- src/canvas.class.js | 6 +++--- src/shapes/group.class.js | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/canvas.class.js b/src/canvas.class.js index 90bb6342..84e48ec2 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -995,9 +995,9 @@ this._activeGroup = group; if (group) { group.canvas = this; - group._calcBounds(); - group._updateObjectsCoords(); - group.setCoords(); + //group._calcBounds(); + //group._updateObjectsCoords(); + //group.setCoords(); group.set('active', true); } }, diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index e80bbc85..53bc4940 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -63,7 +63,9 @@ extend(this, options); } this._setOpacityIfSame(); + this._calcBounds(); this._updateObjectsCoords(); + this.setCoords(); this.saveCoords(); }, From 09186c141b4f7ba9a73bac9c41a4204b62f44b37 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 10:06:40 +0100 Subject: [PATCH 17/27] Fix group coordinates --- src/canvas.class.js | 3 --- src/mixins/canvas_grouping.mixin.js | 6 ++++-- src/static_canvas.class.js | 16 ++++++++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/canvas.class.js b/src/canvas.class.js index 84e48ec2..52326ba9 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -995,9 +995,6 @@ this._activeGroup = group; if (group) { group.canvas = this; - //group._calcBounds(); - //group._updateObjectsCoords(); - //group.setCoords(); group.set('active', true); } }, diff --git a/src/mixins/canvas_grouping.mixin.js b/src/mixins/canvas_grouping.mixin.js index b277ba94..f0323f0d 100644 --- a/src/mixins/canvas_grouping.mixin.js +++ b/src/mixins/canvas_grouping.mixin.js @@ -107,7 +107,8 @@ return new fabric.Group(groupObjects, { originX: 'center', - originY: 'center' + originY: 'center', + canvas: this }); }, @@ -126,7 +127,8 @@ else if (group.length > 1) { group = new fabric.Group(group.reverse(), { originX: 'center', - originY: 'center' + originY: 'center', + canvas: this }); this.setActiveGroup(group, e); group.saveCoords(); diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 6567023a..c26134d1 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -670,14 +670,14 @@ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; - if (obj._objects) { - obj._calcBounds(); - for (var i = 0, len = obj._objects.length; i < len; i++) { - obj._objects[i].canvas = this; - this._onObjectAdded(obj._objects[i]); - } - obj._updateObjectsCoords(); - } + //if (obj._objects) { + // obj._calcBounds(); + // for (var i = 0, len = obj._objects.length; i < len; i++) { + // obj._objects[i].canvas = this; + // this._onObjectAdded(obj._objects[i]); + // } + // obj._updateObjectsCoords(); + //} obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); From 77811aa4bc4da229e239383b7fea7b15f46c9dab Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 10:52:12 +0100 Subject: [PATCH 18/27] Fix FromObject --- src/shapes/group.class.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 53bc4940..8061cb68 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -59,14 +59,14 @@ this.originalState = { }; this.callSuper('initialize'); - if (options) { - extend(this, options); - } this._setOpacityIfSame(); this._calcBounds(); this._updateObjectsCoords(); this.setCoords(); this.saveCoords(); + if (options) { + extend(this, options); + } }, /** From fc0dd0377a80c8d9368fbabce08bb659e3c185ae Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 11:13:55 +0100 Subject: [PATCH 19/27] ToSVG respects viewport transformation --- src/mixins/object.svg_export.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 43717121..ba18e5fc 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -47,6 +47,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var toFixed = fabric.util.toFixed, angle = this.getAngle(), center = this.getCenterPoint(), + vpt = this.getViewportTransform(); + + center = fabric.util.transformPoint(center, vpt); NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, @@ -63,9 +66,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot scalePart = (this.scaleX === 1 && this.scaleY === 1) ? '' : (' scale(' + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + ' ' + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + ')'), flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', From 42ce906e0d9f8e4c8a4beccc3dda27e6ed1f6b9a Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 09:05:48 +0100 Subject: [PATCH 20/27] Fix group coordinates --- src/canvas.class.js | 3 --- src/mixins/canvas_grouping.mixin.js | 6 ++++-- src/static_canvas.class.js | 16 ++++++++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/canvas.class.js b/src/canvas.class.js index 90bb6342..52326ba9 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -995,9 +995,6 @@ this._activeGroup = group; if (group) { group.canvas = this; - group._calcBounds(); - group._updateObjectsCoords(); - group.setCoords(); group.set('active', true); } }, diff --git a/src/mixins/canvas_grouping.mixin.js b/src/mixins/canvas_grouping.mixin.js index b277ba94..f0323f0d 100644 --- a/src/mixins/canvas_grouping.mixin.js +++ b/src/mixins/canvas_grouping.mixin.js @@ -107,7 +107,8 @@ return new fabric.Group(groupObjects, { originX: 'center', - originY: 'center' + originY: 'center', + canvas: this }); }, @@ -126,7 +127,8 @@ else if (group.length > 1) { group = new fabric.Group(group.reverse(), { originX: 'center', - originY: 'center' + originY: 'center', + canvas: this }); this.setActiveGroup(group, e); group.saveCoords(); diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 6567023a..c26134d1 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -670,14 +670,14 @@ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; - if (obj._objects) { - obj._calcBounds(); - for (var i = 0, len = obj._objects.length; i < len; i++) { - obj._objects[i].canvas = this; - this._onObjectAdded(obj._objects[i]); - } - obj._updateObjectsCoords(); - } + //if (obj._objects) { + // obj._calcBounds(); + // for (var i = 0, len = obj._objects.length; i < len; i++) { + // obj._objects[i].canvas = this; + // this._onObjectAdded(obj._objects[i]); + // } + // obj._updateObjectsCoords(); + //} obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); From 6fbd5df49c040b26c22ab7a03b58704dbb90d28d Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 10:52:12 +0100 Subject: [PATCH 21/27] Fix FromObject --- src/shapes/group.class.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 53bc4940..8061cb68 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -59,14 +59,14 @@ this.originalState = { }; this.callSuper('initialize'); - if (options) { - extend(this, options); - } this._setOpacityIfSame(); this._calcBounds(); this._updateObjectsCoords(); this.setCoords(); this.saveCoords(); + if (options) { + extend(this, options); + } }, /** From f6161f8713ba084f1296c661a6a1cea66c6683e4 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 11:13:55 +0100 Subject: [PATCH 22/27] ToSVG respects viewport transformation --- src/mixins/object.svg_export.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 43717121..ba18e5fc 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -47,6 +47,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var toFixed = fabric.util.toFixed, angle = this.getAngle(), center = this.getCenterPoint(), + vpt = this.getViewportTransform(); + + center = fabric.util.transformPoint(center, vpt); NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, @@ -63,9 +66,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot scalePart = (this.scaleX === 1 && this.scaleY === 1) ? '' : (' scale(' + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + ' ' + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + ')'), flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', From ec0dcbfeb0eebfaead0190816f2fa501e2db9a59 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 17:33:37 +0100 Subject: [PATCH 23/27] JSHint --- src/mixins/object.svg_export.js | 6 ++---- src/static_canvas.class.js | 6 +----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index ba18e5fc..b2b038e4 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -46,10 +46,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot getSvgTransform: function() { var toFixed = fabric.util.toFixed, angle = this.getAngle(), - center = this.getCenterPoint(), - vpt = this.getViewportTransform(); - - center = fabric.util.transformPoint(center, vpt); + vpt = this.getViewportTransform(), + center = fabric.util.transformPoint(this.getCenterPoint(), vpt), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index c26134d1..b166d5c0 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -596,17 +596,13 @@ * @chainable true */ absolutePan: function (point) { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ); this.viewportTransform[4] = -point.x; this.viewportTransform[5] = -point.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } - return this + return this; }, /** From d5f8d88bfca69a8f4e9ee271cdfab38713655e8b Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 17:47:25 +0100 Subject: [PATCH 24/27] Fix ToSVG scaling --- src/mixins/object.svg_export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index b2b038e4..6b0346a8 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -61,7 +61,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') : '', - scalePart = (this.scaleX === 1 && this.scaleY === 1) + scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) ? '' : (' scale(' + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + From 300de3be0d65bdf6b2f6942269870f276e86e9f6 Mon Sep 17 00:00:00 2001 From: Tom French Date: Fri, 4 Jul 2014 20:07:04 +0100 Subject: [PATCH 25/27] Correct rendering of active group --- src/canvas.class.js | 1 - src/mixins/canvas_grouping.mixin.js | 2 ++ src/shapes/group.class.js | 14 +++++++++----- src/static_canvas.class.js | 8 -------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/canvas.class.js b/src/canvas.class.js index 52326ba9..a6865c03 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -994,7 +994,6 @@ _setActiveGroup: function(group) { this._activeGroup = group; if (group) { - group.canvas = this; group.set('active', true); } }, diff --git a/src/mixins/canvas_grouping.mixin.js b/src/mixins/canvas_grouping.mixin.js index f0323f0d..54f5877e 100644 --- a/src/mixins/canvas_grouping.mixin.js +++ b/src/mixins/canvas_grouping.mixin.js @@ -83,6 +83,7 @@ if (this._activeObject && target !== this._activeObject) { var group = this._createGroup(target); + group.addWithUpdate(); this.setActiveGroup(group); this._activeObject = null; @@ -130,6 +131,7 @@ originY: 'center', canvas: this }); + group.addWithUpdate(); this.setActiveGroup(group, e); group.saveCoords(); this.fire('selection:created', { target: group }); diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 8061cb68..8f76aab5 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -59,14 +59,16 @@ this.originalState = { }; this.callSuper('initialize'); - this._setOpacityIfSame(); this._calcBounds(); this._updateObjectsCoords(); - this.setCoords(); - this.saveCoords(); + if (options) { extend(this, options); } + this._setOpacityIfSame(); + + this.setCoords(); + this.saveCoords(); }, /** @@ -113,8 +115,10 @@ */ addWithUpdate: function(object) { this._restoreObjectsState(); - this._objects.push(object); - object.group = this; + if (object) { + this._objects.push(object); + object.group = this; + } // since _restoreObjectsState set objects inactive this.forEachObject(this._setObjectActive, this); this._calcBounds(); diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index b166d5c0..a31260a2 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -666,14 +666,6 @@ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; - //if (obj._objects) { - // obj._calcBounds(); - // for (var i = 0, len = obj._objects.length; i < len; i++) { - // obj._objects[i].canvas = this; - // this._onObjectAdded(obj._objects[i]); - // } - // obj._updateObjectsCoords(); - //} obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); From 6b8ce5c1ce383f01bef8e07e6fc49a11ff6987c0 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 5 Jul 2014 18:06:21 +0100 Subject: [PATCH 26/27] JSHint --- src/shapes/circle.class.js | 2 +- src/shapes/group.class.js | 6 ++---- src/shapes/text.class.js | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/shapes/circle.class.js b/src/shapes/circle.class.js index bdf1a6c6..fccffd58 100644 --- a/src/shapes/circle.class.js +++ b/src/shapes/circle.class.js @@ -172,7 +172,7 @@ parsedAttributes.left = 0; } if (!('top' in parsedAttributes)) { - parsedAttributes.top = 0 + parsedAttributes.top = 0; } if (!('transformMatrix' in parsedAttributes)) { parsedAttributes.left -= (options.width / 2); diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 8f76aab5..04b7c299 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -6,8 +6,7 @@ extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, - invoke = fabric.util.array.invoke, - degreesToRadians = fabric.util.degreesToRadians; + invoke = fabric.util.array.invoke; if (fabric.Group) { return; @@ -216,9 +215,8 @@ /** * Renders instance on a given context * @param {CanvasRenderingContext2D} ctx context to render instance on - * @param {Boolean} [noTransform] When true, context is not transformed */ - render: function(ctx, noTransform) { + render: function(ctx) { // do not render if object is not visible if (!this.visible) return; diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 6f30c625..e164b1a7 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -759,9 +759,8 @@ /** * Renders text instance on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed */ - render: function(ctx, noTransform) { + render: function(ctx) { // do not render if object is not visible if (!this.visible) return; From 8cf9642fe4abc153c17c0f82845293f5b156a96a Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 5 Jul 2014 20:18:23 +0100 Subject: [PATCH 27/27] Transform point given to zoomToPoint --- src/static_canvas.class.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index a31260a2..f3ca801a 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -565,7 +565,8 @@ */ zoomToPoint: function (point, value) { // TODO: just change the scale, preserve other transformations - var before = fabric.util.transformPoint(point, this.viewportTransform); + var before = point; + point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); this.viewportTransform[0] = value; this.viewportTransform[3] = value; var after = fabric.util.transformPoint(point, this.viewportTransform);

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 VAAIEMh1"); - - fabric.window = fabric.document.createWindow(); -} - -/** - * True when in environment that supports touch events - * @type boolean - */ -fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; - -/** - * True when in environment that's probably Node.js - * @type boolean - */ -fabric.isLikelyNode = typeof Buffer !== 'undefined' && - typeof window === 'undefined'; - - -/** - * Attributes parsed from all SVG elements - * @type array - */ -fabric.SHARED_ATTRIBUTES = [ - "display", - "transform", - "fill", "fill-opacity", "fill-rule", - "opacity", - "stroke", "stroke-dasharray", "stroke-linecap", - "stroke-linejoin", "stroke-miterlimit", - "stroke-opacity", "stroke-width" -]; - - -/*! - * Copyright (c) 2009 Simo Kinnunen. - * Licensed under the MIT license. - */ - -var Cufon = (function() { - - /** @ignore */ - var api = function() { - return api.replace.apply(null, arguments); - }; - - /** @ignore */ - var DOM = api.DOM = { - - ready: (function() { - - var complete = false, readyStatus = { loaded: 1, complete: 1 }; - - var queue = [], /** @ignore */ perform = function() { - if (complete) return; - complete = true; - for (var fn; fn = queue.shift(); fn()); - }; - - // Gecko, Opera, WebKit r26101+ - - if (fabric.document.addEventListener) { - fabric.document.addEventListener('DOMContentLoaded', perform, false); - fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages - } - - // Old WebKit, Internet Explorer - - if (!fabric.window.opera && fabric.document.readyState) (function() { - readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10); - })(); - - // Internet Explorer - - if (fabric.document.readyState && fabric.document.createStyleSheet) (function() { - try { - fabric.document.body.doScroll('left'); - perform(); - } - catch (e) { - setTimeout(arguments.callee, 1); - } - })(); - - addEvent(fabric.window, 'load', perform); // Fallback - - return function(listener) { - if (!arguments.length) perform(); - else complete ? listener() : queue.push(listener); - }; - - })() - - }; - - /** @ignore */ - var CSS = api.CSS = /** @ignore */ { - - /** @ignore */ - Size: function(value, base) { - - this.value = parseFloat(value); - this.unit = String(value).match(/[a-z%]*$/)[0] || 'px'; - - /** @ignore */ - this.convert = function(value) { - return value / base * this.value; - }; - - /** @ignore */ - this.convertFrom = function(value) { - return value / this.value * base; - }; - - /** @ignore */ - this.toString = function() { - return this.value + this.unit; - }; - - }, - - /** @ignore */ - getStyle: function(el) { - return new Style(el.style); - /* - var view = document.defaultView; - if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null)); - if (el.currentStyle) return new Style(el.currentStyle); - return new Style(el.style); - */ - }, - - quotedList: cached(function(value) { - // doesn't work properly with empty quoted strings (""), but - // it's not worth the extra code. - var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match; - while (match = re.exec(value)) list.push(match[3] || match[1]); - return list; - }), - - ready: (function() { - - var complete = false; - - var queue = [], perform = function() { - complete = true; - for (var fn; fn = queue.shift(); fn()); - }; - - // Safari 2 does not include '); - - function getFontSizeInPixels(el, value) { - return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value); - } - - // Original by Dead Edwards. - // Combined with getFontSizeInPixels it also works with relative units. - function getSizeInPixels(el, value) { - if (/px$/i.test(value)) return parseFloat(value); - var style = el.style.left, runtimeStyle = el.runtimeStyle.left; - el.runtimeStyle.left = el.currentStyle.left; - el.style.left = value; - var result = el.style.pixelLeft; - el.style.left = style; - el.runtimeStyle.left = runtimeStyle; - return result; - } - - return function(font, text, style, options, node, el, hasNext) { - var redraw = (text === null); - - if (redraw) text = node.alt; - - // @todo word-spacing, text-decoration - - var viewBox = font.viewBox; - - var size = style.computedFontSize || - (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize)); - - var letterSpacing = style.computedLSpacing; - - if (letterSpacing == undefined) { - letterSpacing = style.get('letterSpacing'); - style.computedLSpacing = letterSpacing = - (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing)); - } - - var wrapper, canvas; - - if (redraw) { - wrapper = node; - canvas = node.firstChild; - } - else { - wrapper = fabric.document.createElement('span'); - wrapper.className = 'cufon cufon-vml'; - wrapper.alt = text; - - canvas = fabric.document.createElement('span'); - canvas.className = 'cufon-vml-canvas'; - wrapper.appendChild(canvas); - - if (options.printable) { - var print = fabric.document.createElement('span'); - print.className = 'cufon-alt'; - print.appendChild(fabric.document.createTextNode(text)); - wrapper.appendChild(print); - } - - // ie6, for some reason, has trouble rendering the last VML element in the document. - // we can work around this by injecting a dummy element where needed. - // @todo find a better solution - if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape')); - } - - var wStyle = wrapper.style; - var cStyle = canvas.style; - - var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height); - var roundingFactor = roundedHeight / height; - var minX = viewBox.minX, minY = viewBox.minY; - - cStyle.height = roundedHeight; - cStyle.top = Math.round(size.convert(minY - font.ascent)); - cStyle.left = Math.round(size.convert(minX)); - - wStyle.height = size.convert(font.height) + 'px'; - - var textDecoration = Cufon.getTextDecoration(options); - - var color = style.get('color'); - - var chars = Cufon.CSS.textTransform(text, style).split(''); - - var width = 0, offsetX = 0, advance = null; - - var glyph, shape, shadows = options.textShadow; - - // pre-calculate width - for (var i = 0, k = 0, l = chars.length; i < l; ++i) { - glyph = font.glyphs[chars[i]] || font.missingGlyph; - if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing; - } - - if (advance === null) return null; - - var fullWidth = -minX + width + (viewBox.width - advance); - - var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth); - - var coordSize = fullWidth + ',' + viewBox.height, coordOrigin; - var stretch = 'r' + coordSize + 'nsnf'; - - for (i = 0; i < l; ++i) { - - glyph = font.glyphs[chars[i]] || font.missingGlyph; - if (!glyph) continue; - - if (redraw) { - // some glyphs may be missing so we can't use i - shape = canvas.childNodes[k]; - if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow - } - else { - shape = fabric.document.createElement('cvml:shape'); - canvas.appendChild(shape); - } - - shape.stroked = 'f'; - shape.coordsize = coordSize; - shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY; - shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch; - shape.fillcolor = color; - - // it's important to not set top/left or IE8 will grind to a halt - var sStyle = shape.style; - sStyle.width = roundedShapeWidth; - sStyle.height = roundedHeight; - - if (shadows) { - // due to the limitations of the VML shadow element there - // can only be two visible shadows. opacity is shared - // for all shadows. - var shadow1 = shadows[0], shadow2 = shadows[1]; - var color1 = Cufon.CSS.color(shadow1.color), color2; - var shadow = fabric.document.createElement('cvml:shadow'); - shadow.on = 't'; - shadow.color = color1.color; - shadow.offset = shadow1.offX + ',' + shadow1.offY; - if (shadow2) { - color2 = Cufon.CSS.color(shadow2.color); - shadow.type = 'double'; - shadow.color2 = color2.color; - shadow.offset2 = shadow2.offX + ',' + shadow2.offY; - } - shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1; - shape.appendChild(shadow); - } - - offsetX += ~~(glyph.w || font.w) + letterSpacing; - - ++k; - - } - - wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0); - - return wrapper; - - }; - -})()); - -Cufon.getTextDecoration = function(options) { - return { - underline: options.textDecoration === 'underline', - overline: options.textDecoration === 'overline', - 'line-through': options.textDecoration === 'line-through' - }; +var fabric = fabric || { + version: "1.4.6" }; -if (typeof exports != 'undefined') { - exports.Cufon = Cufon; +if (typeof exports !== "undefined") { + exports.fabric = fabric; } +if (typeof document !== "undefined" && typeof window !== "undefined") { + fabric.document = document; + fabric.window = window; +} else { + fabric.document = require("jsdom").jsdom(""); + fabric.window = fabric.document.createWindow(); +} -/* - json2.js - 2014-02-04 +fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; - Public Domain. +fabric.isLikelyNode = typeof Buffer !== "undefined" && typeof window === "undefined"; - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. +fabric.SHARED_ATTRIBUTES = [ "display", "transform", "fill", "fill-opacity", "fill-rule", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width" ]; - See http://www.JSON.org/js.html +var Cufon = function() { + var api = function() { + return api.replace.apply(null, arguments); + }; + var DOM = api.DOM = { + ready: function() { + var complete = false, readyStatus = { + loaded: 1, + complete: 1 + }; + var queue = [], perform = function() { + if (complete) return; + complete = true; + for (var fn; fn = queue.shift(); fn()) ; + }; + if (fabric.document.addEventListener) { + fabric.document.addEventListener("DOMContentLoaded", perform, false); + fabric.window.addEventListener("pageshow", perform, false); + } + if (!fabric.window.opera && fabric.document.readyState) (function() { + readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10); + })(); + if (fabric.document.readyState && fabric.document.createStyleSheet) (function() { + try { + fabric.document.body.doScroll("left"); + perform(); + } catch (e) { + setTimeout(arguments.callee, 1); + } + })(); + addEvent(fabric.window, "load", perform); + return function(listener) { + if (!arguments.length) perform(); else complete ? listener() : queue.push(listener); + }; + }() + }; + var CSS = api.CSS = { + Size: function(value, base) { + this.value = parseFloat(value); + this.unit = String(value).match(/[a-z%]*$/)[0] || "px"; + this.convert = function(value) { + return value / base * this.value; + }; + this.convertFrom = function(value) { + return value / this.value * base; + }; + this.toString = function() { + return this.value + this.unit; + }; + }, + getStyle: function(el) { + return new Style(el.style); + }, + quotedList: cached(function(value) { + var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match; + while (match = re.exec(value)) list.push(match[3] || match[1]); + return list; + }), + ready: function() { + var complete = false; + var queue = [], perform = function() { + complete = true; + for (var fn; fn = queue.shift(); fn()) ; + }; + var styleElements = Object.prototype.propertyIsEnumerable ? elementsByTagName("style") : { + length: 0 + }; + var linkElements = elementsByTagName("link"); + DOM.ready(function() { + var linkStyles = 0, link; + for (var i = 0, l = linkElements.length; link = linkElements[i], i < l; ++i) { + if (!link.disabled && link.rel.toLowerCase() == "stylesheet") ++linkStyles; + } + if (fabric.document.styleSheets.length >= styleElements.length + linkStyles) perform(); else setTimeout(arguments.callee, 10); + }); + return function(listener) { + if (complete) listener(); else queue.push(listener); + }; + }(), + supports: function(property, value) { + var checker = fabric.document.createElement("span").style; + if (checker[property] === undefined) return false; + checker[property] = value; + return checker[property] === value; + }, + textAlign: function(word, style, position, wordCount) { + if (style.get("textAlign") == "right") { + if (position > 0) word = " " + word; + } else if (position < wordCount - 1) word += " "; + return word; + }, + textDecoration: function(el, style) { + if (!style) style = this.getStyle(el); + var types = { + underline: null, + overline: null, + "line-through": null + }; + for (var search = el; search.parentNode && search.parentNode.nodeType == 1; ) { + var foundAll = true; + for (var type in types) { + if (types[type]) continue; + if (style.get("textDecoration").indexOf(type) != -1) types[type] = style.get("color"); + foundAll = false; + } + if (foundAll) break; + style = this.getStyle(search = search.parentNode); + } + return types; + }, + textShadow: cached(function(value) { + if (value == "none") return null; + var shadows = [], currentShadow = {}, result, offCount = 0; + var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/gi; + while (result = re.exec(value)) { + if (result[0] == ",") { + shadows.push(currentShadow); + currentShadow = {}, offCount = 0; + } else if (result[1]) { + currentShadow.color = result[1]; + } else { + currentShadow[[ "offX", "offY", "blur" ][offCount++]] = result[2]; + } + } + shadows.push(currentShadow); + return shadows; + }), + color: cached(function(value) { + var parsed = {}; + parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) { + parsed.opacity = parseFloat($2); + return "rgb(" + $1 + ")"; + }); + return parsed; + }), + textTransform: function(text, style) { + return text[{ + uppercase: "toUpperCase", + lowercase: "toLowerCase" + }[style.get("textTransform")] || "toString"](); + } + }; + function Font(data) { + var face = this.face = data.face; + this.glyphs = data.glyphs; + this.w = data.w; + this.baseSize = parseInt(face["units-per-em"], 10); + this.family = face["font-family"].toLowerCase(); + this.weight = face["font-weight"]; + this.style = face["font-style"] || "normal"; + this.viewBox = function() { + var parts = face.bbox.split(/\s+/); + var box = { + minX: parseInt(parts[0], 10), + minY: parseInt(parts[1], 10), + maxX: parseInt(parts[2], 10), + maxY: parseInt(parts[3], 10) + }; + box.width = box.maxX - box.minX, box.height = box.maxY - box.minY; + box.toString = function() { + return [ this.minX, this.minY, this.width, this.height ].join(" "); + }; + return box; + }(); + this.ascent = -parseInt(face.ascent, 10); + this.descent = -parseInt(face.descent, 10); + this.height = -this.ascent + this.descent; + } + function FontFamily() { + var styles = {}, mapping = { + oblique: "italic", + italic: "oblique" + }; + this.add = function(font) { + (styles[font.style] || (styles[font.style] = {}))[font.weight] = font; + }; + this.get = function(style, weight) { + var weights = styles[style] || styles[mapping[style]] || styles.normal || styles.italic || styles.oblique; + if (!weights) return null; + weight = { + normal: 400, + bold: 700 + }[weight] || parseInt(weight, 10); + if (weights[weight]) return weights[weight]; + var up = { + 1: 1, + 99: 0 + }[weight % 100], alts = [], min, max; + if (up === undefined) up = weight > 400; + if (weight == 500) weight = 400; + for (var alt in weights) { + alt = parseInt(alt, 10); + if (!min || alt < min) min = alt; + if (!max || alt > max) max = alt; + alts.push(alt); + } + if (weight < min) weight = min; + if (weight > max) weight = max; + alts.sort(function(a, b) { + return (up ? a > weight && b > weight ? a < b : a > b : a < weight && b < weight ? a > b : a < b) ? -1 : 1; + }); + return weights[alts[0]]; + }; + } + function HoverHandler() { + function contains(node, anotherNode) { + if (node.contains) return node.contains(anotherNode); + return node.compareDocumentPosition(anotherNode) & 16; + } + function onOverOut(e) { + var related = e.relatedTarget; + if (!related || contains(this, related)) return; + trigger(this); + } + function onEnterLeave(e) { + trigger(this); + } + function trigger(el) { + setTimeout(function() { + api.replace(el, sharedStorage.get(el).options, true); + }, 10); + } + this.attach = function(el) { + if (el.onmouseenter === undefined) { + addEvent(el, "mouseover", onOverOut); + addEvent(el, "mouseout", onOverOut); + } else { + addEvent(el, "mouseenter", onEnterLeave); + addEvent(el, "mouseleave", onEnterLeave); + } + }; + } + function Storage() { + var map = {}, at = 0; + function identify(el) { + return el.cufid || (el.cufid = ++at); + } + this.get = function(el) { + var id = identify(el); + return map[id] || (map[id] = {}); + }; + } + function Style(style) { + var custom = {}, sizes = {}; + this.get = function(property) { + return custom[property] != undefined ? custom[property] : style[property]; + }; + this.getSize = function(property, base) { + return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base)); + }; + this.extend = function(styles) { + for (var property in styles) custom[property] = styles[property]; + return this; + }; + } + function addEvent(el, type, listener) { + if (el.addEventListener) { + el.addEventListener(type, listener, false); + } else if (el.attachEvent) { + el.attachEvent("on" + type, function() { + return listener.call(el, fabric.window.event); + }); + } + } + function attach(el, options) { + var storage = sharedStorage.get(el); + if (storage.options) return el; + if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) { + hoverHandler.attach(el); + } + storage.options = options; + return el; + } + function cached(fun) { + var cache = {}; + return function(key) { + if (!cache.hasOwnProperty(key)) cache[key] = fun.apply(null, arguments); + return cache[key]; + }; + } + function getFont(el, style) { + if (!style) style = CSS.getStyle(el); + var families = CSS.quotedList(style.get("fontFamily").toLowerCase()), family; + for (var i = 0, l = families.length; i < l; ++i) { + family = families[i]; + if (fonts[family]) return fonts[family].get(style.get("fontStyle"), style.get("fontWeight")); + } + return null; + } + function elementsByTagName(query) { + return fabric.document.getElementsByTagName(query); + } + function merge() { + var merged = {}, key; + for (var i = 0, l = arguments.length; i < l; ++i) { + for (key in arguments[i]) merged[key] = arguments[i][key]; + } + return merged; + } + function process(font, text, style, options, node, el) { + var separate = options.separate; + if (separate == "none") return engines[options.engine].apply(null, arguments); + var fragment = fabric.document.createDocumentFragment(), processed; + var parts = text.split(separators[separate]), needsAligning = separate == "words"; + if (needsAligning && HAS_BROKEN_REGEXP) { + if (/^\s/.test(text)) parts.unshift(""); + if (/\s$/.test(text)) parts.push(""); + } + for (var i = 0, l = parts.length; i < l; ++i) { + processed = engines[options.engine](font, needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i], style, options, node, el, i < l - 1); + if (processed) fragment.appendChild(processed); + } + return fragment; + } + function replaceElement(el, options) { + var font, style, nextNode, redraw; + for (var node = attach(el, options).firstChild; node; node = nextNode) { + nextNode = node.nextSibling; + redraw = false; + if (node.nodeType == 1) { + if (!node.firstChild) continue; + if (!/cufon/.test(node.className)) { + arguments.callee(node, options); + continue; + } else redraw = true; + } + if (!style) style = CSS.getStyle(el).extend(options); + if (!font) font = getFont(el, style); + if (!font) continue; + if (redraw) { + engines[options.engine](font, null, style, options, node, el); + continue; + } + var text = node.data; + if (typeof G_vmlCanvasManager != "undefined") { + text = text.replace(/\r/g, "\n"); + } + if (text === "") continue; + var processed = process(font, text, style, options, node, el); + if (processed) node.parentNode.replaceChild(processed, node); else node.parentNode.removeChild(node); + } + } + var HAS_BROKEN_REGEXP = " ".split(/\s+/).length == 0; + var sharedStorage = new Storage(); + var hoverHandler = new HoverHandler(); + var replaceHistory = []; + var engines = {}, fonts = {}, defaultOptions = { + engine: null, + hover: false, + hoverables: { + a: true + }, + printable: true, + selector: fabric.window.Sizzle || fabric.window.jQuery && function(query) { + return jQuery(query); + } || fabric.window.dojo && dojo.query || fabric.window.$$ && function(query) { + return $$(query); + } || fabric.window.$ && function(query) { + return $(query); + } || fabric.document.querySelectorAll && function(query) { + return fabric.document.querySelectorAll(query); + } || elementsByTagName, + separate: "words", + textShadow: "none" + }; + var separators = { + words: /\s+/, + characters: "" + }; + api.now = function() { + DOM.ready(); + return api; + }; + api.refresh = function() { + var currentHistory = replaceHistory.splice(0, replaceHistory.length); + for (var i = 0, l = currentHistory.length; i < l; ++i) { + api.replace.apply(null, currentHistory[i]); + } + return api; + }; + api.registerEngine = function(id, engine) { + if (!engine) return api; + engines[id] = engine; + return api.set("engine", id); + }; + api.registerFont = function(data) { + var font = new Font(data), family = font.family; + if (!fonts[family]) fonts[family] = new FontFamily(); + fonts[family].add(font); + return api.set("fontFamily", '"' + family + '"'); + }; + api.replace = function(elements, options, ignoreHistory) { + options = merge(defaultOptions, options); + if (!options.engine) return api; + if (typeof options.textShadow == "string" && options.textShadow) options.textShadow = CSS.textShadow(options.textShadow); + if (!ignoreHistory) replaceHistory.push(arguments); + if (elements.nodeType || typeof elements == "string") elements = [ elements ]; + CSS.ready(function() { + for (var i = 0, l = elements.length; i < l; ++i) { + var el = elements[i]; + if (typeof el == "string") api.replace(options.selector(el), options, true); else replaceElement(el, options); + } + }); + return api; + }; + api.replaceElement = function(el, options) { + options = merge(defaultOptions, options); + if (typeof options.textShadow == "string" && options.textShadow) options.textShadow = CSS.textShadow(options.textShadow); + return replaceElement(el, options); + }; + api.engines = engines; + api.fonts = fonts; + api.getOptions = function() { + return merge(defaultOptions); + }; + api.set = function(option, value) { + defaultOptions[option] = value; + return api; + }; + return api; +}(); - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; +Cufon.registerEngine("canvas", function() { + var HAS_INLINE_BLOCK = Cufon.CSS.supports("display", "inline-block"); + var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (fabric.document.compatMode == "BackCompat" || /frameset|transitional/i.test(fabric.document.doctype.publicId)); + var styleSheet = fabric.document.createElement("style"); + styleSheet.type = "text/css"; + var textNode = fabric.document.createTextNode(".cufon-canvas{text-indent:0}" + "@media screen,projection{" + ".cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle" + (HAS_BROKEN_LINEHEIGHT ? "" : ";font-size:1px;line-height:1px") + "}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}" + (HAS_INLINE_BLOCK ? ".cufon-canvas canvas{position:relative}" : ".cufon-canvas canvas{position:absolute}") + "}" + "@media print{" + ".cufon-canvas{padding:0 !important}" + ".cufon-canvas canvas{display:none}" + ".cufon-canvas .cufon-alt{display:inline}" + "}"); + try { + styleSheet.appendChild(textNode); + } catch (e) { + styleSheet.setAttribute("type", "text/css"); + styleSheet.styleSheet.cssText = textNode.data; + } + fabric.document.getElementsByTagName("head")[0].appendChild(styleSheet); + function generateFromVML(path, context) { + var atX = 0, atY = 0; + var code = [], re = /([mrvxe])([^a-z]*)/g, match; + generate: for (var i = 0; match = re.exec(path); ++i) { + var c = match[2].split(","); + switch (match[1]) { + case "v": + code[i] = { + m: "bezierCurveTo", + a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] }; + break; - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. + case "r": + code[i] = { + m: "lineTo", + a: [ atX += ~~c[0], atY += ~~c[1] ] + }; + break; - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. + case "m": + code[i] = { + m: "moveTo", + a: [ atX = ~~c[0], atY = ~~c[1] ] + }; + break; - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. + case "x": + code[i] = { + m: "closePath", + a: [] + }; + break; - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } + case "e": + break generate; + } + context[code[i].m].apply(context, code[i].a); + } + return code; + } + function interpret(code, context) { + for (var i = 0, l = code.length; i < l; ++i) { + var line = code[i]; + context[line.m].apply(context, line.a); + } + } + return function(font, text, style, options, node, el) { + var redraw = text === null; + var viewBox = font.viewBox; + var size = style.getSize("fontSize", font.baseSize); + var letterSpacing = style.get("letterSpacing"); + letterSpacing = letterSpacing == "normal" ? 0 : size.convertFrom(parseInt(letterSpacing, 10)); + var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0; + var shadows = options.textShadow, shadowOffsets = []; + Cufon.textOptions.shadowOffsets = []; + Cufon.textOptions.shadows = null; + if (shadows) { + Cufon.textOptions.shadows = shadows; + for (var i = 0, l = shadows.length; i < l; ++i) { + var shadow = shadows[i]; + var x = size.convertFrom(parseFloat(shadow.offX)); + var y = size.convertFrom(parseFloat(shadow.offY)); + shadowOffsets[i] = [ x, y ]; + } + } + var chars = Cufon.CSS.textTransform(redraw ? node.alt : text, style).split(""); + var width = 0, lastWidth = null; + var maxWidth = 0, lines = 1, lineWidths = []; + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === "\n") { + lines++; + if (width > maxWidth) { + maxWidth = width; } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; + lineWidths.push(width); + width = 0; + continue; + } + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + width += lastWidth = Number(glyph.w || font.w) + letterSpacing; + } + lineWidths.push(width); + width = Math.max(maxWidth, width); + var lineOffsets = []; + for (var i = lineWidths.length; i--; ) { + lineOffsets[i] = width - lineWidths[i]; + } + if (lastWidth === null) return null; + expandRight += viewBox.width - lastWidth; + expandLeft += viewBox.minX; + var wrapper, canvas; + if (redraw) { + wrapper = node; + canvas = node.firstChild; + } else { + wrapper = fabric.document.createElement("span"); + wrapper.className = "cufon cufon-canvas"; + wrapper.alt = text; + canvas = fabric.document.createElement("canvas"); + wrapper.appendChild(canvas); + if (options.printable) { + var print = fabric.document.createElement("span"); + print.className = "cufon-alt"; + print.appendChild(fabric.document.createTextNode(text)); + wrapper.appendChild(print); + } + } + var wStyle = wrapper.style; + var cStyle = canvas.style || {}; + var height = size.convert(viewBox.height - expandTop + expandBottom); + var roundedHeight = Math.ceil(height); + var roundingFactor = roundedHeight / height; + canvas.width = Math.ceil(size.convert(width + expandRight - expandLeft) * roundingFactor); + canvas.height = roundedHeight; + expandTop += viewBox.minY; + cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + "px"; + cStyle.left = Math.round(size.convert(expandLeft)) + "px"; + var _width = Math.ceil(size.convert(width * roundingFactor)); + var wrapperWidth = _width + "px"; + var _height = size.convert(font.height); + var totalLineHeight = (options.lineHeight - 1) * size.convert(-font.ascent / 5) * (lines - 1); + Cufon.textOptions.width = _width; + Cufon.textOptions.height = _height * lines + totalLineHeight; + Cufon.textOptions.lines = lines; + Cufon.textOptions.totalLineHeight = totalLineHeight; + if (HAS_INLINE_BLOCK) { + wStyle.width = wrapperWidth; + wStyle.height = _height + "px"; + } else { + wStyle.paddingLeft = wrapperWidth; + wStyle.paddingBottom = _height - 1 + "px"; + } + var g = Cufon.textOptions.context || canvas.getContext("2d"), scale = roundedHeight / viewBox.height; + Cufon.textOptions.fontAscent = font.ascent * scale; + Cufon.textOptions.boundaries = null; + for (var offsets = Cufon.textOptions.shadowOffsets, i = shadowOffsets.length; i--; ) { + offsets[i] = [ shadowOffsets[i][0] * scale, shadowOffsets[i][1] * scale ]; + } + g.save(); + g.scale(scale, scale); + g.translate(-expandLeft - 1 / scale * canvas.width / 2 + (Cufon.fonts[font.family].offsetLeft || 0), -expandTop - Cufon.textOptions.height / scale / 2 + (Cufon.fonts[font.family].offsetTop || 0)); + g.lineWidth = font.face["underline-thickness"]; + g.save(); + function line(y, color) { + g.strokeStyle = color; + g.beginPath(); + g.moveTo(0, y); + g.lineTo(width, y); + g.stroke(); + } + var textDecoration = Cufon.getTextDecoration(options), isItalic = options.fontStyle === "italic"; + function renderBackground() { + g.save(); + var left = 0, lineNum = 0, boundaries = [ { + left: 0 + } ]; + if (options.backgroundColor) { + g.save(); + g.fillStyle = options.backgroundColor; + g.translate(0, font.ascent); + g.fillRect(0, 0, width + 10, (-font.ascent + font.descent) * lines); + g.restore(); + } + if (options.textAlign === "right") { + g.translate(lineOffsets[lineNum], 0); + boundaries[0].left = lineOffsets[lineNum] * scale; + } else if (options.textAlign === "center") { + g.translate(lineOffsets[lineNum] / 2, 0); + boundaries[0].left = lineOffsets[lineNum] / 2 * scale; + } + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === "\n") { + lineNum++; + var topOffset = -font.ascent - font.ascent / 5 * options.lineHeight; + var boundary = boundaries[boundaries.length - 1]; + var nextBoundary = { + left: 0 + }; + boundary.width = left * scale; + boundary.height = (-font.ascent + font.descent) * scale; + if (options.textAlign === "right") { + g.translate(-width, topOffset); + g.translate(lineOffsets[lineNum], 0); + nextBoundary.left = lineOffsets[lineNum] * scale; + } else if (options.textAlign === "center") { + g.translate(-left - lineOffsets[lineNum - 1] / 2, topOffset); + g.translate(lineOffsets[lineNum] / 2, 0); + nextBoundary.left = lineOffsets[lineNum] / 2 * scale; + } else { + g.translate(-left, topOffset); } + boundaries.push(nextBoundary); + left = 0; + continue; } - return value; - }); + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + var charWidth = Number(glyph.w || font.w) + letterSpacing; + if (options.textBackgroundColor) { + g.save(); + g.fillStyle = options.textBackgroundColor; + g.translate(0, font.ascent); + g.fillRect(0, 0, charWidth + 10, -font.ascent + font.descent); + g.restore(); + } + g.translate(charWidth, 0); + left += charWidth; + if (i == l - 1) { + boundaries[boundaries.length - 1].width = left * scale; + boundaries[boundaries.length - 1].height = (-font.ascent + font.descent) * scale; + } + } + g.restore(); + Cufon.textOptions.boundaries = boundaries; + } + function renderText(color) { + g.fillStyle = color || Cufon.textOptions.color || style.get("color"); + var left = 0, lineNum = 0; + if (options.textAlign === "right") { + g.translate(lineOffsets[lineNum], 0); + } else if (options.textAlign === "center") { + g.translate(lineOffsets[lineNum] / 2, 0); + } + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === "\n") { + lineNum++; + var topOffset = -font.ascent - font.ascent / 5 * options.lineHeight; + if (options.textAlign === "right") { + g.translate(-width, topOffset); + g.translate(lineOffsets[lineNum], 0); + } else if (options.textAlign === "center") { + g.translate(-left - lineOffsets[lineNum - 1] / 2, topOffset); + g.translate(lineOffsets[lineNum] / 2, 0); + } else { + g.translate(-left, topOffset); + } + left = 0; + continue; + } + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + var charWidth = Number(glyph.w || font.w) + letterSpacing; + if (textDecoration) { + g.save(); + g.strokeStyle = g.fillStyle; + g.lineWidth += g.lineWidth; + g.beginPath(); + if (textDecoration.underline) { + g.moveTo(0, -font.face["underline-position"] + .5); + g.lineTo(charWidth, -font.face["underline-position"] + .5); + } + if (textDecoration.overline) { + g.moveTo(0, font.ascent + .5); + g.lineTo(charWidth, font.ascent + .5); + } + if (textDecoration["line-through"]) { + g.moveTo(0, -font.descent + .5); + g.lineTo(charWidth, -font.descent + .5); + } + g.stroke(); + g.restore(); + } + if (isItalic) { + g.save(); + g.transform(1, 0, -.25, 1, 0, 0); + } + g.beginPath(); + if (glyph.d) { + if (glyph.code) interpret(glyph.code, g); else glyph.code = generateFromVML("m" + glyph.d, g); + } + g.fill(); + if (options.strokeStyle) { + g.closePath(); + g.save(); + g.lineWidth = options.strokeWidth; + g.strokeStyle = options.strokeStyle; + g.stroke(); + g.restore(); + } + if (isItalic) { + g.restore(); + } + g.translate(charWidth, 0); + left += charWidth; + } + } + g.save(); + renderBackground(); + if (shadows) { + for (var i = 0, l = shadows.length; i < l; ++i) { + var shadow = shadows[i]; + g.save(); + g.translate.apply(g, shadowOffsets[i]); + renderText(shadow.color); + g.restore(); + } + } + renderText(); + g.restore(); + g.restore(); + g.restore(); + return wrapper; + }; +}()); +Cufon.registerEngine("vml", function() { + if (!fabric.document.namespaces) return; + var canvasEl = fabric.document.createElement("canvas"); + if (canvasEl && canvasEl.getContext && canvasEl.getContext.apply) return; + if (fabric.document.namespaces.cvml == null) { + fabric.document.namespaces.add("cvml", "urn:schemas-microsoft-com:vml"); + } + var check = fabric.document.createElement("cvml:shape"); + check.style.behavior = "url(#default#VML)"; + if (!check.coordsize) return; + check = null; + fabric.document.write('"); + function getFontSizeInPixels(el, value) { + return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? "1em" : value); + } + function getSizeInPixels(el, value) { + if (/px$/i.test(value)) return parseFloat(value); + var style = el.style.left, runtimeStyle = el.runtimeStyle.left; + el.runtimeStyle.left = el.currentStyle.left; + el.style.left = value; + var result = el.style.pixelLeft; + el.style.left = style; + el.runtimeStyle.left = runtimeStyle; + return result; + } + return function(font, text, style, options, node, el, hasNext) { + var redraw = text === null; + if (redraw) text = node.alt; + var viewBox = font.viewBox; + var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get("fontSize")) + "px", font.baseSize)); + var letterSpacing = style.computedLSpacing; + if (letterSpacing == undefined) { + letterSpacing = style.get("letterSpacing"); + style.computedLSpacing = letterSpacing = letterSpacing == "normal" ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing)); + } + var wrapper, canvas; + if (redraw) { + wrapper = node; + canvas = node.firstChild; + } else { + wrapper = fabric.document.createElement("span"); + wrapper.className = "cufon cufon-vml"; + wrapper.alt = text; + canvas = fabric.document.createElement("span"); + canvas.className = "cufon-vml-canvas"; + wrapper.appendChild(canvas); + if (options.printable) { + var print = fabric.document.createElement("span"); + print.className = "cufon-alt"; + print.appendChild(fabric.document.createTextNode(text)); + wrapper.appendChild(print); + } + if (!hasNext) wrapper.appendChild(fabric.document.createElement("cvml:shape")); + } + var wStyle = wrapper.style; + var cStyle = canvas.style; + var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height); + var roundingFactor = roundedHeight / height; + var minX = viewBox.minX, minY = viewBox.minY; + cStyle.height = roundedHeight; + cStyle.top = Math.round(size.convert(minY - font.ascent)); + cStyle.left = Math.round(size.convert(minX)); + wStyle.height = size.convert(font.height) + "px"; + var textDecoration = Cufon.getTextDecoration(options); + var color = style.get("color"); + var chars = Cufon.CSS.textTransform(text, style).split(""); + var width = 0, offsetX = 0, advance = null; + var glyph, shape, shadows = options.textShadow; + for (var i = 0, k = 0, l = chars.length; i < l; ++i) { + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing; + } + if (advance === null) return null; + var fullWidth = -minX + width + (viewBox.width - advance); + var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth); + var coordSize = fullWidth + "," + viewBox.height, coordOrigin; + var stretch = "r" + coordSize + "nsnf"; + for (i = 0; i < l; ++i) { + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + if (redraw) { + shape = canvas.childNodes[k]; + if (shape.firstChild) shape.removeChild(shape.firstChild); + } else { + shape = fabric.document.createElement("cvml:shape"); + canvas.appendChild(shape); + } + shape.stroked = "f"; + shape.coordsize = coordSize; + shape.coordorigin = coordOrigin = minX - offsetX + "," + minY; + shape.path = (glyph.d ? "m" + glyph.d + "xe" : "") + "m" + coordOrigin + stretch; + shape.fillcolor = color; + var sStyle = shape.style; + sStyle.width = roundedShapeWidth; + sStyle.height = roundedHeight; + if (shadows) { + var shadow1 = shadows[0], shadow2 = shadows[1]; + var color1 = Cufon.CSS.color(shadow1.color), color2; + var shadow = fabric.document.createElement("cvml:shadow"); + shadow.on = "t"; + shadow.color = color1.color; + shadow.offset = shadow1.offX + "," + shadow1.offY; + if (shadow2) { + color2 = Cufon.CSS.color(shadow2.color); + shadow.type = "double"; + shadow.color2 = color2.color; + shadow.offset2 = shadow2.offX + "," + shadow2.offY; + } + shadow.opacity = color1.opacity || color2 && color2.opacity || 1; + shape.appendChild(shadow); + } + offsetX += ~~(glyph.w || font.w) + letterSpacing; + ++k; + } + wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0); + return wrapper; + }; +}()); - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ +Cufon.getTextDecoration = function(options) { + return { + underline: options.textDecoration === "underline", + overline: options.textDecoration === "overline", + "line-through": options.textDecoration === "line-through" + }; +}; -/*jslint evil: true, regexp: true */ +if (typeof exports != "undefined") { + exports.Cufon = Cufon; +} -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (typeof JSON !== 'object') { +if (typeof JSON !== "object") { JSON = {}; } -(function () { - 'use strict'; - +(function() { + "use strict"; function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; + return n < 10 ? "0" + n : n; } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function () { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; + if (typeof Date.prototype.toJSON !== "function") { + Date.prototype.toJSON = function() { + return isFinite(this.valueOf()) ? this.getUTCFullYear() + "-" + f(this.getUTCMonth() + 1) + "-" + f(this.getUTCDate()) + "T" + f(this.getUTCHours()) + ":" + f(this.getUTCMinutes()) + ":" + f(this.getUTCSeconds()) + "Z" : null; + }; + String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function() { + return this.valueOf(); }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function () { - return this.valueOf(); - }; } - - var cx, - escapable, - gap, - indent, - meta, - rep; - - + var cx, escapable, gap, indent, meta, rep; function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + return escapable.test(string) ? '"' + string.replace(escapable, function(a) { var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + return typeof c === "string" ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { + var i, k, v, length, mind = gap, partial, value = holder[key]; + if (value && typeof value === "object" && typeof value.toJSON === "function") { value = value.toJSON(key); } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { + if (typeof rep === "function") { value = rep.call(holder, key, value); } - -// What happens next depends on the value's type. - switch (typeof value) { - case 'string': + case "string": return quote(value); - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. + case "number": + return isFinite(value) ? String(value) : "null"; + case "boolean": + case "null": return String(value); -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - + case "object": if (!value) { - return 'null'; + return "null"; } - -// Make an array to hold the partial results of stringifying this object value. - gap += indent; partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - + if (Object.prototype.toString.apply(value) === "[object Array]") { length = value.length; for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; + partial[i] = str(i, value) || "null"; } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + v = partial.length === 0 ? "[]" : gap ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" : "[" + partial.join(",") + "]"; gap = mind; return v; } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { + if (rep && typeof rep === "object") { length = rep.length; for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { + if (typeof rep[i] === "string") { k = rep[i]; v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + (gap ? ": " : ":") + v); } } } } else { - -// Otherwise, iterate through all of the keys in the object. - for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + (gap ? ": " : ":") + v); } } } } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + v = partial.length === 0 ? "{}" : gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" : "{" + partial.join(",") + "}"; gap = mind; return v; } } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { + if (typeof JSON.stringify !== "function") { escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' + meta = { + "\b": "\\b", + " ": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + '"': '\\"', + "\\": "\\\\" }; - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - + JSON.stringify = function(value, replacer, space) { var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { + gap = ""; + indent = ""; + if (typeof space === "number") { for (i = 0; i < space; i += 1) { - indent += ' '; + indent += " "; } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { + } else if (typeof space === "string") { indent = space; } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); + if (replacer && typeof replacer !== "function" && (typeof replacer !== "object" || typeof replacer.length !== "number")) { + throw new Error("JSON.stringify"); } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); + return str("", { + "": value + }); }; } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { + if (typeof JSON.parse !== "function") { cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - + JSON.parse = function(text, reviver) { var j; - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { + if (value && typeof value === "object") { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); @@ -1710,16566 +1029,8343 @@ if (typeof JSON !== 'object') { } return reviver.call(holder, key, value); } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - text = String(text); cx.lastIndex = 0; if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + text = text.replace(cx, function(a) { + return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); }); } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; + if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, ""))) { + j = eval("(" + text + ")"); + return typeof reviver === "function" ? walk({ + "": j + }, "") : j; } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); + throw new SyntaxError("JSON.parse"); }; } -}()); - - -/* - ---------------------------------------------------- - Event.js : 1.1.3 : 2013/07/17 : MIT License - ---------------------------------------------------- - https://github.com/mudcube/Event.js - ---------------------------------------------------- - https://github.com/rykerwilliams/Event.js - ---------------------------------------------------- - 1 : click, dblclick, dbltap - 1+ : tap, longpress, drag, swipe - 2+ : pinch, rotate - : mousewheel, devicemotion, shake - ---------------------------------------------------- - Ideas for the future - ---------------------------------------------------- - * GamePad, and other input abstractions. - * Event batching - i.e. for every x fingers down a new gesture is created. - ---------------------------------------------------- - http://www.w3.org/TR/2011/WD-touch-events-20110505/ - ---------------------------------------------------- - -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(eventjs) === "undefined") var eventjs = Event; - -(function(root) { "use strict"; - -// Add custom *EventListener commands to HTMLElements (set false to prevent funkiness). -root.modifyEventListener = false; - -// Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness). -root.modifySelectors = false; - -// Event maintenance. -root.add = function(target, type, listener, configure) { - return eventManager(target, type, listener, configure, "add"); -}; - -root.remove = function(target, type, listener, configure) { - return eventManager(target, type, listener, configure, "remove"); -}; - -root.stop = function(event) { - if (!event) return; - if (event.stopPropagation) event.stopPropagation(); - event.cancelBubble = true; // <= IE8 - event.bubble = 0; -}; - -root.prevent = function(event) { - if (!event) return; - if (event.preventDefault) event.preventDefault(); - if (event.preventManipulation) event.preventManipulation(); // MS - event.returnValue = false; // <= IE8 -}; - -root.cancel = function(event) { - root.stop(event); - root.prevent(event); -}; - -// Check whether event is natively supported (via @kangax) -root.getEventSupport = function (target, type) { - if (typeof(target) === "string") { - type = target; - target = window; - } - type = "on" + type; - if (type in target) return true; - if (!target.setAttribute) target = document.createElement("div"); - if (target.setAttribute && target.removeAttribute) { - target.setAttribute(type, ""); - var isSupported = typeof target[type] === "function"; - if (typeof target[type] !== "undefined") target[type] = null; - target.removeAttribute(type); - return isSupported; - } -}; - -var clone = function (obj) { - if (!obj || typeof (obj) !== 'object') return obj; - var temp = new obj.constructor(); - for (var key in obj) { - if (!obj[key] || typeof (obj[key]) !== 'object') { - temp[key] = obj[key]; - } else { // clone sub-object - temp[key] = clone(obj[key]); - } - } - return temp; -}; - -/// Handle custom *EventListener commands. -var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { - configure = configure || {}; - // Check whether target is a configuration variable; - if (String(target) === "[object Object]") { - var data = target; - target = data.target; - type = data.type; - listener = data.listener; - delete data.target; - delete data.type; - delete data.listener; - for (var key in data) { - configure[key] = data[key]; - } - } - /// - if (!target || !type || !listener) return; - // Check for element to load on interval (before onload). - if (typeof(target) === "string" && type === "ready") { - var time = (new Date()).getTime(); - var timeout = configure.timeout; - var ms = configure.interval || 1000 / 60; - var interval = window.setInterval(function() { - if ((new Date()).getTime() - time > timeout) { - window.clearInterval(interval); - } - if (document.querySelector(target)) { - window.clearInterval(interval); - setTimeout(listener, 1); - } - }, ms); - return; - } - // Get DOM element from Query Selector. - if (typeof(target) === "string") { - target = document.querySelectorAll(target); - if (target.length === 0) return createError("Missing target on listener!", arguments); // No results. - if (target.length === 1) { // Single target. - target = target[0]; - } - } - - /// Handle multiple targets. - var event; - var events = {}; - if (target.length > 0 && target !== window) { - for (var n0 = 0, length0 = target.length; n0 < length0; n0 ++) { - event = eventManager(target[n0], type, listener, clone(configure), trigger); - if (event) events[n0] = event; - } - return createBatchCommands(events); - } - // Check for multiple events in one string. - if (type.indexOf && type.indexOf(" ") !== -1) type = type.split(" "); - if (type.indexOf && type.indexOf(",") !== -1) type = type.split(","); - // Attach or remove multiple events associated with a target. - if (typeof(type) !== "string") { // Has multiple events. - if (typeof(type.length) === "number") { // Handle multiple listeners glued together. - for (var n1 = 0, length1 = type.length; n1 < length1; n1 ++) { // Array [type] - event = eventManager(target, type[n1], listener, clone(configure), trigger); - if (event) events[type[n1]] = event; - } - } else { // Handle multiple listeners. - for (var key in type) { // Object {type} - if (typeof(type[key]) === "function") { // without configuration. - event = eventManager(target, key, type[key], clone(configure), trigger); - } else { // with configuration. - event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); - } - if (event) events[key] = event; - } - } - return createBatchCommands(events); - } - // Ensure listener is a function. - if (typeof(target) !== "object") return createError("Target is not defined!", arguments); - if (typeof(listener) !== "function") return createError("Listener is not a function!", arguments); - // Generate a unique wrapper identifier. - var useCapture = configure.useCapture || false; - var id = getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); - // Handle the event. - if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event. - id = type + id; - if (trigger === "remove") { // Remove event listener. - if (!wrappers[id]) return; // Already removed. - wrappers[id].remove(); - delete wrappers[id]; - } else if (trigger === "add") { // Attach event listener. - if (wrappers[id]) { - wrappers[id].add(); - return wrappers[id]; // Already attached. - } - // Retains "this" orientation. - if (configure.useCall && !root.modifyEventListener) { - var tmp = listener; - listener = function(event, self) { - for (var key in self) event[key] = self[key]; - return tmp.call(target, event); - }; - } - // Create listener proxy. - configure.gesture = type; - configure.target = target; - configure.listener = listener; - configure.fromOverwrite = fromOverwrite; - // Record wrapper. - wrappers[id] = root.proxy[type](configure); - } - return wrappers[id]; - } else { // Fire native event. - var eventList = getEventList(type); - for (var n = 0, eventId; n < eventList.length; n ++) { - type = eventList[n]; - eventId = type + "." + id; - if (trigger === "remove") { // Remove event listener. - if (!wrappers[eventId]) continue; // Already removed. - target[remove](type, listener, useCapture); - delete wrappers[eventId]; - } else if (trigger === "add") { // Attach event listener. - if (wrappers[eventId]) return wrappers[eventId]; // Already attached. - target[add](type, listener, useCapture); - // Record wrapper. - wrappers[eventId] = { - id: eventId, - type: type, - target: target, - listener: listener, - remove: function() { - for (var n = 0; n < eventList.length; n ++) { - root.remove(target, eventList[n], listener, configure); - } - } - }; - } - } - return wrappers[eventId]; - } -}; - -/// Perform batch actions on multiple events. -var createBatchCommands = function(events) { - return { - remove: function() { // Remove multiple events. - for (var key in events) { - events[key].remove(); - } - }, - add: function() { // Add multiple events. - for (var key in events) { - events[key].add(); - } - } - }; -}; - -/// Display error message in console. -var createError = function(message, data) { - if (typeof(console) === "undefined") return; - if (typeof(console.error) === "undefined") return; - console.error(message, data); -}; - -/// Handle naming discrepancies between platforms. -var pointerDefs = { - "msPointer": [ "MSPointerDown", "MSPointerMove", "MSPointerUp" ], - "touch": [ "touchstart", "touchmove", "touchend" ], - "mouse": [ "mousedown", "mousemove", "mouseup" ] -}; - -var pointerDetect = { - // MSPointer - "MSPointerDown": 0, - "MSPointerMove": 1, - "MSPointerUp": 2, - // Touch - "touchstart": 0, - "touchmove": 1, - "touchend": 2, - // Mouse - "mousedown": 0, - "mousemove": 1, - "mouseup": 2 -}; - -var getEventSupport = (function() { - root.supports = {}; - if (window.navigator.msPointerEnabled) { - root.supports.msPointer = true; - } - if (root.getEventSupport("touchstart")) { - root.supports.touch = true; - } - if (root.getEventSupport("mousedown")) { - root.supports.mouse = true; - } })(); -var getEventList = (function() { - return function(type) { - var prefix = document.addEventListener ? "" : "on"; // IE - var idx = pointerDetect[type]; - if (isFinite(idx)) { - var types = []; - for (var key in root.supports) { - types.push(prefix + pointerDefs[key][idx]); - } - return types; - } else { - return [ prefix + type ]; - } - }; -})(); +if (typeof Event === "undefined") var Event = {}; -/// Event wrappers to keep track of all events placed in the window. -var wrappers = {}; -var counter = 0; -var getID = function(object) { - if (object === window) return "#window"; - if (object === document) return "#document"; - if (!object.uniqueID) object.uniqueID = "e" + counter ++; - return object.uniqueID; -}; - -/// Detect platforms native *EventListener command. -var add = document.addEventListener ? "addEventListener" : "attachEvent"; -var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; - -/* - Pointer.js - ------------------------ - Modified from; https://github.com/borismus/pointer.js -*/ - -root.createPointerEvent = function (event, self, preventRecord) { - var eventName = self.gesture; - var target = self.target; - var pts = event.changedTouches || root.proxy.getCoords(event); - if (pts.length) { - var pt = pts[0]; - self.pointers = preventRecord ? [] : pts; - self.pageX = pt.pageX; - self.pageY = pt.pageY; - self.x = self.pageX; - self.y = self.pageY; - } - /// - var newEvent = document.createEvent("Event"); - newEvent.initEvent(eventName, true, true); - newEvent.originalEvent = event; - for (var k in self) { - if (k === "target") continue; - newEvent[k] = self[k]; - } - /// - var type = newEvent.type; - if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. -// target.dispatchEvent(newEvent); - self.oldListener.call(target, newEvent, self, false); - } -}; - -/// Allows *EventListener to use custom event proxies. -if (root.modifyEventListener && window.HTMLElement) (function() { - var augmentEventListener = function(proto) { - var recall = function(trigger) { // overwrite native *EventListener's - var handle = trigger + "EventListener"; - var handler = proto[handle]; - proto[handle] = function (type, listener, useCapture) { - if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events. - var configure = useCapture; - if (typeof(useCapture) === "object") { - configure.useCall = true; - } else { // convert to configuration object. - configure = { - useCall: true, - useCapture: useCapture - }; - } - eventManager(this, type, listener, configure, trigger, true); -// handler.call(this, type, listener, useCapture); - } else { // use native function. - var types = getEventList(type); - for (var n = 0; n < types.length; n ++) { - handler.call(this, types[n], listener, useCapture); - } - } - }; - }; - recall("add"); - recall("remove"); - }; - // NOTE: overwriting HTMLElement doesn't do anything in Firefox. - if (navigator.userAgent.match(/Firefox/)) { - // TODO: fix Firefox for the general case. - augmentEventListener(HTMLDivElement.prototype); - augmentEventListener(HTMLCanvasElement.prototype); - } else { - augmentEventListener(HTMLElement.prototype); - } - augmentEventListener(document); - augmentEventListener(window); -})(); - -/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk. -if (root.modifySelectors) (function() { - var proto = NodeList.prototype; - proto.removeEventListener = function(type, listener, useCapture) { - for (var n = 0, length = this.length; n < length; n ++) { - this[n].removeEventListener(type, listener, useCapture); - } - }; - proto.addEventListener = function(type, listener, useCapture) { - for (var n = 0, length = this.length; n < length; n ++) { - this[n].addEventListener(type, listener, useCapture); - } - }; -})(); - -return root; +if (typeof eventjs === "undefined") var eventjs = Event; +(function(root) { + "use strict"; + root.modifyEventListener = false; + root.modifySelectors = false; + root.add = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "add"); + }; + root.remove = function(target, type, listener, configure) { + return eventManager(target, type, listener, configure, "remove"); + }; + root.stop = function(event) { + if (!event) return; + if (event.stopPropagation) event.stopPropagation(); + event.cancelBubble = true; + event.bubble = 0; + }; + root.prevent = function(event) { + if (!event) return; + if (event.preventDefault) event.preventDefault(); + if (event.preventManipulation) event.preventManipulation(); + event.returnValue = false; + }; + root.cancel = function(event) { + root.stop(event); + root.prevent(event); + }; + root.getEventSupport = function(target, type) { + if (typeof target === "string") { + type = target; + target = window; + } + type = "on" + type; + if (type in target) return true; + if (!target.setAttribute) target = document.createElement("div"); + if (target.setAttribute && target.removeAttribute) { + target.setAttribute(type, ""); + var isSupported = typeof target[type] === "function"; + if (typeof target[type] !== "undefined") target[type] = null; + target.removeAttribute(type); + return isSupported; + } + }; + var clone = function(obj) { + if (!obj || typeof obj !== "object") return obj; + var temp = new obj.constructor(); + for (var key in obj) { + if (!obj[key] || typeof obj[key] !== "object") { + temp[key] = obj[key]; + } else { + temp[key] = clone(obj[key]); + } + } + return temp; + }; + var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) { + configure = configure || {}; + if (String(target) === "[object Object]") { + var data = target; + target = data.target; + type = data.type; + listener = data.listener; + delete data.target; + delete data.type; + delete data.listener; + for (var key in data) { + configure[key] = data[key]; + } + } + if (!target || !type || !listener) return; + if (typeof target === "string" && type === "ready") { + var time = new Date().getTime(); + var timeout = configure.timeout; + var ms = configure.interval || 1e3 / 60; + var interval = window.setInterval(function() { + if (new Date().getTime() - time > timeout) { + window.clearInterval(interval); + } + if (document.querySelector(target)) { + window.clearInterval(interval); + setTimeout(listener, 1); + } + }, ms); + return; + } + if (typeof target === "string") { + target = document.querySelectorAll(target); + if (target.length === 0) return createError("Missing target on listener!", arguments); + if (target.length === 1) { + target = target[0]; + } + } + var event; + var events = {}; + if (target.length > 0 && target !== window) { + for (var n0 = 0, length0 = target.length; n0 < length0; n0++) { + event = eventManager(target[n0], type, listener, clone(configure), trigger); + if (event) events[n0] = event; + } + return createBatchCommands(events); + } + if (type.indexOf && type.indexOf(" ") !== -1) type = type.split(" "); + if (type.indexOf && type.indexOf(",") !== -1) type = type.split(","); + if (typeof type !== "string") { + if (typeof type.length === "number") { + for (var n1 = 0, length1 = type.length; n1 < length1; n1++) { + event = eventManager(target, type[n1], listener, clone(configure), trigger); + if (event) events[type[n1]] = event; + } + } else { + for (var key in type) { + if (typeof type[key] === "function") { + event = eventManager(target, key, type[key], clone(configure), trigger); + } else { + event = eventManager(target, key, type[key].listener, clone(type[key]), trigger); + } + if (event) events[key] = event; + } + } + return createBatchCommands(events); + } + if (typeof target !== "object") return createError("Target is not defined!", arguments); + if (typeof listener !== "function") return createError("Listener is not a function!", arguments); + var useCapture = configure.useCapture || false; + var id = getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0); + if (root.Gesture && root.Gesture._gestureHandlers[type]) { + id = type + id; + if (trigger === "remove") { + if (!wrappers[id]) return; + wrappers[id].remove(); + delete wrappers[id]; + } else if (trigger === "add") { + if (wrappers[id]) { + wrappers[id].add(); + return wrappers[id]; + } + if (configure.useCall && !root.modifyEventListener) { + var tmp = listener; + listener = function(event, self) { + for (var key in self) event[key] = self[key]; + return tmp.call(target, event); + }; + } + configure.gesture = type; + configure.target = target; + configure.listener = listener; + configure.fromOverwrite = fromOverwrite; + wrappers[id] = root.proxy[type](configure); + } + return wrappers[id]; + } else { + var eventList = getEventList(type); + for (var n = 0, eventId; n < eventList.length; n++) { + type = eventList[n]; + eventId = type + "." + id; + if (trigger === "remove") { + if (!wrappers[eventId]) continue; + target[remove](type, listener, useCapture); + delete wrappers[eventId]; + } else if (trigger === "add") { + if (wrappers[eventId]) return wrappers[eventId]; + target[add](type, listener, useCapture); + wrappers[eventId] = { + id: eventId, + type: type, + target: target, + listener: listener, + remove: function() { + for (var n = 0; n < eventList.length; n++) { + root.remove(target, eventList[n], listener, configure); + } + } + }; + } + } + return wrappers[eventId]; + } + }; + var createBatchCommands = function(events) { + return { + remove: function() { + for (var key in events) { + events[key].remove(); + } + }, + add: function() { + for (var key in events) { + events[key].add(); + } + } + }; + }; + var createError = function(message, data) { + if (typeof console === "undefined") return; + if (typeof console.error === "undefined") return; + console.error(message, data); + }; + var pointerDefs = { + msPointer: [ "MSPointerDown", "MSPointerMove", "MSPointerUp" ], + touch: [ "touchstart", "touchmove", "touchend" ], + mouse: [ "mousedown", "mousemove", "mouseup" ] + }; + var pointerDetect = { + MSPointerDown: 0, + MSPointerMove: 1, + MSPointerUp: 2, + touchstart: 0, + touchmove: 1, + touchend: 2, + mousedown: 0, + mousemove: 1, + mouseup: 2 + }; + var getEventSupport = function() { + root.supports = {}; + if (window.navigator.msPointerEnabled) { + root.supports.msPointer = true; + } + if (root.getEventSupport("touchstart")) { + root.supports.touch = true; + } + if (root.getEventSupport("mousedown")) { + root.supports.mouse = true; + } + }(); + var getEventList = function() { + return function(type) { + var prefix = document.addEventListener ? "" : "on"; + var idx = pointerDetect[type]; + if (isFinite(idx)) { + var types = []; + for (var key in root.supports) { + types.push(prefix + pointerDefs[key][idx]); + } + return types; + } else { + return [ prefix + type ]; + } + }; + }(); + var wrappers = {}; + var counter = 0; + var getID = function(object) { + if (object === window) return "#window"; + if (object === document) return "#document"; + if (!object.uniqueID) object.uniqueID = "e" + counter++; + return object.uniqueID; + }; + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + root.createPointerEvent = function(event, self, preventRecord) { + var eventName = self.gesture; + var target = self.target; + var pts = event.changedTouches || root.proxy.getCoords(event); + if (pts.length) { + var pt = pts[0]; + self.pointers = preventRecord ? [] : pts; + self.pageX = pt.pageX; + self.pageY = pt.pageY; + self.x = self.pageX; + self.y = self.pageY; + } + var newEvent = document.createEvent("Event"); + newEvent.initEvent(eventName, true, true); + newEvent.originalEvent = event; + for (var k in self) { + if (k === "target") continue; + newEvent[k] = self[k]; + } + var type = newEvent.type; + if (root.Gesture && root.Gesture._gestureHandlers[type]) { + self.oldListener.call(target, newEvent, self, false); + } + }; + if (root.modifyEventListener && window.HTMLElement) (function() { + var augmentEventListener = function(proto) { + var recall = function(trigger) { + var handle = trigger + "EventListener"; + var handler = proto[handle]; + proto[handle] = function(type, listener, useCapture) { + if (root.Gesture && root.Gesture._gestureHandlers[type]) { + var configure = useCapture; + if (typeof useCapture === "object") { + configure.useCall = true; + } else { + configure = { + useCall: true, + useCapture: useCapture + }; + } + eventManager(this, type, listener, configure, trigger, true); + } else { + var types = getEventList(type); + for (var n = 0; n < types.length; n++) { + handler.call(this, types[n], listener, useCapture); + } + } + }; + }; + recall("add"); + recall("remove"); + }; + if (navigator.userAgent.match(/Firefox/)) { + augmentEventListener(HTMLDivElement.prototype); + augmentEventListener(HTMLCanvasElement.prototype); + } else { + augmentEventListener(HTMLElement.prototype); + } + augmentEventListener(document); + augmentEventListener(window); + })(); + if (root.modifySelectors) (function() { + var proto = NodeList.prototype; + proto.removeEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].removeEventListener(type, listener, useCapture); + } + }; + proto.addEventListener = function(type, listener, useCapture) { + for (var n = 0, length = this.length; n < length; n++) { + this[n].addEventListener(type, listener, useCapture); + } + }; + })(); + return root; })(Event); -/* - ---------------------------------------------------- - Event.proxy : 0.4.3 : 2013/07/17 : MIT License - ---------------------------------------------------- - https://github.com/mudcube/Event.js - ---------------------------------------------------- -*/ -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; +if (typeof Event === "undefined") var Event = {}; -Event.proxy = (function(root) { "use strict"; +if (typeof Event.proxy === "undefined") Event.proxy = {}; -/* - Create a new pointer gesture instance. -*/ +Event.proxy = function(root) { + "use strict"; + root.pointerSetup = function(conf, self) { + conf.doc = conf.target.ownerDocument || conf.target; + conf.minFingers = conf.minFingers || conf.fingers || 1; + conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; + conf.position = conf.position || "relative"; + delete conf.fingers; + self = self || {}; + self.enabled = true; + self.gesture = conf.gesture; + self.target = conf.target; + self.env = conf.env; + if (Event.modifyEventListener && conf.fromOverwrite) { + conf.oldListener = conf.listener; + conf.listener = Event.createPointerEvent; + } + var fingers = 0; + var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; + if (conf.oldListener) self.oldListener = conf.oldListener; + self.listener = conf.listener; + self.proxy = function(listener) { + self.defaultListener = conf.listener; + conf.listener = listener; + listener(conf.event, self); + }; + self.add = function() { + if (self.enabled === true) return; + if (conf.onPointerDown) Event.add(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) Event.add(conf.doc, type + "up", conf.onPointerUp); + self.enabled = true; + }; + self.remove = function() { + if (self.enabled === false) return; + if (conf.onPointerDown) Event.remove(conf.target, type + "down", conf.onPointerDown); + if (conf.onPointerMove) Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp) Event.remove(conf.doc, type + "up", conf.onPointerUp); + self.reset(); + self.enabled = false; + }; + self.pause = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) Event.remove(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) Event.remove(conf.doc, type + "up", conf.onPointerUp); + fingers = conf.fingers; + conf.fingers = 0; + }; + self.resume = function(opt) { + if (conf.onPointerMove && (!opt || opt.move)) Event.add(conf.doc, type + "move", conf.onPointerMove); + if (conf.onPointerUp && (!opt || opt.up)) Event.add(conf.doc, type + "up", conf.onPointerUp); + conf.fingers = fingers; + }; + self.reset = function() { + conf.tracker = {}; + conf.fingers = 0; + }; + return self; + }; + var sp = Event.supports; + Event.pointerType = sp.mouse ? "mouse" : sp.touch ? "touch" : "mspointer"; + root.pointerStart = function(event, self, conf) { + var type = (event.type || "mousedown").toUpperCase(); + if (type.indexOf("MOUSE") === 0) Event.pointerType = "mouse"; else if (type.indexOf("TOUCH") === 0) Event.pointerType = "touch"; else if (type.indexOf("MSPOINTER") === 0) Event.pointerType = "mspointer"; + var addTouchStart = function(touch, sid) { + var bbox = conf.bbox; + var pt = track[sid] = {}; + switch (conf.position) { + case "absolute": + pt.offsetX = 0; + pt.offsetY = 0; + break; -root.pointerSetup = function(conf, self) { - /// Configure. - conf.doc = conf.target.ownerDocument || conf.target; // Associated document. - conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers. - conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers. - conf.position = conf.position || "relative"; // Determines what coordinate system points are returned. - delete conf.fingers; //- - /// Convenience data. - self = self || {}; - self.enabled = true; - self.gesture = conf.gesture; - self.target = conf.target; - self.env = conf.env; - /// - if (Event.modifyEventListener && conf.fromOverwrite) { - conf.oldListener = conf.listener; - conf.listener = Event.createPointerEvent; - } - /// Convenience commands. - var fingers = 0; - var type = self.gesture.indexOf("pointer") === 0 && Event.modifyEventListener ? "pointer" : "mouse"; - if (conf.oldListener) self.oldListener = conf.oldListener; - self.listener = conf.listener; - self.proxy = function(listener) { - self.defaultListener = conf.listener; - conf.listener = listener; - listener(conf.event, self); - }; - self.add = function() { - if (self.enabled === true) return; - if (conf.onPointerDown) Event.add(conf.target, type + "down", conf.onPointerDown); - if (conf.onPointerMove) Event.add(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp) Event.add(conf.doc, type + "up", conf.onPointerUp); - self.enabled = true; - }; - self.remove = function() { - if (self.enabled === false) return; - if (conf.onPointerDown) Event.remove(conf.target, type + "down", conf.onPointerDown); - if (conf.onPointerMove) Event.remove(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp) Event.remove(conf.doc, type + "up", conf.onPointerUp); - self.reset(); - self.enabled = false; - }; - self.pause = function(opt) { - if (conf.onPointerMove && (!opt || opt.move)) Event.remove(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp && (!opt || opt.up)) Event.remove(conf.doc, type + "up", conf.onPointerUp); - fingers = conf.fingers; - conf.fingers = 0; - }; - self.resume = function(opt) { - if (conf.onPointerMove && (!opt || opt.move)) Event.add(conf.doc, type + "move", conf.onPointerMove); - if (conf.onPointerUp && (!opt || opt.up)) Event.add(conf.doc, type + "up", conf.onPointerUp); - conf.fingers = fingers; - }; - self.reset = function() { - conf.tracker = {}; - conf.fingers = 0; - }; - /// - return self; -}; + case "differenceFromLast": + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; -/* - Begin proxied pointer command. -*/ + case "difference": + pt.offsetX = touch.pageX; + pt.offsetY = touch.pageY; + break; -var sp = Event.supports; -Event.pointerType = sp.mouse ? "mouse" : sp.touch ? "touch" : "mspointer"; -root.pointerStart = function(event, self, conf) { - var type = (event.type || "mousedown").toUpperCase(); - if (type.indexOf("MOUSE") === 0) Event.pointerType = "mouse"; - else if (type.indexOf("TOUCH") === 0) Event.pointerType = "touch"; - else if (type.indexOf("MSPOINTER") === 0) Event.pointerType = "mspointer"; - /// - var addTouchStart = function(touch, sid) { - var bbox = conf.bbox; - var pt = track[sid] = {}; - /// - switch(conf.position) { - case "absolute": // Absolute from within window. - pt.offsetX = 0; - pt.offsetY = 0; - break; - case "differenceFromLast": // Since last coordinate recorded. - pt.offsetX = touch.pageX; - pt.offsetY = touch.pageY; - break; - case "difference": // Relative from origin. - pt.offsetX = touch.pageX; - pt.offsetY = touch.pageY; - break; - case "move": // Move target element. - pt.offsetX = touch.pageX - bbox.x1; - pt.offsetY = touch.pageY - bbox.y1; - break; - default: // Relative from within target. - pt.offsetX = bbox.x1; - pt.offsetY = bbox.y1; - break; - } - /// - if (conf.position === "relative") { - var x = (touch.pageX + bbox.scrollLeft - pt.offsetX); - var y = (touch.pageY + bbox.scrollTop - pt.offsetY); - } else { - var x = (touch.pageX - pt.offsetX); - var y = (touch.pageY - pt.offsetY); - } - /// - pt.rotation = 0; - pt.scale = 1; - pt.startTime = pt.moveTime = (new Date()).getTime(); - pt.move = { x: x, y: y }; - pt.start = { x: x, y: y }; - /// - conf.fingers ++; - }; - /// - conf.event = event; - if (self.defaultListener) { - conf.listener = self.defaultListener; - delete self.defaultListener; - } - /// - var isTouchStart = !conf.fingers; - var track = conf.tracker; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Adding touch events to tracking. - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; // Touch ID. - // Track the current state of the touches. - if (conf.fingers) { - if (conf.fingers >= conf.maxFingers) { - var ids = []; - for (var sid in conf.tracker) ids.push(sid); - self.identifier = ids.join(","); - return isTouchStart; - } - var fingers = 0; // Finger ID. - for (var rid in track) { - // Replace removed finger. - if (track[rid].up) { - delete track[rid]; - addTouchStart(touch, sid); - conf.cancel = true; - break; - } - fingers ++; - } - // Add additional finger. - if (track[sid]) continue; - addTouchStart(touch, sid); - } else { // Start tracking fingers. - track = conf.tracker = {}; - self.bbox = conf.bbox = root.getBoundingBox(conf.target); - conf.fingers = 0; - conf.cancel = false; - addTouchStart(touch, sid); - } - } - /// - var ids = []; - for (var sid in conf.tracker) ids.push(sid); - self.identifier = ids.join(","); - /// - return isTouchStart; -}; + case "move": + pt.offsetX = touch.pageX - bbox.x1; + pt.offsetY = touch.pageY - bbox.y1; + break; -/* - End proxied pointer command. -*/ + default: + pt.offsetX = bbox.x1; + pt.offsetY = bbox.y1; + break; + } + if (conf.position === "relative") { + var x = touch.pageX + bbox.scrollLeft - pt.offsetX; + var y = touch.pageY + bbox.scrollTop - pt.offsetY; + } else { + var x = touch.pageX - pt.offsetX; + var y = touch.pageY - pt.offsetY; + } + pt.rotation = 0; + pt.scale = 1; + pt.startTime = pt.moveTime = new Date().getTime(); + pt.move = { + x: x, + y: y + }; + pt.start = { + x: x, + y: y + }; + conf.fingers++; + }; + conf.event = event; + if (self.defaultListener) { + conf.listener = self.defaultListener; + delete self.defaultListener; + } + var isTouchStart = !conf.fingers; + var track = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + if (conf.fingers) { + if (conf.fingers >= conf.maxFingers) { + var ids = []; + for (var sid in conf.tracker) ids.push(sid); + self.identifier = ids.join(","); + return isTouchStart; + } + var fingers = 0; + for (var rid in track) { + if (track[rid].up) { + delete track[rid]; + addTouchStart(touch, sid); + conf.cancel = true; + break; + } + fingers++; + } + if (track[sid]) continue; + addTouchStart(touch, sid); + } else { + track = conf.tracker = {}; + self.bbox = conf.bbox = root.getBoundingBox(conf.target); + conf.fingers = 0; + conf.cancel = false; + addTouchStart(touch, sid); + } + } + var ids = []; + for (var sid in conf.tracker) ids.push(sid); + self.identifier = ids.join(","); + return isTouchStart; + }; + root.pointerEnd = function(event, self, conf, onPointerUp) { + var touches = event.touches || []; + var length = touches.length; + var exists = {}; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier; + exists[sid || Infinity] = true; + } + for (var sid in conf.tracker) { + var track = conf.tracker[sid]; + if (exists[sid] || track.up) continue; + if (onPointerUp) { + onPointerUp({ + pageX: track.pageX, + pageY: track.pageY, + changedTouches: [ { + pageX: track.pageX, + pageY: track.pageY, + identifier: sid === "Infinity" ? Infinity : sid + } ] + }, "up"); + } + track.up = true; + conf.fingers--; + } + if (conf.fingers !== 0) return false; + var ids = []; + conf.gestureFingers = 0; + for (var sid in conf.tracker) { + conf.gestureFingers++; + ids.push(sid); + } + self.identifier = ids.join(","); + return true; + }; + root.getCoords = function(event) { + if (typeof event.pageX !== "undefined") { + root.getCoords = function(event) { + return Array({ + type: "mouse", + x: event.pageX, + y: event.pageY, + pageX: event.pageX, + pageY: event.pageY, + identifier: event.pointerId || Infinity + }); + }; + } else { + root.getCoords = function(event) { + event = event || window.event; + return Array({ + type: "mouse", + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop, + pageX: event.clientX + document.documentElement.scrollLeft, + pageY: event.clientY + document.documentElement.scrollTop, + identifier: Infinity + }); + }; + } + return root.getCoords(event); + }; + root.getCoord = function(event) { + if ("ontouchstart" in window) { + var pX = 0; + var pY = 0; + root.getCoord = function(event) { + var touches = event.changedTouches; + if (touches && touches.length) { + return { + x: pX = touches[0].pageX, + y: pY = touches[0].pageY + }; + } else { + return { + x: pX, + y: pY + }; + } + }; + } else if (typeof event.pageX !== "undefined" && typeof event.pageY !== "undefined") { + root.getCoord = function(event) { + return { + x: event.pageX, + y: event.pageY + }; + }; + } else { + root.getCoord = function(event) { + event = event || window.event; + return { + x: event.clientX + document.documentElement.scrollLeft, + y: event.clientY + document.documentElement.scrollTop + }; + }; + } + return root.getCoord(event); + }; + root.getBoundingBox = function(o) { + if (o === window || o === document) o = document.body; + var bbox = {}; + var bcr = o.getBoundingClientRect(); + bbox.width = bcr.width; + bbox.height = bcr.height; + bbox.x1 = bcr.left; + bbox.y1 = bcr.top; + bbox.x2 = bbox.x1 + bbox.width; + bbox.y2 = bbox.y1 + bbox.height; + bbox.scaleX = bcr.width / o.offsetWidth || 1; + bbox.scaleY = bcr.height / o.offsetHeight || 1; + bbox.scrollLeft = 0; + bbox.scrollTop = 0; + var tmp = o.parentNode; + while (tmp !== null) { + if (tmp === document.body) break; + if (tmp.scrollTop === undefined) break; + var style = window.getComputedStyle(tmp); + var position = style.getPropertyValue("position"); + if (position === "absolute") { + break; + } else if (position === "fixed") { + bbox.scrollTop -= tmp.parentNode.scrollTop; + break; + } else { + bbox.scrollLeft += tmp.scrollLeft; + bbox.scrollTop += tmp.scrollTop; + } + tmp = tmp.parentNode; + } + return bbox; + }; + (function() { + var agent = navigator.userAgent.toLowerCase(); + var mac = agent.indexOf("macintosh") !== -1; + if (mac && agent.indexOf("khtml") !== -1) { + var watch = { + 91: true, + 93: true + }; + } else if (mac && agent.indexOf("firefox") !== -1) { + var watch = { + 224: true + }; + } else { + var watch = { + 17: true + }; + } + root.metaTrackerReset = function() { + root.metaKey = false; + root.ctrlKey = false; + root.shiftKey = false; + root.altKey = false; + }; + root.metaTracker = function(event) { + var check = !!watch[event.keyCode]; + if (check) root.metaKey = event.type === "keydown"; + root.ctrlKey = event.ctrlKey; + root.shiftKey = event.shiftKey; + root.altKey = event.altKey; + return check; + }; + })(); + return root; +}(Event.proxy); -root.pointerEnd = function(event, self, conf, onPointerUp) { - // Record changed touches have ended (iOS changedTouches is not reliable). - var touches = event.touches || []; - var length = touches.length; - var exists = {}; - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier; - exists[sid || Infinity] = true; - } - for (var sid in conf.tracker) { - var track = conf.tracker[sid]; - if (exists[sid] || track.up) continue; - if (onPointerUp) { // add changedTouches to mouse. - onPointerUp({ - pageX: track.pageX, - pageY: track.pageY, - changedTouches: [{ - pageX: track.pageX, - pageY: track.pageY, - identifier: sid === "Infinity" ? Infinity : sid - }] - }, "up"); - } - track.up = true; - conf.fingers --; - } -/* // This should work but fails in Safari on iOS4 so not using it. - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Record changed touches have ended (this should work). - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var track = conf.tracker[sid]; - if (track && !track.up) { - if (onPointerUp) { // add changedTouches to mouse. - onPointerUp({ - changedTouches: [{ - pageX: track.pageX, - pageY: track.pageY, - identifier: sid === "Infinity" ? Infinity : sid - }] - }, "up"); - } - track.up = true; - conf.fingers --; - } - } */ - // Wait for all fingers to be released. - if (conf.fingers !== 0) return false; - // Record total number of fingers gesture used. - var ids = []; - conf.gestureFingers = 0; - for (var sid in conf.tracker) { - conf.gestureFingers ++; - ids.push(sid); - } - self.identifier = ids.join(","); - // Our pointer gesture has ended. - return true; -}; +if (typeof Event === "undefined") var Event = {}; -/* - Returns mouse coords in an array to match event.*Touches - ------------------------------------------------------------ - var touch = event.changedTouches || root.getCoords(event); -*/ +Event.MutationObserver = function() { + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var DOMAttrModifiedSupported = function() { + var p = document.createElement("p"); + var flag = false; + var fn = function() { + flag = true; + }; + if (p.addEventListener) { + p.addEventListener("DOMAttrModified", fn, false); + } else if (p.attachEvent) { + p.attachEvent("onDOMAttrModified", fn); + } else { + return false; + } + p.setAttribute("id", "target"); + return flag; + }(); + return function(container, callback) { + if (MutationObserver) { + var options = { + subtree: false, + attributes: true + }; + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(e) { + callback.call(e.target, e.attributeName); + }); + }); + observer.observe(container, options); + } else if (DOMAttrModifiedSupported) { + Event.add(container, "DOMAttrModified", function(e) { + callback.call(container, e.attrName); + }); + } else if ("onpropertychange" in document.body) { + Event.add(container, "propertychange", function(e) { + callback.call(container, window.event.propertyName); + }); + } + }; +}(); -root.getCoords = function(event) { - if (typeof(event.pageX) !== "undefined") { // Desktop browsers. - root.getCoords = function(event) { - return Array({ - type: "mouse", - x: event.pageX, - y: event.pageY, - pageX: event.pageX, - pageY: event.pageY, - identifier: event.pointerId || Infinity // pointerId is MS - }); - }; - } else { // Internet Explorer <= 8.0 - root.getCoords = function(event) { - event = event || window.event; - return Array({ - type: "mouse", - x: event.clientX + document.documentElement.scrollLeft, - y: event.clientY + document.documentElement.scrollTop, - pageX: event.clientX + document.documentElement.scrollLeft, - pageY: event.clientY + document.documentElement.scrollTop, - identifier: Infinity - }); - }; - } - return root.getCoords(event); -}; +if (typeof Event === "undefined") var Event = {}; -/* - Returns single coords in an object. - ------------------------------------------------------------ - var mouse = root.getCoord(event); -*/ +if (typeof Event.proxy === "undefined") Event.proxy = {}; -root.getCoord = function(event) { - if ("ontouchstart" in window) { // Mobile browsers. - var pX = 0; - var pY = 0; - root.getCoord = function(event) { - var touches = event.changedTouches; - if (touches && touches.length) { // ontouchstart + ontouchmove - return { - x: pX = touches[0].pageX, - y: pY = touches[0].pageY - }; - } else { // ontouchend - return { - x: pX, - y: pY - }; - } - }; - } else if(typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers. - root.getCoord = function(event) { - return { - x: event.pageX, - y: event.pageY - }; - }; - } else { // Internet Explorer <=8.0 - root.getCoord = function(event) { - event = event || window.event; - return { - x: event.clientX + document.documentElement.scrollLeft, - y: event.clientY + document.documentElement.scrollTop - }; - }; - } - return root.getCoord(event); -}; +Event.proxy = function(root) { + "use strict"; + root.click = function(conf) { + conf.gesture = conf.gesture || "click"; + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + var EVENT; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + EVENT = event; + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (EVENT.cancelBubble && ++EVENT.bubble > 1) return; + var pointers = EVENT.changedTouches || root.getCoords(EVENT); + var pointer = pointers[0]; + var bbox = conf.bbox; + var newbbox = root.getBoundingBox(conf.target); + if (conf.position === "relative") { + var ax = pointer.pageX + bbox.scrollLeft - bbox.x1; + var ay = pointer.pageY + bbox.scrollTop - bbox.y1; + } else { + var ax = pointer.pageX - bbox.x1; + var ay = pointer.pageY - bbox.y1; + } + if (ax > 0 && ax < bbox.width && ay > 0 && ay < bbox.height && bbox.scrollTop === newbbox.scrollTop) { + for (var key in conf.tracker) break; + var point = conf.tracker[key]; + self.x = point.start.x; + self.y = point.start.y; + conf.listener(EVENT, self); + } + } + }; + var self = root.pointerSetup(conf); + self.state = "click"; + Event.add(conf.target, "mousedown", conf.onPointerDown); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.click = root.click; + return root; +}(Event.proxy); -/* - Get target scale and position in space. -*/ +if (typeof Event === "undefined") var Event = {}; -root.getBoundingBox = function(o) { - if (o === window || o === document) o = document.body; - /// - var bbox = {}; - var bcr = o.getBoundingClientRect(); - bbox.width = bcr.width; - bbox.height = bcr.height; - bbox.x1 = bcr.left; - bbox.y1 = bcr.top; - bbox.x2 = bbox.x1 + bbox.width; - bbox.y2 = bbox.y1 + bbox.height; - bbox.scaleX = bcr.width / o.offsetWidth || 1; - bbox.scaleY = bcr.height / o.offsetHeight || 1; - bbox.scrollLeft = 0; - bbox.scrollTop = 0; +if (typeof Event.proxy === "undefined") Event.proxy = {}; - /// Get the scroll of container element. - var tmp = o.parentNode; - while (tmp !== null) { - if (tmp === document.body) break; - if (tmp.scrollTop === undefined) break; - var style = window.getComputedStyle(tmp); - var position = style.getPropertyValue("position"); - if (position === "absolute") { - break; - } else if (position === "fixed") { - bbox.scrollTop -= tmp.parentNode.scrollTop; - break; - } else { - bbox.scrollLeft += tmp.scrollLeft; - bbox.scrollTop += tmp.scrollTop; - } - tmp = tmp.parentNode; - }; - /// - return bbox; -}; +Event.proxy = function(root) { + "use strict"; + root.dbltap = root.dblclick = function(conf) { + conf.gesture = conf.gesture || "dbltap"; + conf.maxFingers = conf.maxFingers || conf.fingers || 1; + var delay = 700; + var time0, time1, timeout; + var pointer0, pointer1; + conf.onPointerDown = function(event) { + var pointers = event.changedTouches || root.getCoords(event); + if (time0 && !time1) { + pointer1 = pointers[0]; + time1 = new Date().getTime() - time0; + } else { + pointer0 = pointers[0]; + time0 = new Date().getTime(); + time1 = 0; + clearTimeout(timeout); + timeout = setTimeout(function() { + time0 = 0; + }, delay); + } + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + if (time0 && !time1) { + var pointers = event.changedTouches || root.getCoords(event); + pointer1 = pointers[0]; + } + var bbox = conf.bbox; + if (conf.position === "relative") { + var ax = pointer1.pageX + bbox.scrollLeft - bbox.x1; + var ay = pointer1.pageY + bbox.scrollTop - bbox.y1; + } else { + var ax = pointer1.pageX - bbox.x1; + var ay = pointer1.pageY - bbox.y1; + } + if (!(ax > 0 && ax < bbox.width && ay > 0 && ay < bbox.height && Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + if (time0 && time1) { + if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { + self.state = conf.gesture; + for (var key in conf.tracker) break; + var point = conf.tracker[key]; + self.x = point.start.x; + self.y = point.start.y; + conf.listener(event, self); + } + clearTimeout(timeout); + time0 = time1 = 0; + } + }; + var self = root.pointerSetup(conf); + self.state = "dblclick"; + Event.add(conf.target, "mousedown", conf.onPointerDown); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.dbltap = root.dbltap; + Event.Gesture._gestureHandlers.dblclick = root.dblclick; + return root; +}(Event.proxy); -/* - Keep track of metaKey, the proper ctrlKey for users platform. -*/ +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.dragElement = function(that, event) { + root.drag({ + event: event, + target: that, + position: "move", + listener: function(event, self) { + that.style.left = self.x + "px"; + that.style.top = self.y + "px"; + Event.prevent(event); + } + }); + }; + root.drag = function(conf) { + conf.gesture = "drag"; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + if (!conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + conf.onPointerMove(event, "down"); + }; + conf.onPointerMove = function(event, state) { + if (!conf.tracker) return conf.onPointerDown(event); + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + if (!pt) continue; + pt.pageX = touch.pageX; + pt.pageY = touch.pageY; + self.state = state || "move"; + self.identifier = identifier; + self.start = pt.start; + self.fingers = conf.fingers; + if (conf.position === "differenceFromLast") { + self.x = pt.pageX - pt.offsetX; + self.y = pt.pageY - pt.offsetY; + pt.offsetX = pt.pageX; + pt.offsetY = pt.pageY; + } else if (conf.position === "relative") { + self.x = pt.pageX + bbox.scrollLeft - pt.offsetX; + self.y = pt.pageY + bbox.scrollTop - pt.offsetY; + } else { + self.x = pt.pageX - pt.offsetX; + self.y = pt.pageY - pt.offsetY; + } + conf.listener(event, self); + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { + if (!conf.monitor) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + } + }; + var self = root.pointerSetup(conf); + if (conf.event) { + conf.onPointerDown(conf.event); + } else { + Event.add(conf.target, "mousedown", conf.onPointerDown); + if (conf.monitor) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + } + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.drag = root.drag; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + var RAD_DEG = Math.PI / 180; + root.gesture = function(conf) { + conf.gesture = conf.gesture || "gesture"; + conf.minFingers = conf.minFingers || conf.fingers || 2; + conf.onPointerDown = function(event) { + var fingers = conf.fingers; + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { + self.fingers = conf.minFingers; + self.scale = 1; + self.rotation = 0; + self.state = "start"; + var sids = ""; + for (var key in conf.tracker) sids += key; + self.identifier = parseInt(sids); + conf.listener(event, self); + } + }; + conf.onPointerMove = function(event, state) { + var bbox = conf.bbox; + var points = conf.tracker; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var pt = points[sid]; + if (!pt) continue; + if (conf.position === "relative") { + pt.move.x = touch.pageX + bbox.scrollLeft - bbox.x1; + pt.move.y = touch.pageY + bbox.scrollTop - bbox.y1; + } else { + pt.move.x = touch.pageX - bbox.x1; + pt.move.y = touch.pageY - bbox.y1; + } + } + if (conf.fingers < conf.minFingers) return; + var touches = []; + var scale = 0; + var rotation = 0; + var centroidx = 0; + var centroidy = 0; + var length = 0; + for (var sid in points) { + var touch = points[sid]; + if (touch.up) continue; + centroidx += touch.move.x; + centroidy += touch.move.y; + length++; + } + centroidx /= length; + centroidy /= length; + for (var sid in points) { + var touch = points[sid]; + if (touch.up) continue; + var start = touch.start; + if (!start.distance) { + var dx = start.x - centroidx; + var dy = start.y - centroidy; + start.distance = Math.sqrt(dx * dx + dy * dy); + start.angle = Math.atan2(dx, dy) / RAD_DEG; + } + var dx = touch.move.x - centroidx; + var dy = touch.move.y - centroidy; + var distance = Math.sqrt(dx * dx + dy * dy); + scale += distance / start.distance; + var angle = Math.atan2(dx, dy) / RAD_DEG; + var rotate = (start.angle - angle + 360) % 360 - 180; + touch.DEG2 = touch.DEG1; + touch.DEG1 = rotate > 0 ? rotate : -rotate; + if (typeof touch.DEG2 !== "undefined") { + if (rotate > 0) { + touch.rotation += touch.DEG1 - touch.DEG2; + } else { + touch.rotation -= touch.DEG1 - touch.DEG2; + } + rotation += touch.rotation; + } + touches.push(touch.move); + } + self.touches = touches; + self.fingers = conf.fingers; + self.scale = scale / conf.fingers; + self.rotation = rotation / conf.fingers; + self.state = "change"; + conf.listener(event, self); + }; + conf.onPointerUp = function(event) { + var fingers = conf.fingers; + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + } + if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { + self.fingers = conf.fingers; + self.state = "end"; + conf.listener(event, self); + } + }; + var self = root.pointerSetup(conf); + Event.add(conf.target, "mousedown", conf.onPointerDown); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.gesture = root.gesture; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.pointerdown = root.pointermove = root.pointerup = function(conf) { + conf.gesture = conf.gesture || "pointer"; + if (conf.target.isPointerEmitter) return; + var isDown = true; + conf.onPointerDown = function(event) { + isDown = false; + self.gesture = "pointerdown"; + conf.listener(event, self); + }; + conf.onPointerMove = function(event) { + self.gesture = "pointermove"; + conf.listener(event, self, isDown); + }; + conf.onPointerUp = function(event) { + isDown = true; + self.gesture = "pointerup"; + conf.listener(event, self, true); + }; + var self = root.pointerSetup(conf); + Event.add(conf.target, "mousedown", conf.onPointerDown); + Event.add(conf.target, "mousemove", conf.onPointerMove); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + conf.target.isPointerEmitter = true; + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; + Event.Gesture._gestureHandlers.pointermove = root.pointermove; + Event.Gesture._gestureHandlers.pointerup = root.pointerup; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.shake = function(conf) { + var self = { + gesture: "devicemotion", + acceleration: {}, + accelerationIncludingGravity: {}, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener("devicemotion", onDeviceMotion, false); + } + }; + var threshold = 4; + var timeout = 1e3; + var timeframe = 200; + var shakes = 3; + var lastShake = new Date().getTime(); + var gravity = { + x: 0, + y: 0, + z: 0 + }; + var delta = { + x: { + count: 0, + value: 0 + }, + y: { + count: 0, + value: 0 + }, + z: { + count: 0, + value: 0 + } + }; + var onDeviceMotion = function(e) { + var alpha = .8; + var o = e.accelerationIncludingGravity; + gravity.x = alpha * gravity.x + (1 - alpha) * o.x; + gravity.y = alpha * gravity.y + (1 - alpha) * o.y; + gravity.z = alpha * gravity.z + (1 - alpha) * o.z; + self.accelerationIncludingGravity = gravity; + self.acceleration.x = o.x - gravity.x; + self.acceleration.y = o.y - gravity.y; + self.acceleration.z = o.z - gravity.z; + if (conf.gesture === "devicemotion") { + conf.listener(e, self); + return; + } + var data = "xyz"; + var now = new Date().getTime(); + for (var n = 0, length = data.length; n < length; n++) { + var letter = data[n]; + var ACCELERATION = self.acceleration[letter]; + var DELTA = delta[letter]; + var abs = Math.abs(ACCELERATION); + if (now - lastShake < timeout) continue; + if (abs > threshold) { + var idx = now * ACCELERATION / abs; + var span = Math.abs(idx + DELTA.value); + if (DELTA.value && span < timeframe) { + DELTA.value = idx; + DELTA.count++; + if (DELTA.count === shakes) { + conf.listener(e, self); + lastShake = now; + DELTA.value = 0; + DELTA.count = 0; + } + } else { + DELTA.value = idx; + DELTA.count = 1; + } + } + } + }; + if (!window.addEventListener) return; + window.addEventListener("devicemotion", onDeviceMotion, false); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.shake = root.shake; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + var RAD_DEG = Math.PI / 180; + root.swipe = function(conf) { + conf.snap = conf.snap || 90; + conf.threshold = conf.threshold || 1; + conf.gesture = conf.gesture || "swipe"; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + } + }; + conf.onPointerMove = function(event) { + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var sid = touch.identifier || Infinity; + var o = conf.tracker[sid]; + if (!o) continue; + o.move.x = touch.pageX; + o.move.y = touch.pageY; + o.moveTime = new Date().getTime(); + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + var velocity1; + var velocity2; + var degree1; + var degree2; + var start = { + x: 0, + y: 0 + }; + var endx = 0; + var endy = 0; + var length = 0; + for (var sid in conf.tracker) { + var touch = conf.tracker[sid]; + var xdist = touch.move.x - touch.start.x; + var ydist = touch.move.y - touch.start.y; + endx += touch.move.x; + endy += touch.move.y; + start.x += touch.start.x; + start.y += touch.start.y; + length++; + var distance = Math.sqrt(xdist * xdist + ydist * ydist); + var ms = touch.moveTime - touch.startTime; + var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; + var velocity2 = ms ? distance / ms : 0; + if (typeof degree1 === "undefined") { + degree1 = degree2; + velocity1 = velocity2; + } else if (Math.abs(degree2 - degree1) <= 20) { + degree1 = (degree1 + degree2) / 2; + velocity1 = (velocity1 + velocity2) / 2; + } else { + return; + } + } + var fingers = conf.gestureFingers; + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + if (velocity1 > conf.threshold) { + start.x /= length; + start.y /= length; + self.start = start; + self.x = endx / length; + self.y = endy / length; + self.angle = -(((degree1 / conf.snap + .5 >> 0) * conf.snap || 360) - 360); + self.velocity = velocity1; + self.fingers = fingers; + self.state = "swipe"; + conf.listener(event, self); + } + } + } + }; + var self = root.pointerSetup(conf); + Event.add(conf.target, "mousedown", conf.onPointerDown); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.swipe = root.swipe; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.longpress = function(conf) { + conf.gesture = "longpress"; + return root.tap(conf); + }; + root.tap = function(conf) { + conf.delay = conf.delay || 500; + conf.timeout = conf.timeout || 250; + conf.driftDeviance = conf.driftDeviance || 10; + conf.gesture = conf.gesture || "tap"; + var timestamp, timeout; + conf.onPointerDown = function(event) { + if (root.pointerStart(event, self, conf)) { + timestamp = new Date().getTime(); + Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); + Event.add(conf.doc, "mouseup", conf.onPointerUp); + if (conf.gesture !== "longpress") return; + timeout = setTimeout(function() { + if (event.cancelBubble && ++event.bubble > 1) return; + var fingers = 0; + for (var key in conf.tracker) { + var point = conf.tracker[key]; + if (point.end === true) return; + if (conf.cancel) return; + fingers++; + } + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + self.state = "start"; + self.fingers = fingers; + self.x = point.start.x; + self.y = point.start.y; + conf.listener(event, self); + } + }, conf.delay); + } + }; + conf.onPointerMove = function(event) { + var bbox = conf.bbox; + var touches = event.changedTouches || root.getCoords(event); + var length = touches.length; + for (var i = 0; i < length; i++) { + var touch = touches[i]; + var identifier = touch.identifier || Infinity; + var pt = conf.tracker[identifier]; + if (!pt) continue; + if (conf.position === "relative") { + var x = touch.pageX + bbox.scrollLeft - bbox.x1; + var y = touch.pageY + bbox.scrollTop - bbox.y1; + } else { + var x = touch.pageX - bbox.x1; + var y = touch.pageY - bbox.y1; + } + var dx = x - pt.start.x; + var dy = y - pt.start.y; + var distance = Math.sqrt(dx * dx + dy * dy); + if (!(x > 0 && x < bbox.width && y > 0 && y < bbox.height && distance <= conf.driftDeviance)) { + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + conf.cancel = true; + return; + } + } + }; + conf.onPointerUp = function(event) { + if (root.pointerEnd(event, self, conf)) { + clearTimeout(timeout); + Event.remove(conf.doc, "mousemove", conf.onPointerMove); + Event.remove(conf.doc, "mouseup", conf.onPointerUp); + if (event.cancelBubble && ++event.bubble > 1) return; + if (conf.gesture === "longpress") { + if (self.state === "start") { + self.state = "end"; + conf.listener(event, self); + } + return; + } + if (conf.cancel) return; + if (new Date().getTime() - timestamp > conf.timeout) return; + var fingers = conf.gestureFingers; + if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { + self.state = "tap"; + self.fingers = conf.gestureFingers; + conf.listener(event, self); + } + } + }; + var self = root.pointerSetup(conf); + Event.add(conf.target, "mousedown", conf.onPointerDown); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.tap = root.tap; + Event.Gesture._gestureHandlers.longpress = root.longpress; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.wheel = function(conf) { + var interval; + var timeout = conf.timeout || 150; + var count = 0; + var self = { + gesture: "wheel", + state: "start", + wheelDelta: 0, + target: conf.target, + listener: conf.listener, + preventElasticBounce: function() { + var target = this.target; + var scrollTop = target.scrollTop; + var top = scrollTop + target.offsetHeight; + var height = target.scrollHeight; + if (top === height && this.wheelDelta <= 0) Event.cancel(event); else if (scrollTop === 0 && this.wheelDelta >= 0) Event.cancel(event); + Event.stop(event); + }, + add: function() { + conf.target[add](type, onMouseWheel, false); + }, + remove: function() { + conf.target[remove](type, onMouseWheel, false); + } + }; + var onMouseWheel = function(event) { + event = event || window.event; + self.state = count++ ? "change" : "start"; + self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; + conf.listener(event, self); + clearTimeout(interval); + interval = setTimeout(function() { + count = 0; + self.state = "end"; + self.wheelDelta = 0; + conf.listener(event, self); + }, timeout); + }; + var add = document.addEventListener ? "addEventListener" : "attachEvent"; + var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; + var type = Event.getEventSupport("mousewheel") ? "mousewheel" : "DOMMouseScroll"; + conf.target[add](type, onMouseWheel, false); + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.wheel = root.wheel; + return root; +}(Event.proxy); + +if (typeof Event === "undefined") var Event = {}; + +if (typeof Event.proxy === "undefined") Event.proxy = {}; + +Event.proxy = function(root) { + "use strict"; + root.orientation = function(conf) { + var self = { + gesture: "orientationchange", + previous: null, + current: window.orientation, + target: conf.target, + listener: conf.listener, + remove: function() { + window.removeEventListener("orientationchange", onOrientationChange, false); + } + }; + var onOrientationChange = function(e) { + self.previous = self.current; + self.current = window.orientation; + if (self.previous !== null && self.previous != self.current) { + conf.listener(e, self); + return; + } + }; + if (window.DeviceOrientationEvent) { + window.addEventListener("orientationchange", onOrientationChange, false); + } + return self; + }; + Event.Gesture = Event.Gesture || {}; + Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; + Event.Gesture._gestureHandlers.orientation = root.orientation; + return root; +}(Event.proxy); (function() { - var agent = navigator.userAgent.toLowerCase(); - var mac = agent.indexOf("macintosh") !== -1; - if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari. - var watch = { 91: true, 93: true }; - } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox. - var watch = { 224: true }; - } else { // windows, linux, or mac opera. - var watch = { 17: true }; - } - root.metaTrackerReset = function() { - root.metaKey = false; - root.ctrlKey = false; - root.shiftKey = false; - root.altKey = false; - }; - root.metaTracker = function(event) { - var check = !!watch[event.keyCode]; - if (check) root.metaKey = event.type === "keydown"; - root.ctrlKey = event.ctrlKey; - root.shiftKey = event.shiftKey; - root.altKey = event.altKey; - return check; - }; + function _removeEventListener(eventName, handler) { + if (!this.__eventListeners[eventName]) return; + if (handler) { + fabric.util.removeFromArray(this.__eventListeners[eventName], handler); + } else { + this.__eventListeners[eventName].length = 0; + } + } + function observe(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = {}; + } + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = []; + } + this.__eventListeners[eventName].push(handler); + } + return this; + } + function stopObserving(eventName, handler) { + if (!this.__eventListeners) return; + if (arguments.length === 0) { + this.__eventListeners = {}; + } else if (arguments.length === 1 && typeof arguments[0] === "object") { + for (var prop in eventName) { + _removeEventListener.call(this, prop, eventName[prop]); + } + } else { + _removeEventListener.call(this, eventName, handler); + } + return this; + } + function fire(eventName, options) { + if (!this.__eventListeners) return; + var listenersForEvent = this.__eventListeners[eventName]; + if (!listenersForEvent) return; + for (var i = 0, len = listenersForEvent.length; i < len; i++) { + listenersForEvent[i].call(this, options || {}); + } + return this; + } + fabric.Observable = { + observe: observe, + stopObserving: stopObserving, + fire: fire, + on: observe, + off: stopObserving, + trigger: fire + }; })(); -return root; - -})(Event.proxy); -/* - ---------------------------------------------------- - "MutationObserver" event proxy. - ---------------------------------------------------- - Author: Selvakumar Arumugam (MIT LICENSE) - http://stackoverflow.com/questions/10868104/can-you-have-a-javascript-hook-trigger-after-a-dom-elements-style-object-change - ---------------------------------------------------- -*/ -if (typeof(Event) === "undefined") var Event = {}; - -Event.MutationObserver = (function() { - var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; - var DOMAttrModifiedSupported = (function() { - var p = document.createElement("p"); - var flag = false; - var fn = function() { flag = true }; - if (p.addEventListener) { - p.addEventListener("DOMAttrModified", fn, false); - } else if (p.attachEvent) { - p.attachEvent("onDOMAttrModified", fn); - } else { - return false; - } - /// - p.setAttribute("id", "target"); - /// - return flag; - })(); - /// - return function(container, callback) { - if (MutationObserver) { - var options = { - subtree: false, - attributes: true - }; - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(e) { - callback.call(e.target, e.attributeName); - }); - }); - observer.observe(container, options) - } else if (DOMAttrModifiedSupported) { - Event.add(container, "DOMAttrModified", function(e) { - callback.call(container, e.attrName); - }); - } else if ("onpropertychange" in document.body) { - Event.add(container, "propertychange", function(e) { - callback.call(container, window.event.propertyName); - }); - } - } -})(); -/* - "Click" event proxy. - ---------------------------------------------------- - Event.add(window, "click", function(event, self) {}); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.click = function(conf) { - conf.gesture = conf.gesture || "click"; - conf.maxFingers = conf.maxFingers || conf.fingers || 1; - // Setting up local variables. - var EVENT; - // Tracking the events. - conf.onPointerDown = function (event) { - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function (event) { - EVENT = event; - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - if (EVENT.cancelBubble && ++ EVENT.bubble > 1) return; - var pointers = EVENT.changedTouches || root.getCoords(EVENT); - var pointer = pointers[0]; - var bbox = conf.bbox; - var newbbox = root.getBoundingBox(conf.target); - if (conf.position === "relative") { - var ax = (pointer.pageX + bbox.scrollLeft - bbox.x1); - var ay = (pointer.pageY + bbox.scrollTop - bbox.y1); - } else { - var ax = (pointer.pageX - bbox.x1); - var ay = (pointer.pageY - bbox.y1); - } - if (ax > 0 && ax < bbox.width && // Within target coordinates. - ay > 0 && ay < bbox.height && - bbox.scrollTop === newbbox.scrollTop) { - /// - for (var key in conf.tracker) break; //- should be modularized? in dblclick too - var point = conf.tracker[key]; - self.x = point.start.x; - self.y = point.start.y; - /// - conf.listener(EVENT, self); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - self.state = "click"; - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.click = root.click; - -return root; - -})(Event.proxy); -/* - "Double-Click" aka "Double-Tap" event proxy. - ---------------------------------------------------- - Event.add(window, "dblclick", function(event, self) {}); - ---------------------------------------------------- - Touch an target twice for <= 700ms, with less than 25 pixel drift. -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.dbltap = -root.dblclick = function(conf) { - conf.gesture = conf.gesture || "dbltap"; - conf.maxFingers = conf.maxFingers || conf.fingers || 1; - // Setting up local variables. - var delay = 700; // in milliseconds - var time0, time1, timeout; - var pointer0, pointer1; - // Tracking the events. - conf.onPointerDown = function (event) { - var pointers = event.changedTouches || root.getCoords(event); - if (time0 && !time1) { // Click #2 - pointer1 = pointers[0]; - time1 = (new Date()).getTime() - time0; - } else { // Click #1 - pointer0 = pointers[0]; - time0 = (new Date()).getTime(); - time1 = 0; - clearTimeout(timeout); - timeout = setTimeout(function() { - time0 = 0; - }, delay); - } - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function (event) { - if (time0 && !time1) { - var pointers = event.changedTouches || root.getCoords(event); - pointer1 = pointers[0]; - } - var bbox = conf.bbox; - if (conf.position === "relative") { - var ax = (pointer1.pageX + bbox.scrollLeft - bbox.x1); - var ay = (pointer1.pageY + bbox.scrollTop - bbox.y1); - } else { - var ax = (pointer1.pageX - bbox.x1); - var ay = (pointer1.pageY - bbox.y1); - } - if (!(ax > 0 && ax < bbox.width && // Within target coordinates.. - ay > 0 && ay < bbox.height && - Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance. - Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) { - // Cancel out this listener. - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - clearTimeout(timeout); - time0 = time1 = 0; - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - if (time0 && time1) { - if (time1 <= delay && !(event.cancelBubble && ++event.bubble > 1)) { - self.state = conf.gesture; - for (var key in conf.tracker) break; - var point = conf.tracker[key]; - self.x = point.start.x; - self.y = point.start.y; - conf.listener(event, self); - } - clearTimeout(timeout); - time0 = time1 = 0; - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - self.state = "dblclick"; - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.dbltap = root.dbltap; -Event.Gesture._gestureHandlers.dblclick = root.dblclick; - -return root; - -})(Event.proxy); -/* - "Drag" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: maxFingers, position. - ---------------------------------------------------- - Event.add(window, "drag", function(event, self) { - console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.dragElement = function(that, event) { - root.drag({ - event: event, - target: that, - position: "move", - listener: function(event, self) { - that.style.left = self.x + "px"; - that.style.top = self.y + "px"; - Event.prevent(event); - } - }); -}; - -root.drag = function(conf) { - conf.gesture = "drag"; - conf.onPointerDown = function (event) { - if (root.pointerStart(event, self, conf)) { - if (!conf.monitor) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - } - // Process event listener. - conf.onPointerMove(event, "down"); - }; - conf.onPointerMove = function (event, state) { - if (!conf.tracker) return conf.onPointerDown(event); - var bbox = conf.bbox; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var identifier = touch.identifier || Infinity; - var pt = conf.tracker[identifier]; - // Identifier defined outside of listener. - if (!pt) continue; - pt.pageX = touch.pageX; - pt.pageY = touch.pageY; - // Record data. - self.state = state || "move"; - self.identifier = identifier; - self.start = pt.start; - self.fingers = conf.fingers; - if (conf.position === "differenceFromLast") { - self.x = (pt.pageX - pt.offsetX); - self.y = (pt.pageY - pt.offsetY); - pt.offsetX = pt.pageX; - pt.offsetY = pt.pageY; - } else if (conf.position === "relative") { - self.x = (pt.pageX + bbox.scrollLeft - pt.offsetX); - self.y = (pt.pageY + bbox.scrollTop - pt.offsetY); - } else { - self.x = (pt.pageX - pt.offsetX); - self.y = (pt.pageY - pt.offsetY); - } - /// - conf.listener(event, self); - } - }; - conf.onPointerUp = function(event) { - // Remove tracking for touch. - if (root.pointerEnd(event, self, conf, conf.onPointerMove)) { - if (!conf.monitor) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - if (conf.event) { - conf.onPointerDown(conf.event); - } else { // - Event.add(conf.target, "mousedown", conf.onPointerDown); - if (conf.monitor) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - } - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.drag = root.drag; - -return root; - -})(Event.proxy); -/* - "Gesture" event proxy (2+ fingers). - ---------------------------------------------------- - CONFIGURE: minFingers, maxFingers. - ---------------------------------------------------- - Event.add(window, "gesture", function(event, self) { - console.log(self.rotation, self.scale, self.fingers, self.state); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -var RAD_DEG = Math.PI / 180; - -root.gesture = function(conf) { - conf.gesture = conf.gesture || "gesture"; - conf.minFingers = conf.minFingers || conf.fingers || 2; - // Tracking the events. - conf.onPointerDown = function (event) { - var fingers = conf.fingers; - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - // Record gesture start. - if (conf.fingers === conf.minFingers && fingers !== conf.fingers) { - self.fingers = conf.minFingers; - self.scale = 1; - self.rotation = 0; - self.state = "start"; - var sids = ""; //- FIXME(mud): can generate duplicate IDs. - for (var key in conf.tracker) sids += key; - self.identifier = parseInt(sids); - conf.listener(event, self); - } - }; - /// - conf.onPointerMove = function (event, state) { - var bbox = conf.bbox; - var points = conf.tracker; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - // Update tracker coordinates. - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var pt = points[sid]; - // Check whether "pt" is used by another gesture. - if (!pt) continue; - // Find the actual coordinates. - if (conf.position === "relative") { - pt.move.x = (touch.pageX + bbox.scrollLeft - bbox.x1); - pt.move.y = (touch.pageY + bbox.scrollTop - bbox.y1); - } else { - pt.move.x = (touch.pageX - bbox.x1); - pt.move.y = (touch.pageY - bbox.y1); - } - } - /// - if (conf.fingers < conf.minFingers) return; - /// - var touches = []; - var scale = 0; - var rotation = 0; - /// Calculate centroid of gesture. - var centroidx = 0; - var centroidy = 0; - var length = 0; - for (var sid in points) { - var touch = points[sid]; - if (touch.up) continue; - centroidx += touch.move.x; - centroidy += touch.move.y; - length ++; - } - centroidx /= length; - centroidy /= length; - /// - for (var sid in points) { - var touch = points[sid]; - if (touch.up) continue; - var start = touch.start; - if (!start.distance) { - var dx = start.x - centroidx; - var dy = start.y - centroidy; - start.distance = Math.sqrt(dx * dx + dy * dy); - start.angle = Math.atan2(dx, dy) / RAD_DEG; - } - // Calculate scale. - var dx = touch.move.x - centroidx; - var dy = touch.move.y - centroidy; - var distance = Math.sqrt(dx * dx + dy * dy); - scale += distance / start.distance; - // Calculate rotation. - var angle = Math.atan2(dx, dy) / RAD_DEG; - var rotate = (start.angle - angle + 360) % 360 - 180; - touch.DEG2 = touch.DEG1; // Previous degree. - touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree. - if (typeof(touch.DEG2) !== "undefined") { - if (rotate > 0) { - touch.rotation += touch.DEG1 - touch.DEG2; - } else { - touch.rotation -= touch.DEG1 - touch.DEG2; - } - rotation += touch.rotation; - } - // Attach current points to self. - touches.push(touch.move); - } - /// - self.touches = touches; - self.fingers = conf.fingers; - self.scale = scale / conf.fingers; - self.rotation = rotation / conf.fingers; - self.state = "change"; - conf.listener(event, self); - }; - conf.onPointerUp = function(event) { - // Remove tracking for touch. - var fingers = conf.fingers; - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - } - // Check whether fingers has dropped below minFingers. - if (fingers === conf.minFingers && conf.fingers < conf.minFingers) { - self.fingers = conf.fingers; - self.state = "end"; - conf.listener(event, self); - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.gesture = root.gesture; - -return root; - -})(Event.proxy); -/* - "Pointer" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: minFingers, maxFingers. - ---------------------------------------------------- - Event.add(window, "gesture", function(event, self) { - console.log(self.rotation, self.scale, self.fingers, self.state); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.pointerdown = -root.pointermove = -root.pointerup = function(conf) { - conf.gesture = conf.gesture || "pointer"; - if (conf.target.isPointerEmitter) return; - // Tracking the events. - var isDown = true; - conf.onPointerDown = function (event) { - isDown = false; - self.gesture = "pointerdown"; - conf.listener(event, self); - }; - conf.onPointerMove = function (event) { - self.gesture = "pointermove"; - conf.listener(event, self, isDown); - }; - conf.onPointerUp = function (event) { - isDown = true; - self.gesture = "pointerup"; - conf.listener(event, self, true); - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - Event.add(conf.target, "mousemove", conf.onPointerMove); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - // Return this object. - conf.target.isPointerEmitter = true; - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.pointerdown = root.pointerdown; -Event.Gesture._gestureHandlers.pointermove = root.pointermove; -Event.Gesture._gestureHandlers.pointerup = root.pointerup; - -return root; - -})(Event.proxy); -/* - "Device Motion" and "Shake" event proxy. - ---------------------------------------------------- - http://developer.android.com/reference/android/hardware/SensorEvent.html#values - ---------------------------------------------------- - Event.add(window, "shake", function(event, self) {}); - Event.add(window, "devicemotion", function(event, self) { - console.log(self.acceleration, self.accelerationIncludingGravity); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.shake = function(conf) { - // Externally accessible data. - var self = { - gesture: "devicemotion", - acceleration: {}, - accelerationIncludingGravity: {}, - target: conf.target, - listener: conf.listener, - remove: function() { - window.removeEventListener('devicemotion', onDeviceMotion, false); - } - }; - // Setting up local variables. - var threshold = 4; // Gravitational threshold. - var timeout = 1000; // Timeout between shake events. - var timeframe = 200; // Time between shakes. - var shakes = 3; // Minimum shakes to trigger event. - var lastShake = (new Date()).getTime(); - var gravity = { x: 0, y: 0, z: 0 }; - var delta = { - x: { count: 0, value: 0 }, - y: { count: 0, value: 0 }, - z: { count: 0, value: 0 } - }; - // Tracking the events. - var onDeviceMotion = function(e) { - var alpha = 0.8; // Low pass filter. - var o = e.accelerationIncludingGravity; - gravity.x = alpha * gravity.x + (1 - alpha) * o.x; - gravity.y = alpha * gravity.y + (1 - alpha) * o.y; - gravity.z = alpha * gravity.z + (1 - alpha) * o.z; - self.accelerationIncludingGravity = gravity; - self.acceleration.x = o.x - gravity.x; - self.acceleration.y = o.y - gravity.y; - self.acceleration.z = o.z - gravity.z; - /// - if (conf.gesture === "devicemotion") { - conf.listener(e, self); - return; - } - var data = "xyz"; - var now = (new Date()).getTime(); - for (var n = 0, length = data.length; n < length; n ++) { - var letter = data[n]; - var ACCELERATION = self.acceleration[letter]; - var DELTA = delta[letter]; - var abs = Math.abs(ACCELERATION); - /// Check whether another shake event was recently registered. - if (now - lastShake < timeout) continue; - /// Check whether delta surpasses threshold. - if (abs > threshold) { - var idx = now * ACCELERATION / abs; - var span = Math.abs(idx + DELTA.value); - // Check whether last delta was registered within timeframe. - if (DELTA.value && span < timeframe) { - DELTA.value = idx; - DELTA.count ++; - // Check whether delta count has enough shakes. - if (DELTA.count === shakes) { - conf.listener(e, self); - // Reset tracking. - lastShake = now; - DELTA.value = 0; - DELTA.count = 0; - } - } else { - // Track first shake. - DELTA.value = idx; - DELTA.count = 1; - } - } - } - }; - // Attach events. - if (!window.addEventListener) return; - window.addEventListener('devicemotion', onDeviceMotion, false); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.shake = root.shake; - -return root; - -})(Event.proxy); -/* - "Swipe" event proxy (1+ fingers). - ---------------------------------------------------- - CONFIGURE: snap, threshold, maxFingers. - ---------------------------------------------------- - Event.add(window, "swipe", function(event, self) { - console.log(self.velocity, self.angle); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -var RAD_DEG = Math.PI / 180; - -root.swipe = function(conf) { - conf.snap = conf.snap || 90; // angle snap. - conf.threshold = conf.threshold || 1; // velocity threshold. - conf.gesture = conf.gesture || "swipe"; - // Tracking the events. - conf.onPointerDown = function (event) { - if (root.pointerStart(event, self, conf)) { - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - } - }; - conf.onPointerMove = function (event) { - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var sid = touch.identifier || Infinity; - var o = conf.tracker[sid]; - // Identifier defined outside of listener. - if (!o) continue; - o.move.x = touch.pageX; - o.move.y = touch.pageY; - o.moveTime = (new Date()).getTime(); - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - /// - var velocity1; - var velocity2 - var degree1; - var degree2; - /// Calculate centroid of gesture. - var start = { x: 0, y: 0 }; - var endx = 0; - var endy = 0; - var length = 0; - /// - for (var sid in conf.tracker) { - var touch = conf.tracker[sid]; - var xdist = touch.move.x - touch.start.x; - var ydist = touch.move.y - touch.start.y; - /// - endx += touch.move.x; - endy += touch.move.y; - start.x += touch.start.x; - start.y += touch.start.y; - length ++; - /// - var distance = Math.sqrt(xdist * xdist + ydist * ydist); - var ms = touch.moveTime - touch.startTime; - var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180; - var velocity2 = ms ? distance / ms : 0; - if (typeof(degree1) === "undefined") { - degree1 = degree2; - velocity1 = velocity2; - } else if (Math.abs(degree2 - degree1) <= 20) { - degree1 = (degree1 + degree2) / 2; - velocity1 = (velocity1 + velocity2) / 2; - } else { - return; - } - } - /// - var fingers = conf.gestureFingers; - if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { - if (velocity1 > conf.threshold) { - start.x /= length; - start.y /= length; - self.start = start; - self.x = endx / length; - self.y = endy / length; - self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360); - self.velocity = velocity1; - self.fingers = fingers; - self.state = "swipe"; - conf.listener(event, self); - } - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.swipe = root.swipe; - -return root; - -})(Event.proxy); -/* - "Tap" and "Longpress" event proxy. - ---------------------------------------------------- - CONFIGURE: delay (longpress), timeout (tap). - ---------------------------------------------------- - Event.add(window, "tap", function(event, self) { - console.log(self.fingers); - }); - ---------------------------------------------------- - multi-finger tap // touch an target for <= 250ms. - multi-finger longpress // touch an target for >= 500ms -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.longpress = function(conf) { - conf.gesture = "longpress"; - return root.tap(conf); -}; - -root.tap = function(conf) { - conf.delay = conf.delay || 500; - conf.timeout = conf.timeout || 250; - conf.driftDeviance = conf.driftDeviance || 10; - conf.gesture = conf.gesture || "tap"; - // Setting up local variables. - var timestamp, timeout; - // Tracking the events. - conf.onPointerDown = function (event) { - if (root.pointerStart(event, self, conf)) { - timestamp = (new Date()).getTime(); - // Initialize event listeners. - Event.add(conf.doc, "mousemove", conf.onPointerMove).listener(event); - Event.add(conf.doc, "mouseup", conf.onPointerUp); - // Make sure this is a "longpress" event. - if (conf.gesture !== "longpress") return; - timeout = setTimeout(function() { - if (event.cancelBubble && ++event.bubble > 1) return; - // Make sure no fingers have been changed. - var fingers = 0; - for (var key in conf.tracker) { - var point = conf.tracker[key]; - if (point.end === true) return; - if (conf.cancel) return; - fingers ++; - } - // Send callback. - if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { - self.state = "start"; - self.fingers = fingers; - self.x = point.start.x; - self.y = point.start.y; - conf.listener(event, self); - } - }, conf.delay); - } - }; - conf.onPointerMove = function (event) { - var bbox = conf.bbox; - var touches = event.changedTouches || root.getCoords(event); - var length = touches.length; - for (var i = 0; i < length; i ++) { - var touch = touches[i]; - var identifier = touch.identifier || Infinity; - var pt = conf.tracker[identifier]; - if (!pt) continue; - if (conf.position === "relative") { - var x = (touch.pageX + bbox.scrollLeft - bbox.x1); - var y = (touch.pageY + bbox.scrollTop - bbox.y1); - } else { - var x = (touch.pageX - bbox.x1); - var y = (touch.pageY - bbox.y1); - } - /// - var dx = x - pt.start.x; - var dy = y - pt.start.y; - var distance = Math.sqrt(dx * dx + dy * dy); - if (!(x > 0 && x < bbox.width && // Within target coordinates.. - y > 0 && y < bbox.height && - distance <= conf.driftDeviance)) { // Within drift deviance. - // Cancel out this listener. - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - conf.cancel = true; - return; - } - } - }; - conf.onPointerUp = function(event) { - if (root.pointerEnd(event, self, conf)) { - clearTimeout(timeout); - Event.remove(conf.doc, "mousemove", conf.onPointerMove); - Event.remove(conf.doc, "mouseup", conf.onPointerUp); - if (event.cancelBubble && ++event.bubble > 1) return; - // Callback release on longpress. - if (conf.gesture === "longpress") { - if (self.state === "start") { - self.state = "end"; - conf.listener(event, self); - } - return; - } - // Cancel event due to movement. - if (conf.cancel) return; - // Ensure delay is within margins. - if ((new Date()).getTime() - timestamp > conf.timeout) return; - // Send callback. - var fingers = conf.gestureFingers; - if (conf.minFingers <= fingers && conf.maxFingers >= fingers) { - self.state = "tap"; - self.fingers = conf.gestureFingers; - conf.listener(event, self); - } - } - }; - // Generate maintenance commands, and other configurations. - var self = root.pointerSetup(conf); - // Attach events. - Event.add(conf.target, "mousedown", conf.onPointerDown); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.tap = root.tap; -Event.Gesture._gestureHandlers.longpress = root.longpress; - -return root; - -})(Event.proxy); -/* - "Mouse Wheel" event proxy. - ---------------------------------------------------- - Event.add(window, "wheel", function(event, self) { - console.log(self.state, self.wheelDelta); - }); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.wheel = function(conf) { - // Configure event listener. - var interval; - var timeout = conf.timeout || 150; - var count = 0; - // Externally accessible data. - var self = { - gesture: "wheel", - state: "start", - wheelDelta: 0, - target: conf.target, - listener: conf.listener, - preventElasticBounce: function() { - var target = this.target; - var scrollTop = target.scrollTop; - var top = scrollTop + target.offsetHeight; - var height = target.scrollHeight; - if (top === height && this.wheelDelta <= 0) Event.cancel(event); - else if (scrollTop === 0 && this.wheelDelta >= 0) Event.cancel(event); - Event.stop(event); - }, - add: function() { - conf.target[add](type, onMouseWheel, false); - }, - remove: function() { - conf.target[remove](type, onMouseWheel, false); - } - }; - // Tracking the events. - var onMouseWheel = function(event) { - event = event || window.event; - self.state = count++ ? "change" : "start"; - self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta; - conf.listener(event, self); - clearTimeout(interval); - interval = setTimeout(function() { - count = 0; - self.state = "end"; - self.wheelDelta = 0; - conf.listener(event, self); - }, timeout); - }; - // Attach events. - var add = document.addEventListener ? "addEventListener" : "attachEvent"; - var remove = document.removeEventListener ? "removeEventListener" : "detachEvent"; - var type = Event.getEventSupport("mousewheel") ? "mousewheel" : "DOMMouseScroll"; - conf.target[add](type, onMouseWheel, false); - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.wheel = root.wheel; - -return root; - -})(Event.proxy); -/* - "Orientation Change" - ---------------------------------------------------- - https://developer.apple.com/library/safari/documentation/SafariDOMAdditions/Reference/DeviceOrientationEventClassRef/DeviceOrientationEvent/DeviceOrientationEvent.html#//apple_ref/doc/uid/TP40010526 - ---------------------------------------------------- - Event.add(window, "deviceorientation", function(event, self) {}); -*/ - -if (typeof(Event) === "undefined") var Event = {}; -if (typeof(Event.proxy) === "undefined") Event.proxy = {}; - -Event.proxy = (function(root) { "use strict"; - -root.orientation = function(conf) { - // Externally accessible data. - var self = { - gesture: "orientationchange", - previous: null, /* Report the previous orientation */ - current: window.orientation, - target: conf.target, - listener: conf.listener, - remove: function() { - window.removeEventListener('orientationchange', onOrientationChange, false); - } - }; - - // Tracking the events. - var onOrientationChange = function(e) { - - self.previous = self.current; - self.current = window.orientation; - if(self.previous !== null && self.previous != self.current) { - conf.listener(e, self); - return; - } - - - }; - // Attach events. - if (window.DeviceOrientationEvent) { - window.addEventListener("orientationchange", onOrientationChange, false); - } - // Return this object. - return self; -}; - -Event.Gesture = Event.Gesture || {}; -Event.Gesture._gestureHandlers = Event.Gesture._gestureHandlers || {}; -Event.Gesture._gestureHandlers.orientation = root.orientation; - -return root; - -})(Event.proxy); - - -(function(){ - - /** - * @private - * @param {String} eventName - * @param {Function} handler - */ - function _removeEventListener(eventName, handler) { - if (!this.__eventListeners[eventName]) return; - - if (handler) { - fabric.util.removeFromArray(this.__eventListeners[eventName], handler); - } - else { - this.__eventListeners[eventName].length = 0; - } - } - - /** - * Observes specified event - * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) - * @memberOf fabric.Observable - * @alias on - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function that receives a notification when an event of the specified type occurs - * @return {Self} thisArg - * @chainable - */ - function observe(eventName, handler) { - if (!this.__eventListeners) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - if (arguments.length === 1) { - for (var prop in eventName) { - this.on(prop, eventName[prop]); - } - } - else { - if (!this.__eventListeners[eventName]) { - this.__eventListeners[eventName] = [ ]; - } - this.__eventListeners[eventName].push(handler); - } - return this; - } - - /** - * Stops event observing for a particular event handler. Calling this method - * without arguments removes all handlers for all events - * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) - * @memberOf fabric.Observable - * @alias off - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function to be deleted from EventListeners - * @return {Self} thisArg - * @chainable - */ - function stopObserving(eventName, handler) { - if (!this.__eventListeners) return; - - // remove all key/value pairs (event name -> event handler) - if (arguments.length === 0) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - else if (arguments.length === 1 && typeof arguments[0] === 'object') { - for (var prop in eventName) { - _removeEventListener.call(this, prop, eventName[prop]); - } - } - else { - _removeEventListener.call(this, eventName, handler); - } - return this; - } - - /** - * Fires event with an optional options object - * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) - * @memberOf fabric.Observable - * @alias trigger - * @param {String} eventName Event name to fire - * @param {Object} [options] Options object - * @return {Self} thisArg - * @chainable - */ - function fire(eventName, options) { - if (!this.__eventListeners) return; - - var listenersForEvent = this.__eventListeners[eventName]; - if (!listenersForEvent) return; - for (var i = 0, len = listenersForEvent.length; i < len; i++) { - // avoiding try/catch for perf. reasons - listenersForEvent[i].call(this, options || { }); - } - return this; - } - - /** - * @namespace fabric.Observable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} - * @see {@link http://fabricjs.com/events/|Events demo} - */ - fabric.Observable = { - observe: observe, - stopObserving: stopObserving, - fire: fire, - - on: observe, - off: stopObserving, - trigger: fire - }; -})(); - - -/** - * @namespace fabric.Collection - */ fabric.Collection = { - - /** - * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * Objects should be instances of (or inherit from) fabric.Object - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - */ - add: function () { - this._objects.push.apply(this._objects, arguments); - for (var i = 0, length = arguments.length; i < length; i++) { - this._onObjectAdded(arguments[i]); + add: function() { + this._objects.push.apply(this._objects, arguments); + for (var i = 0, length = arguments.length; i < length; i++) { + this._onObjectAdded(arguments[i]); + } + this.renderOnAddRemove && this.renderAll(); + return this; + }, + insertAt: function(object, index, nonSplicing) { + var objects = this.getObjects(); + if (nonSplicing) { + objects[index] = object; + } else { + objects.splice(index, 0, object); + } + this._onObjectAdded(object); + this.renderOnAddRemove && this.renderAll(); + return this; + }, + remove: function() { + var objects = this.getObjects(), index; + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } + } + this.renderOnAddRemove && this.renderAll(); + return this; + }, + forEachObject: function(callback, context) { + var objects = this.getObjects(), i = objects.length; + while (i--) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + getObjects: function(type) { + if (typeof type === "undefined") { + return this._objects; + } + return this._objects.filter(function(o) { + return o.type === type; + }); + }, + item: function(index) { + return this.getObjects()[index]; + }, + isEmpty: function() { + return this.getObjects().length === 0; + }, + size: function() { + return this.getObjects().length; + }, + contains: function(object) { + return this.getObjects().indexOf(object) > -1; + }, + complexity: function() { + return this.getObjects().reduce(function(memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); } - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) - * An object should be an instance of (or inherit from) fabric.Object - * @param {Object} object Object to insert - * @param {Number} index Index to insert object at - * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs - * @return {Self} thisArg - * @chainable - */ - insertAt: function (object, index, nonSplicing) { - var objects = this.getObjects(); - if (nonSplicing) { - objects[index] = object; - } - else { - objects.splice(index, 0, object); - } - this._onObjectAdded(object); - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - * @chainable - */ - remove: function() { - var objects = this.getObjects(), - index; - - for (var i = 0, length = arguments.length; i < length; i++) { - index = objects.indexOf(arguments[i]); - - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(arguments[i]); - } - } - - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Executes given function for each object in this group - * @param {Function} callback - * Callback invoked with current object as first argument, - * index - as second and an array of all objects - as third. - * Iteration happens in reverse order (for performance reasons). - * Callback is invoked in a context of Global Object (e.g. `window`) - * when no `context` argument is given - * - * @param {Object} context Context (aka thisObject) - * @return {Self} thisArg - */ - forEachObject: function(callback, context) { - var objects = this.getObjects(), - i = objects.length; - while (i--) { - callback.call(context, objects[i], i, objects); - } - return this; - }, - - /** - * Returns an array of children objects of this instance - * Type parameter introduced in 1.3.10 - * @param {String} [type] When specified, only objects of this type are returned - * @return {Array} - */ - getObjects: function(type) { - if (typeof type === 'undefined') { - return this._objects; - } - return this._objects.filter(function(o) { - return o.type === type; - }); - }, - - /** - * Returns object at specified index - * @param {Number} index - * @return {Self} thisArg - */ - item: function (index) { - return this.getObjects()[index]; - }, - - /** - * Returns true if collection contains no objects - * @return {Boolean} true if collection is empty - */ - isEmpty: function () { - return this.getObjects().length === 0; - }, - - /** - * Returns a size of a collection (i.e: length of an array containing its objects) - * @return {Number} Collection size - */ - size: function() { - return this.getObjects().length; - }, - - /** - * Returns true if collection contains an object - * @param {Object} object Object to check against - * @return {Boolean} `true` if collection contains an object - */ - contains: function(object) { - return this.getObjects().indexOf(object) > -1; - }, - - /** - * Returns number representation of a collection complexity - * @return {Number} complexity - */ - complexity: function () { - return this.getObjects().reduce(function (memo, current) { - memo += current.complexity ? current.complexity() : 0; - return memo; - }, 0); - } }; - (function(global) { - - var sqrt = Math.sqrt, - atan2 = Math.atan2, - PiBy180 = Math.PI / 180; - - /** - * @namespace fabric.util - */ - fabric.util = { - - /** - * Removes value from an array. - * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` - * @static - * @memberOf fabric.util - * @param {Array} array - * @param {Any} value - * @return {Array} original array - */ - removeFromArray: function(array, value) { - var idx = array.indexOf(value); - if (idx !== -1) { - array.splice(idx, 1); - } - return array; - }, - - /** - * Returns random number between 2 specified ones. - * @static - * @memberOf fabric.util - * @param {Number} min lower limit - * @param {Number} max upper limit - * @return {Number} random value (between min and max) - */ - getRandomInt: function(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }, - - /** - * Transforms degrees to radians. - * @static - * @memberOf fabric.util - * @param {Number} degrees value in degrees - * @return {Number} value in radians - */ - degreesToRadians: function(degrees) { - return degrees * PiBy180; - }, - - /** - * Transforms radians to degrees. - * @static - * @memberOf fabric.util - * @param {Number} radians value in radians - * @return {Number} value in degrees - */ - radiansToDegrees: function(radians) { - return radians / PiBy180; - }, - - /** - * Rotates `point` around `origin` with `radians` - * @static - * @memberOf fabric.util - * @param {fabric.Point} The point to rotate - * @param {fabric.Point} The origin of the rotation - * @param {Number} The radians of the angle for the rotation - * @return {fabric.Point} The new rotated point - */ - rotatePoint: function(point, origin, radians) { - var sin = Math.sin(radians), - cos = Math.cos(radians); - - point.subtractEquals(origin); - - var rx = point.x * cos - point.y * sin, - ry = point.x * sin + point.y * cos; - - return new fabric.Point(rx, ry).addEquals(origin); - }, - - /** - * Apply transform t to point p - * @static - * @memberOf fabric.util - * @param {fabric.Point} p The point to transform - * @param {Array} t The transform - * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied - * @return {fabric.Point} The transformed point - */ - transformPoint: function(p, t, ignoreOffset) { - if (ignoreOffset) { - return new fabric.Point( - t[0] * p.x + t[1] * p.y, - t[2] * p.x + t[3] * p.y - ); - } - return new fabric.Point( - t[0] * p.x + t[1] * p.y + t[4], - t[2] * p.x + t[3] * p.y + t[5] - ); - }, - - /** - * Invert transformation t - * @static - * @memberOf fabric.util - * @param {Array} t The transform - * @return {Array} The inverted transform - */ - invertTransform: function(t) { - var r = t.slice(), - a = 1 / (t[0] * t[3] - t[1] * t[2]); - r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; - var o = fabric.util.transformPoint({x: t[4], y: t[5]}, r); - r[4] = -o.x; - r[5] = -o.y; - return r; - }, - - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static - * @memberOf fabric.util - * @param {Number | String} number number to operate on - * @param {Number} fractionDigits number of fraction digits to "leave" - * @return {Number} - */ - toFixed: function(number, fractionDigits) { - return parseFloat(Number(number).toFixed(fractionDigits)); - }, - - /** - * Function which always returns `false`. - * @static - * @memberOf fabric.util - * @return {Boolean} - */ - falseFunction: function() { - return false; - }, - - /** - * Returns klass "Class" object of given namespace - * @memberOf fabric.util - * @param {String} type Type of object (eg. 'circle') - * @param {String} namespace Namespace to get klass "Class" object from - * @return {Object} klass "Class" - */ - getKlass: function(type, namespace) { - // capitalize first letter only - type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); - return fabric.util.resolveNamespace(namespace)[type]; - }, - - /** - * Returns object of given namespace - * @memberOf fabric.util - * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' - * @return {Object} Object for given namespace (default fabric) - */ - resolveNamespace: function(namespace) { - if (!namespace) { - return fabric; - } - - var parts = namespace.split('.'), - len = parts.length, - obj = global || fabric.window; - - for (var i = 0; i < len; ++i) { - obj = obj[parts[i]]; - } - - return obj; - }, - - /** - * Loads image element from given url and passes it to a callback - * @memberOf fabric.util - * @param {String} url URL representing an image - * @param {Function} callback Callback; invoked with loaded image - * @param {Any} [context] Context to invoke callback in - * @param {Object} [crossOrigin] crossOrigin value to set image element to - */ - loadImage: function(url, callback, context, crossOrigin) { - if (!url) { - callback && callback.call(context, url); - return; - } - - var img = fabric.util.createImage(); - - /** @ignore */ - img.onload = function () { - callback && callback.call(context, img); - img = img.onload = img.onerror = null; - }; - - /** @ignore */ - img.onerror = function() { - fabric.log('Error loading ' + img.src); - callback && callback.call(context, null, true); - img = img.onload = img.onerror = null; - }; - - // data-urls appear to be buggy with crossOrigin - // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 - // see https://code.google.com/p/chromium/issues/detail?id=315152 - // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 - if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { - img.crossOrigin = crossOrigin; - } - - img.src = url; - }, - - /** - * Creates corresponding fabric instances from their object representations - * @static - * @memberOf fabric.util - * @param {Array} objects Objects to enliven - * @param {Function} callback Callback to invoke when all objects are created - * @param {Function} [reviver] Method for further parsing of object elements, - * called after each fabric object created. - */ - enlivenObjects: function(objects, callback, namespace, reviver) { - objects = objects || [ ]; - - function onLoaded() { - if (++numLoadedObjects === numTotalObjects) { - callback && callback(enlivenedObjects); - } - } - - var enlivenedObjects = [ ], - numLoadedObjects = 0, - numTotalObjects = objects.length; - - if (!numTotalObjects) { - callback && callback(enlivenedObjects); - return; - } - - objects.forEach(function (o, index) { - // if sparse array - if (!o || !o.type) { - onLoaded(); - return; - } - var klass = fabric.util.getKlass(o.type, namespace); - if (klass.async) { - klass.fromObject(o, function (obj, error) { - if (!error) { - enlivenedObjects[index] = obj; - reviver && reviver(o, enlivenedObjects[index]); + var sqrt = Math.sqrt, atan2 = Math.atan2, PiBy180 = Math.PI / 180; + fabric.util = { + removeFromArray: function(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); } - onLoaded(); - }); + return array; + }, + getRandomInt: function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + degreesToRadians: function(degrees) { + return degrees * PiBy180; + }, + radiansToDegrees: function(radians) { + return radians / PiBy180; + }, + rotatePoint: function(point, origin, radians) { + var sin = Math.sin(radians), cos = Math.cos(radians); + point.subtractEquals(origin); + var rx = point.x * cos - point.y * sin, ry = point.x * sin + point.y * cos; + return new fabric.Point(rx, ry).addEquals(origin); + }, + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point(t[0] * p.x + t[1] * p.y, t[2] * p.x + t[3] * p.y); + } + return new fabric.Point(t[0] * p.x + t[1] * p.y + t[4], t[2] * p.x + t[3] * p.y + t[5]); + }, + invertTransform: function(t) { + var r = t.slice(), a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [ a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0 ]; + var o = fabric.util.transformPoint({ + x: t[4], + y: t[5] + }, r); + r[4] = -o.x; + r[5] = -o.y; + return r; + }, + toFixed: function(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + }, + falseFunction: function() { + return false; + }, + getKlass: function(type, namespace) { + type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); + return fabric.util.resolveNamespace(namespace)[type]; + }, + resolveNamespace: function(namespace) { + if (!namespace) { + return fabric; + } + var parts = namespace.split("."), len = parts.length, obj = global || fabric.window; + for (var i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + return obj; + }, + loadImage: function(url, callback, context, crossOrigin) { + if (!url) { + callback && callback.call(context, url); + return; + } + var img = fabric.util.createImage(); + img.onload = function() { + callback && callback.call(context, img); + img = img.onload = img.onerror = null; + }; + img.onerror = function() { + fabric.log("Error loading " + img.src); + callback && callback.call(context, null, true); + img = img.onload = img.onerror = null; + }; + if (url.indexOf("data") !== 0 && typeof crossOrigin !== "undefined") { + img.crossOrigin = crossOrigin; + } + img.src = url; + }, + enlivenObjects: function(objects, callback, namespace, reviver) { + objects = objects || []; + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + callback && callback(enlivenedObjects); + } + } + var enlivenedObjects = [], numLoadedObjects = 0, numTotalObjects = objects.length; + if (!numTotalObjects) { + callback && callback(enlivenedObjects); + return; + } + objects.forEach(function(o, index) { + if (!o || !o.type) { + onLoaded(); + return; + } + var klass = fabric.util.getKlass(o.type, namespace); + if (klass.async) { + klass.fromObject(o, function(obj, error) { + if (!error) { + enlivenedObjects[index] = obj; + reviver && reviver(o, enlivenedObjects[index]); + } + onLoaded(); + }); + } else { + enlivenedObjects[index] = klass.fromObject(o); + reviver && reviver(o, enlivenedObjects[index]); + onLoaded(); + } + }); + }, + groupSVGElements: function(elements, options, path) { + var object; + if (elements.length > 1) { + object = new fabric.PathGroup(elements, options); + } else { + object = elements[0]; + } + if (typeof path !== "undefined") { + object.setSourcePath(path); + } + return object; + }, + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === "[object Array]") { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } + } + } + }, + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, dy = y2 - y, len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, draw = true; + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; + } + ctx[draw ? "lineTo" : "moveTo"](x, 0); + draw = !draw; + } + ctx.restore(); + }, + createCanvasElement: function(canvasEl) { + canvasEl || (canvasEl = fabric.document.createElement("canvas")); + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== "undefined") { + G_vmlCanvasManager.initElement(canvasEl); + } + return canvasEl; + }, + createImage: function() { + return fabric.isLikelyNode ? new (require("canvas").Image)() : fabric.document.createElement("img"); + }, + createAccessors: function(klass) { + var proto = klass.prototype; + for (var i = proto.stateProperties.length; i--; ) { + var propName = proto.stateProperties[i], capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), setterName = "set" + capitalizedPropName, getterName = "get" + capitalizedPropName; + if (!proto[getterName]) { + proto[getterName] = function(property) { + return new Function('return this.get("' + property + '")'); + }(propName); + } + if (!proto[setterName]) { + proto[setterName] = function(property) { + return new Function("value", 'return this.set("' + property + '", value)'); + }(propName); + } + } + }, + clipContext: function(receiver, ctx) { + ctx.save(); + ctx.beginPath(); + receiver.clipTo(ctx); + ctx.clip(); + }, + multiplyTransformMatrices: function(matrixA, matrixB) { + var a = [ [ matrixA[0], matrixA[2], matrixA[4] ], [ matrixA[1], matrixA[3], matrixA[5] ], [ 0, 0, 1 ] ], b = [ [ matrixB[0], matrixB[2], matrixB[4] ], [ matrixB[1], matrixB[3], matrixB[5] ], [ 0, 0, 1 ] ], result = []; + for (var r = 0; r < 3; r++) { + result[r] = []; + for (var c = 0; c < 3; c++) { + var sum = 0; + for (var k = 0; k < 3; k++) { + sum += a[r][k] * b[k][c]; + } + result[r][c] = sum; + } + } + return [ result[0][0], result[1][0], result[0][1], result[1][1], result[0][2], result[1][2] ]; + }, + getFunctionBody: function(fn) { + return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; + }, + normalizePoints: function(points, options) { + var minX = fabric.util.array.min(points, "x"), minY = fabric.util.array.min(points, "y"); + minX = minX < 0 ? minX : 0; + minY = minX < 0 ? minY : 0; + for (var i = 0, len = points.length; i < len; i++) { + points[i].x -= options.width / 2 + minX || 0; + points[i].y -= options.height / 2 + minY || 0; + } + }, + isTransparent: function(ctx, x, y, tolerance) { + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } else { + y = 0; + } + } + var _isTransparent = true, imageData = ctx.getImageData(x, y, tolerance * 2 || 1, tolerance * 2 || 1); + for (var i = 3, l = imageData.data.length; i < l; i += 4) { + var temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) break; + } + imageData = null; + return _isTransparent; } - else { - enlivenedObjects[index] = klass.fromObject(o); - reviver && reviver(o, enlivenedObjects[index]); - onLoaded(); - } - }); - }, - - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @param {Object} [options] Options object - * @return {fabric.Object|fabric.PathGroup} - */ - groupSVGElements: function(elements, options, path) { - var object; - - if (elements.length > 1) { - object = new fabric.PathGroup(elements, options); - } - else { - object = elements[0]; - } - - if (typeof path !== 'undefined') { - object.setSourcePath(path); - } - return object; - }, - - /** - * Populates an object with properties of another object - * @static - * @memberOf fabric.util - * @param {Object} source Source object - * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include - */ - populateWithProperties: function(source, destination, properties) { - if (properties && Object.prototype.toString.call(properties) === '[object Array]') { - for (var i = 0, len = properties.length; i < len; i++) { - if (properties[i] in source) { - destination[properties[i]] = source[properties[i]]; - } - } - } - }, - - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x start x coordinate - * @param {Number} y start y coordinate - * @param {Number} x2 end x coordinate - * @param {Number} y2 end y coordinate - * @param {Array} da dash array pattern - */ - drawDashedLine: function(ctx, x, y, x2, y2, da) { - var dx = x2 - x, - dy = y2 - y, - len = sqrt(dx * dx + dy * dy), - rot = atan2(dy, dx), - dc = da.length, - di = 0, - draw = true; - - ctx.save(); - ctx.translate(x, y); - ctx.moveTo(0, 0); - ctx.rotate(rot); - - x = 0; - while (len > x) { - x += da[di++ % dc]; - if (x > len) { - x = len; - } - ctx[draw ? 'lineTo' : 'moveTo'](x, 0); - draw = !draw; - } - - ctx.restore(); - }, - - /** - * Creates canvas element and initializes it via excanvas if necessary - * @static - * @memberOf fabric.util - * @param {CanvasElement} [canvasEl] optional canvas element to initialize; - * when not given, element is created implicitly - * @return {CanvasElement} initialized canvas element - */ - createCanvasElement: function(canvasEl) { - canvasEl || (canvasEl = fabric.document.createElement('canvas')); - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - return canvasEl; - }, - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - createImage: function() { - return fabric.isLikelyNode - ? new (require('canvas').Image)() - : fabric.document.createElement('img'); - }, - - /** - * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array - * @static - * @memberOf fabric.util - * @param {Object} klass "Class" to create accessors for - */ - createAccessors: function(klass) { - var proto = klass.prototype; - - for (var i = proto.stateProperties.length; i--; ) { - - var propName = proto.stateProperties[i], - capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), - setterName = 'set' + capitalizedPropName, - getterName = 'get' + capitalizedPropName; - - // using `new Function` for better introspection - if (!proto[getterName]) { - proto[getterName] = (function(property) { - return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - return new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } - }, - - /** - * @static - * @memberOf fabric.util - * @param {fabric.Object} receiver Object implementing `clipTo` method - * @param {CanvasRenderingContext2D} ctx Context to clip - */ - clipContext: function(receiver, ctx) { - ctx.save(); - ctx.beginPath(); - receiver.clipTo(ctx); - ctx.clip(); - }, - - /** - * Multiply matrix A by matrix B to nest transformations - * @static - * @memberOf fabric.util - * @param {Array} matrixA First transformMatrix - * @param {Array} matrixB Second transformMatrix - * @return {Array} The product of the two transform matrices - */ - multiplyTransformMatrices: function(matrixA, matrixB) { - // Matrix multiply matrixA * matrixB - var a = [ - [matrixA[0], matrixA[2], matrixA[4]], - [matrixA[1], matrixA[3], matrixA[5]], - [0, 0, 1 ] - ], - - b = [ - [matrixB[0], matrixB[2], matrixB[4]], - [matrixB[1], matrixB[3], matrixB[5]], - [0, 0, 1 ] - ], - - result = []; - - for (var r = 0; r < 3; r++) { - result[r] = []; - for (var c = 0; c < 3; c++) { - var sum = 0; - for (var k = 0; k < 3; k++) { - sum += a[r][k] * b[k][c]; - } - - result[r][c] = sum; - } - } - - return [ - result[0][0], - result[1][0], - result[0][1], - result[1][1], - result[0][2], - result[1][2] - ]; - }, - - /** - * Returns string representation of function body - * @param {Function} fn Function to get body of - * @return {String} Function body - */ - getFunctionBody: function(fn) { - return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; - }, - - /** - * Normalizes polygon/polyline points according to their dimensions - * @param {Array} points - * @param {Object} options - */ - normalizePoints: function(points, options) { - var minX = fabric.util.array.min(points, 'x'), - minY = fabric.util.array.min(points, 'y'); - - minX = minX < 0 ? minX : 0; - minY = minX < 0 ? minY : 0; - - for (var i = 0, len = points.length; i < len; i++) { - // normalize coordinates, according to containing box - // (dimensions of which are passed via `options`) - points[i].x -= (options.width / 2 + minX) || 0; - points[i].y -= (options.height / 2 + minY) || 0; - } - }, - - /** - * Returns true if context has transparent pixel - * at specified location (taking tolerance into account) - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x x coordinate - * @param {Number} y y coordinate - * @param {Number} tolerance Tolerance - */ - isTransparent: function(ctx, x, y, tolerance) { - - // If tolerance is > 0 adjust start coords to take into account. - // If moves off Canvas fix to 0 - if (tolerance > 0) { - if (x > tolerance) { - x -= tolerance; - } - else { - x = 0; - } - if (y > tolerance) { - y -= tolerance; - } - else { - y = 0; - } - } - - var _isTransparent = true, - imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); - - // Split image data - for tolerance > 1, pixelDataSize = 4; - for (var i = 3, l = imageData.data.length; i < l; i += 4) { - var temp = imageData.data[i]; - _isTransparent = temp <= 0; - if (_isTransparent === false) break; // Stop if colour found - } - - imageData = null; - - return _isTransparent; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - + }; +})(typeof exports !== "undefined" ? exports : this); (function() { - - var arcToSegmentsCache = { }, - segmentToBezierCache = { }, - _join = Array.prototype.join, - argsString; - - // Generous contribution by Raph Levien, from libsvg-0.1.0.tar.gz - function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { - - argsString = _join.call(arguments); - - if (arcToSegmentsCache[argsString]) { - return arcToSegmentsCache[argsString]; - } - - var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y), - - d = (coords.x1 - coords.x0) * (coords.x1 - coords.x0) + - (coords.y1 - coords.y0) * (coords.y1 - coords.y0), - - sfactorSq = 1 / d - 0.25; - - if (sfactorSq < 0) { - sfactorSq = 0; - } - - var sfactor = Math.sqrt(sfactorSq); - if (sweep === large) { - sfactor = -sfactor; - } - - var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1 - coords.y0), - yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1 - coords.x0), - th0 = Math.atan2(coords.y0 - yc, coords.x0 - xc), - th1 = Math.atan2(coords.y1 - yc, coords.x1 - xc), - thArc = th1 - th0; - - if (thArc < 0 && sweep === 1) { - thArc += 2 * Math.PI; - } - else if (thArc > 0 && sweep === 0) { - thArc -= 2 * Math.PI; - } - - var segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001))), - result = []; - - for (var i = 0; i < segments; i++) { - var th2 = th0 + i * thArc / segments, - th3 = th0 + (i + 1) * thArc / segments; - - result[i] = [xc, yc, th2, th3, rx, ry, coords.sinTh, coords.cosTh]; - } - - arcToSegmentsCache[argsString] = result; - return result; - } - - function getXYCoords(rotateX, rx, ry, ox, oy, x, y) { - - var th = rotateX * (Math.PI / 180), - sinTh = Math.sin(th), - cosTh = Math.cos(th); - - rx = Math.abs(rx); - ry = Math.abs(ry); - - var px = cosTh * (ox - x) * 0.5 + sinTh * (oy - y) * 0.5, - py = cosTh * (oy - y) * 0.5 - sinTh * (ox - x) * 0.5, - pl = (px * px) / (rx * rx) + (py * py) / (ry * ry); - - if (pl > 1) { - pl = Math.sqrt(pl); - rx *= pl; - ry *= pl; - } - - var a00 = cosTh / rx, - a01 = sinTh / rx, - a10 = (-sinTh) / ry, - a11 = (cosTh) / ry; - - return { - x0: a00 * ox + a01 * oy, - y0: a10 * ox + a11 * oy, - x1: a00 * x + a01 * y, - y1: a10 * x + a11 * y, - sinTh: sinTh, - cosTh: cosTh - }; - } - - function segmentToBezier(cx, cy, th0, th1, rx, ry, sinTh, cosTh) { - argsString = _join.call(arguments); - - if (segmentToBezierCache[argsString]) { - return segmentToBezierCache[argsString]; - } - - var a00 = cosTh * rx, - a01 = -sinTh * ry, - a10 = sinTh * rx, - a11 = cosTh * ry, - thHalf = 0.5 * (th1 - th0), - t = (8 / 3) * Math.sin(thHalf * 0.5) * - Math.sin(thHalf * 0.5) / Math.sin(thHalf), - - x1 = cx + Math.cos(th0) - t * Math.sin(th0), - y1 = cy + Math.sin(th0) + t * Math.cos(th0), - x3 = cx + Math.cos(th1), - y3 = cy + Math.sin(th1), - x2 = x3 + t * Math.sin(th1), - y2 = y3 - t * Math.cos(th1); - - segmentToBezierCache[argsString] = [ - a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, - a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, - a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 - ]; - - return segmentToBezierCache[argsString]; - } - - /** - * Draws arc - * @param {CanvasRenderingContext2D} ctx - * @param {Number} x - * @param {Number} y - * @param {Array} coords - */ - fabric.util.drawArc = function(ctx, x, y, coords) { - var rx = coords[0], - ry = coords[1], - rot = coords[2], - large = coords[3], - sweep = coords[4], - ex = coords[5], - ey = coords[6], - segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); - - for (var i = 0; i < segs.length; i++) { - var bez = segmentToBezier.apply(this, segs[i]); - ctx.bezierCurveTo.apply(ctx, bez); - } - }; -})(); - - -(function() { - - var slice = Array.prototype.slice; - - /* _ES5_COMPAT_START_ */ - - if (!Array.prototype.indexOf) { - /** - * Finds index of an element in an array - * @param {Any} searchElement - * @param {Number} [fromIndex] - * @return {Number} - */ - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - if (this === void 0 || this === null) { - throw new TypeError(); - } - var t = Object(this), len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n !== n) { // shortcut for verifying if it's NaN - n = 0; + var arcToSegmentsCache = {}, segmentToBezierCache = {}, _join = Array.prototype.join, argsString; + function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { + argsString = _join.call(arguments); + if (arcToSegmentsCache[argsString]) { + return arcToSegmentsCache[argsString]; } - else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); + var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y), d = (coords.x1 - coords.x0) * (coords.x1 - coords.x0) + (coords.y1 - coords.y0) * (coords.y1 - coords.y0), sfactorSq = 1 / d - .25; + if (sfactorSq < 0) { + sfactorSq = 0; } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; + var sfactor = Math.sqrt(sfactorSq); + if (sweep === large) { + sfactor = -sfactor; } - } - return -1; - }; - } - - if (!Array.prototype.forEach) { - /** - * Iterates an array, invoking callback for each element - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.forEach = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - fn.call(context, this[i], i, this); + var xc = .5 * (coords.x0 + coords.x1) - sfactor * (coords.y1 - coords.y0), yc = .5 * (coords.y0 + coords.y1) + sfactor * (coords.x1 - coords.x0), th0 = Math.atan2(coords.y0 - yc, coords.x0 - xc), th1 = Math.atan2(coords.y1 - yc, coords.x1 - xc), thArc = th1 - th0; + if (thArc < 0 && sweep === 1) { + thArc += 2 * Math.PI; + } else if (thArc > 0 && sweep === 0) { + thArc -= 2 * Math.PI; } - } - }; - } - - if (!Array.prototype.map) { - /** - * Returns a result of iterating over an array, invoking callback for each element - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.map = function(fn, context) { - var result = [ ]; - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - result[i] = fn.call(context, this[i], i, this); + var segments = Math.ceil(Math.abs(thArc / (Math.PI * .5 + .001))), result = []; + for (var i = 0; i < segments; i++) { + var th2 = th0 + i * thArc / segments, th3 = th0 + (i + 1) * thArc / segments; + result[i] = [ xc, yc, th2, th3, rx, ry, coords.sinTh, coords.cosTh ]; } - } - return result; - }; - } - - if (!Array.prototype.every) { - /** - * Returns true if a callback returns truthy value for all elements in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Boolean} - */ - Array.prototype.every = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this && !fn.call(context, this[i], i, this)) { - return false; - } - } - return true; - }; - } - - if (!Array.prototype.some) { - /** - * Returns true if a callback returns truthy value for at least one element in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Boolean} - */ - Array.prototype.some = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this && fn.call(context, this[i], i, this)) { - return true; - } - } - return false; - }; - } - - if (!Array.prototype.filter) { - /** - * Returns the result of iterating over elements in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.filter = function(fn, context) { - var result = [ ], val; - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - val = this[i]; // in case fn mutates this - if (fn.call(context, val, i, this)) { - result.push(val); - } - } - } - return result; - }; - } - - if (!Array.prototype.reduce) { - /** - * Returns "folded" (reduced) result of iterating over elements in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Any} - */ - Array.prototype.reduce = function(fn /*, initial*/) { - var len = this.length >>> 0, - i = 0, - rv; - - if (arguments.length > 1) { - rv = arguments[1]; - } - else { - do { - if (i in this) { - rv = this[i++]; - break; - } - // if array contains no values, no initial value to return - if (++i >= len) { - throw new TypeError(); - } - } - while (true); - } - for (; i < len; i++) { - if (i in this) { - rv = fn.call(null, rv, this[i], i, this); - } - } - return rv; - }; - } - - /* _ES5_COMPAT_END_ */ - - /** - * Invokes method on all items in a given array - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} method Name of a method to invoke - * @return {Array} - */ - function invoke(array, method) { - var args = slice.call(arguments, 2), result = [ ]; - for (var i = 0, len = array.length; i < len; i++) { - result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + arcToSegmentsCache[argsString] = result; + return result; } - return result; - } - - /** - * Finds maximum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function max(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 >= value2; - }); - } - - /** - * Finds minimum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {Any} - */ - function min(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 < value2; - }); - } - - /** - * @private - */ - function find(array, byProperty, condition) { - if (!array || array.length === 0) return; - - var i = array.length - 1, - result = byProperty ? array[i][byProperty] : array[i]; - if (byProperty) { - while (i--) { - if (condition(array[i][byProperty], result)) { - result = array[i][byProperty]; + function getXYCoords(rotateX, rx, ry, ox, oy, x, y) { + var th = rotateX * (Math.PI / 180), sinTh = Math.sin(th), cosTh = Math.cos(th); + rx = Math.abs(rx); + ry = Math.abs(ry); + var px = cosTh * (ox - x) * .5 + sinTh * (oy - y) * .5, py = cosTh * (oy - y) * .5 - sinTh * (ox - x) * .5, pl = px * px / (rx * rx) + py * py / (ry * ry); + if (pl > 1) { + pl = Math.sqrt(pl); + rx *= pl; + ry *= pl; } - } - } - else { - while (i--) { - if (condition(array[i], result)) { - result = array[i]; - } - } - } - return result; - } - - /** - * @namespace fabric.util.array - */ - fabric.util.array = { - invoke: invoke, - min: min, - max: max - }; - -})(); - - -(function(){ - - /** - * Copies all enumerable properties of one object to another - * @memberOf fabric.util.object - * @param {Object} destination Where to copy to - * @param {Object} source Where to copy from - * @return {Object} - */ - function extend(destination, source) { - // JScript DontEnum bug is not taken care of - for (var property in source) { - destination[property] = source[property]; - } - return destination; - } - - /** - * Creates an empty object and copies all enumerable properties of another object to it - * @memberOf fabric.util.object - * @param {Object} object Object to clone - * @return {Object} - */ - function clone(object) { - return extend({ }, object); - } - - /** @namespace fabric.util.object */ - fabric.util.object = { - extend: extend, - clone: clone - }; - -})(); - - -(function() { - - /* _ES5_COMPAT_START_ */ - if (!String.prototype.trim) { - /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see String#trim on MDN - */ - String.prototype.trim = function () { - // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now - return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); - }; - } - /* _ES5_COMPAT_END_ */ - - /** - * Camelizes a string - * @memberOf fabric.util.string - * @param {String} string String to camelize - * @return {String} Camelized version of a string - */ - function camelize(string) { - return string.replace(/-+(.)?/g, function(match, character) { - return character ? character.toUpperCase() : ''; - }); - } - - /** - * Capitalizes a string - * @memberOf fabric.util.string - * @param {String} string String to capitalize - * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized - * and other letters stay untouched, if false first letter is capitalized - * and other letters are converted to lowercase. - * @return {String} Capitalized version of a string - */ - function capitalize(string, firstLetterOnly) { - return string.charAt(0).toUpperCase() + - (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); - } - - /** - * Escapes XML in a string - * @memberOf fabric.util.string - * @param {String} string String to escape - * @return {String} Escaped version of a string - */ - function escapeXml(string) { - return string.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>'); - } - - /** - * String utilities - * @namespace fabric.util.string - */ - fabric.util.string = { - camelize: camelize, - capitalize: capitalize, - escapeXml: escapeXml - }; -}()); - - -/* _ES5_COMPAT_START_ */ -(function() { - - var slice = Array.prototype.slice, - apply = Function.prototype.apply, - Dummy = function() { }; - - if (!Function.prototype.bind) { - /** - * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) - * @see Function#bind on MDN - * @param {Object} thisArg Object to bind function to - * @param {Any[]} [...] Values to pass to a bound function - * @return {Function} - */ - Function.prototype.bind = function(thisArg) { - var _this = this, args = slice.call(arguments, 1), bound; - if (args.length) { - bound = function() { - return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); + var a00 = cosTh / rx, a01 = sinTh / rx, a10 = -sinTh / ry, a11 = cosTh / ry; + return { + x0: a00 * ox + a01 * oy, + y0: a10 * ox + a11 * oy, + x1: a00 * x + a01 * y, + y1: a10 * x + a11 * y, + sinTh: sinTh, + cosTh: cosTh }; - } - else { - /** @ignore */ - bound = function() { - return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); - }; - } - Dummy.prototype = this.prototype; - bound.prototype = new Dummy(); - - return bound; + } + function segmentToBezier(cx, cy, th0, th1, rx, ry, sinTh, cosTh) { + argsString = _join.call(arguments); + if (segmentToBezierCache[argsString]) { + return segmentToBezierCache[argsString]; + } + var a00 = cosTh * rx, a01 = -sinTh * ry, a10 = sinTh * rx, a11 = cosTh * ry, thHalf = .5 * (th1 - th0), t = 8 / 3 * Math.sin(thHalf * .5) * Math.sin(thHalf * .5) / Math.sin(thHalf), x1 = cx + Math.cos(th0) - t * Math.sin(th0), y1 = cy + Math.sin(th0) + t * Math.cos(th0), x3 = cx + Math.cos(th1), y3 = cy + Math.sin(th1), x2 = x3 + t * Math.sin(th1), y2 = y3 - t * Math.cos(th1); + segmentToBezierCache[argsString] = [ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 ]; + return segmentToBezierCache[argsString]; + } + fabric.util.drawArc = function(ctx, x, y, coords) { + var rx = coords[0], ry = coords[1], rot = coords[2], large = coords[3], sweep = coords[4], ex = coords[5], ey = coords[6], segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); + for (var i = 0; i < segs.length; i++) { + var bez = segmentToBezier.apply(this, segs[i]); + ctx.bezierCurveTo.apply(ctx, bez); + } }; - } - })(); -/* _ES5_COMPAT_END_ */ - (function() { + var slice = Array.prototype.slice; + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(searchElement) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + var t = Object(this), len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { + n = 0; + } else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (;k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + }; + } + if (!Array.prototype.forEach) { + Array.prototype.forEach = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + fn.call(context, this[i], i, this); + } + } + }; + } + if (!Array.prototype.map) { + Array.prototype.map = function(fn, context) { + var result = []; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + result[i] = fn.call(context, this[i], i, this); + } + } + return result; + }; + } + if (!Array.prototype.every) { + Array.prototype.every = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && !fn.call(context, this[i], i, this)) { + return false; + } + } + return true; + }; + } + if (!Array.prototype.some) { + Array.prototype.some = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && fn.call(context, this[i], i, this)) { + return true; + } + } + return false; + }; + } + if (!Array.prototype.filter) { + Array.prototype.filter = function(fn, context) { + var result = [], val; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + val = this[i]; + if (fn.call(context, val, i, this)) { + result.push(val); + } + } + } + return result; + }; + } + if (!Array.prototype.reduce) { + Array.prototype.reduce = function(fn) { + var len = this.length >>> 0, i = 0, rv; + if (arguments.length > 1) { + rv = arguments[1]; + } else { + do { + if (i in this) { + rv = this[i++]; + break; + } + if (++i >= len) { + throw new TypeError(); + } + } while (true); + } + for (;i < len; i++) { + if (i in this) { + rv = fn.call(null, rv, this[i], i, this); + } + } + return rv; + }; + } + function invoke(array, method) { + var args = slice.call(arguments, 2), result = []; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + function max(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 >= value2; + }); + } + function min(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 < value2; + }); + } + function find(array, byProperty, condition) { + if (!array || array.length === 0) return; + var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (condition(array[i][byProperty], result)) { + result = array[i][byProperty]; + } + } + } else { + while (i--) { + if (condition(array[i], result)) { + result = array[i]; + } + } + } + return result; + } + fabric.util.array = { + invoke: invoke, + min: min, + max: max + }; +})(); - var slice = Array.prototype.slice, emptyFunction = function() { }, +(function() { + function extend(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; + } + function clone(object) { + return extend({}, object); + } + fabric.util.object = { + extend: extend, + clone: clone + }; +})(); - IS_DONTENUM_BUGGY = (function(){ - for (var p in { toString: 1 }) { - if (p === 'toString') return false; +(function() { + if (!String.prototype.trim) { + String.prototype.trim = function() { + return this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""); + }; + } + function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ""; + }); + } + function capitalize(string, firstLetterOnly) { + return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); + } + function escapeXml(string) { + return string.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(//g, ">"); + } + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml + }; +})(); + +(function() { + var slice = Array.prototype.slice, apply = Function.prototype.apply, Dummy = function() {}; + if (!Function.prototype.bind) { + Function.prototype.bind = function(thisArg) { + var _this = this, args = slice.call(arguments, 1), bound; + if (args.length) { + bound = function() { + return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); + }; + } else { + bound = function() { + return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); + }; + } + Dummy.prototype = this.prototype; + bound.prototype = new Dummy(); + return bound; + }; + } +})(); + +(function() { + var slice = Array.prototype.slice, emptyFunction = function() {}, IS_DONTENUM_BUGGY = function() { + for (var p in { + toString: 1 + }) { + if (p === "toString") return false; } return true; - })(), - - /** @ignore */ - addMethods = function(klass, source, parent) { + }(), addMethods = function(klass, source, parent) { for (var property in source) { - - if (property in klass.prototype && - typeof klass.prototype[property] === 'function' && - (source[property] + '').indexOf('callSuper') > -1) { - - klass.prototype[property] = (function(property) { - return function() { - - var superclass = this.constructor.superclass; - this.constructor.superclass = parent; - var returnValue = source[property].apply(this, arguments); - this.constructor.superclass = superclass; - - if (property !== 'initialize') { - return returnValue; + if (property in klass.prototype && typeof klass.prototype[property] === "function" && (source[property] + "").indexOf("callSuper") > -1) { + klass.prototype[property] = function(property) { + return function() { + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + if (property !== "initialize") { + return returnValue; + } + }; + }(property); + } else { + klass.prototype[property] = source[property]; + } + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; } - }; - })(property); - } - else { - klass.prototype[property] = source[property]; - } - - if (IS_DONTENUM_BUGGY) { - if (source.toString !== Object.prototype.toString) { - klass.prototype.toString = source.toString; } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } } - }; - - function Subclass() { } - - function callSuper(methodName) { - var fn = this.constructor.superclass.prototype[methodName]; - return (arguments.length > 1) - ? fn.apply(this, slice.call(arguments, 1)) - : fn.call(this); - } - - /** - * Helper for creation of "classes". - * @memberOf fabric.util - * @param parent optional "Class" to inherit from - * @param properties Properties shared by all instances of this class - * (be careful modifying objects defined here as this would affect all instances) - */ - function createClass() { - var parent = null, - properties = slice.call(arguments, 0); - - if (typeof properties[0] === 'function') { - parent = properties.shift(); + }; + function Subclass() {} + function callSuper(methodName) { + var fn = this.constructor.superclass.prototype[methodName]; + return arguments.length > 1 ? fn.apply(this, slice.call(arguments, 1)) : fn.call(this); } - function klass() { - this.initialize.apply(this, arguments); + function createClass() { + var parent = null, properties = slice.call(arguments, 0); + if (typeof properties[0] === "function") { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + klass.superclass = parent; + klass.subclasses = []; + if (parent) { + Subclass.prototype = parent.prototype; + klass.prototype = new Subclass(); + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + klass.prototype.callSuper = callSuper; + return klass; } - - klass.superclass = parent; - klass.subclasses = [ ]; - - if (parent) { - Subclass.prototype = parent.prototype; - klass.prototype = new Subclass(); - parent.subclasses.push(klass); - } - for (var i = 0, length = properties.length; i < length; i++) { - addMethods(klass, properties[i], parent); - } - if (!klass.prototype.initialize) { - klass.prototype.initialize = emptyFunction; - } - klass.prototype.constructor = klass; - klass.prototype.callSuper = callSuper; - return klass; - } - - fabric.util.createClass = createClass; + fabric.util.createClass = createClass; })(); - -(function () { - - var unknown = 'unknown'; - - /* EVENT HANDLING */ - - function areHostMethods(object) { - var methodNames = Array.prototype.slice.call(arguments, 1), - t, i, len = methodNames.length; - for (i = 0; i < len; i++) { - t = typeof object[methodNames[i]]; - if (!(/^(?:function|object|unknown)$/).test(t)) return false; - } - return true; - } - - /** @ignore */ - var getElement, - setElement, - getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); - - (function () { - var elements = { }; - /** @ignore */ - getElement = function (uid) { - return elements[uid]; - }; - /** @ignore */ - setElement = function (uid, element) { - elements[uid] = element; - }; - })(); - - function createListener(uid, handler) { - return { - handler: handler, - wrappedHandler: createWrappedHandler(uid, handler) - }; - } - - function createWrappedHandler(uid, handler) { - return function (e) { - handler.call(getElement(uid), e || fabric.window.event); - }; - } - - function createDispatcher(uid, eventName) { - return function (e) { - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - handlersForEvent[i].call(this, e || fabric.window.event); - } - } - }; - } - - var shouldUseAddListenerRemoveListener = ( - areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && - areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), - - shouldUseAttachEventDetachEvent = ( - areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && - areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), - - // IE branch - listeners = { }, - - // DOM L0 branch - handlers = { }, - - addListener, removeListener; - - if (shouldUseAddListenerRemoveListener) { - /** @ignore */ - addListener = function (element, eventName, handler) { - element.addEventListener(eventName, handler, false); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - element.removeEventListener(eventName, handler, false); - }; - } - - else if (shouldUseAttachEventDetachEvent) { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - setElement(uid, element); - if (!listeners[uid]) { - listeners[uid] = { }; - } - if (!listeners[uid][eventName]) { - listeners[uid][eventName] = [ ]; - - } - var listener = createListener(uid, handler); - listeners[uid][eventName].push(listener); - element.attachEvent('on' + eventName, listener.wrappedHandler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element), listener; - if (listeners[uid] && listeners[uid][eventName]) { - for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { - listener = listeners[uid][eventName][i]; - if (listener && listener.handler === handler) { - element.detachEvent('on' + eventName, listener.wrappedHandler); - listeners[uid][eventName][i] = null; - } - } - } - }; - } - else { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (!handlers[uid]) { - handlers[uid] = { }; - } - if (!handlers[uid][eventName]) { - handlers[uid][eventName] = [ ]; - var existingHandler = element['on' + eventName]; - if (existingHandler) { - handlers[uid][eventName].push(existingHandler); - } - element['on' + eventName] = createDispatcher(uid, eventName); - } - handlers[uid][eventName].push(handler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - if (handlersForEvent[i] === handler) { - handlersForEvent.splice(i, 1); - } - } - } - }; - } - - /** - * Adds an event listener to an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.addListener = addListener; - - /** - * Removes an event listener from an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.removeListener = removeListener; - - /** - * Cross-browser wrapper for getting event's coordinates - * @memberOf fabric.util - * @param {Event} event Event object - * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn - */ - function getPointer(event, upperCanvasEl) { - event || (event = fabric.window.event); - - var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null), - - scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); - - return { - x: pointerX(event) + scroll.left, - y: pointerY(event) + scroll.top - }; - } - - var pointerX = function(event) { - // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) - // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] - // need to investigate later - return (typeof event.clientX !== unknown ? event.clientX : 0); - }, - - pointerY = function(event) { - return (typeof event.clientY !== unknown ? event.clientY : 0); - }; - - function _getPointer(event, pageProp, clientProp) { - var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; - - return (event[touchProp] && event[touchProp][0] - ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) - || event[clientProp] - : event[clientProp]); - } - - if (fabric.isTouchSupported) { - pointerX = function(event) { - return _getPointer(event, 'pageX', 'clientX'); - }; - pointerY = function(event) { - return _getPointer(event, 'pageY', 'clientY'); - }; - } - - fabric.util.getPointer = getPointer; - - fabric.util.object.extend(fabric.util, fabric.Observable); - -})(); - - -(function () { - - /** - * Cross-browser wrapper for setting element's style - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {Object} styles - * @return {HTMLElement} Element that was passed as a first argument - */ - function setStyle(element, styles) { - var elementStyle = element.style; - if (!elementStyle) { - return element; - } - if (typeof styles === 'string') { - element.style.cssText += ';' + styles; - return styles.indexOf('opacity') > -1 - ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) - : element; - } - for (var property in styles) { - if (property === 'opacity') { - setOpacity(element, styles[property]); - } - else { - var normalizedProperty = (property === 'float' || property === 'cssFloat') - ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') - : property; - elementStyle[normalizedProperty] = styles[property]; - } - } - return element; - } - - var parseEl = fabric.document.createElement('div'), - supportsOpacity = typeof parseEl.style.opacity === 'string', - supportsFilters = typeof parseEl.style.filter === 'string', - reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, - - /** @ignore */ - setOpacity = function (element) { return element; }; - - if (supportsOpacity) { - /** @ignore */ - setOpacity = function(element, value) { - element.style.opacity = value; - return element; - }; - } - else if (supportsFilters) { - /** @ignore */ - setOpacity = function(element, value) { - var es = element.style; - if (element.currentStyle && !element.currentStyle.hasLayout) { - es.zoom = 1; - } - if (reOpacity.test(es.filter)) { - value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); - es.filter = es.filter.replace(reOpacity, value); - } - else { - es.filter += ' alpha(opacity=' + (value * 100) + ')'; - } - return element; - }; - } - - fabric.util.setStyle = setStyle; - -})(); - - (function() { + var unknown = "unknown"; + function areHostMethods(object) { + var methodNames = Array.prototype.slice.call(arguments, 1), t, i, len = methodNames.length; + for (i = 0; i < len; i++) { + t = typeof object[methodNames[i]]; + if (!/^(?:function|object|unknown)$/.test(t)) return false; + } + return true; + } + var getElement, setElement, getUniqueId = function() { + var uid = 0; + return function(element) { + return element.__uniqueID || (element.__uniqueID = "uniqueID__" + uid++); + }; + }(); + (function() { + var elements = {}; + getElement = function(uid) { + return elements[uid]; + }; + setElement = function(uid, element) { + elements[uid] = element; + }; + })(); + function createListener(uid, handler) { + return { + handler: handler, + wrappedHandler: createWrappedHandler(uid, handler) + }; + } + function createWrappedHandler(uid, handler) { + return function(e) { + handler.call(getElement(uid), e || fabric.window.event); + }; + } + function createDispatcher(uid, eventName) { + return function(e) { + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + handlersForEvent[i].call(this, e || fabric.window.event); + } + } + }; + } + var shouldUseAddListenerRemoveListener = areHostMethods(fabric.document.documentElement, "addEventListener", "removeEventListener") && areHostMethods(fabric.window, "addEventListener", "removeEventListener"), shouldUseAttachEventDetachEvent = areHostMethods(fabric.document.documentElement, "attachEvent", "detachEvent") && areHostMethods(fabric.window, "attachEvent", "detachEvent"), listeners = {}, handlers = {}, addListener, removeListener; + if (shouldUseAddListenerRemoveListener) { + addListener = function(element, eventName, handler) { + element.addEventListener(eventName, handler, false); + }; + removeListener = function(element, eventName, handler) { + element.removeEventListener(eventName, handler, false); + }; + } else if (shouldUseAttachEventDetachEvent) { + addListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + setElement(uid, element); + if (!listeners[uid]) { + listeners[uid] = {}; + } + if (!listeners[uid][eventName]) { + listeners[uid][eventName] = []; + } + var listener = createListener(uid, handler); + listeners[uid][eventName].push(listener); + element.attachEvent("on" + eventName, listener.wrappedHandler); + }; + removeListener = function(element, eventName, handler) { + var uid = getUniqueId(element), listener; + if (listeners[uid] && listeners[uid][eventName]) { + for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { + listener = listeners[uid][eventName][i]; + if (listener && listener.handler === handler) { + element.detachEvent("on" + eventName, listener.wrappedHandler); + listeners[uid][eventName][i] = null; + } + } + } + }; + } else { + addListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + if (!handlers[uid]) { + handlers[uid] = {}; + } + if (!handlers[uid][eventName]) { + handlers[uid][eventName] = []; + var existingHandler = element["on" + eventName]; + if (existingHandler) { + handlers[uid][eventName].push(existingHandler); + } + element["on" + eventName] = createDispatcher(uid, eventName); + } + handlers[uid][eventName].push(handler); + }; + removeListener = function(element, eventName, handler) { + var uid = getUniqueId(element); + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + if (handlersForEvent[i] === handler) { + handlersForEvent.splice(i, 1); + } + } + } + }; + } + fabric.util.addListener = addListener; + fabric.util.removeListener = removeListener; + function getPointer(event, upperCanvasEl) { + event || (event = fabric.window.event); + var element = event.target || (typeof event.srcElement !== unknown ? event.srcElement : null), scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); + return { + x: pointerX(event) + scroll.left, + y: pointerY(event) + scroll.top + }; + } + var pointerX = function(event) { + return typeof event.clientX !== unknown ? event.clientX : 0; + }, pointerY = function(event) { + return typeof event.clientY !== unknown ? event.clientY : 0; + }; + function _getPointer(event, pageProp, clientProp) { + var touchProp = event.type === "touchend" ? "changedTouches" : "touches"; + return event[touchProp] && event[touchProp][0] ? event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]) || event[clientProp] : event[clientProp]; + } + if (fabric.isTouchSupported) { + pointerX = function(event) { + return _getPointer(event, "pageX", "clientX"); + }; + pointerY = function(event) { + return _getPointer(event, "pageY", "clientY"); + }; + } + fabric.util.getPointer = getPointer; + fabric.util.object.extend(fabric.util, fabric.Observable); +})(); - var _slice = Array.prototype.slice; +(function() { + function setStyle(element, styles) { + var elementStyle = element.style; + if (!elementStyle) { + return element; + } + if (typeof styles === "string") { + element.style.cssText += ";" + styles; + return styles.indexOf("opacity") > -1 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) { + if (property === "opacity") { + setOpacity(element, styles[property]); + } else { + var normalizedProperty = property === "float" || property === "cssFloat" ? typeof elementStyle.styleFloat === "undefined" ? "cssFloat" : "styleFloat" : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + var parseEl = fabric.document.createElement("div"), supportsOpacity = typeof parseEl.style.opacity === "string", supportsFilters = typeof parseEl.style.filter === "string", reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, setOpacity = function(element) { + return element; + }; + if (supportsOpacity) { + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } else if (supportsFilters) { + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= .9999 ? "" : "alpha(opacity=" + value * 100 + ")"; + es.filter = es.filter.replace(reOpacity, value); + } else { + es.filter += " alpha(opacity=" + value * 100 + ")"; + } + return element; + }; + } + fabric.util.setStyle = setStyle; +})(); - /** - * Takes id and returns an element with that id (if one exists in a document) - * @memberOf fabric.util - * @param {String|HTMLElement} id - * @return {HTMLElement|null} - */ - function getById(id) { - return typeof id === 'string' ? fabric.document.getElementById(id) : id; - } - - var sliceCanConvertNodelists, - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - toArray = function(arrayLike) { +(function() { + var _slice = Array.prototype.slice; + function getById(id) { + return typeof id === "string" ? fabric.document.getElementById(id) : id; + } + var sliceCanConvertNodelists, toArray = function(arrayLike) { return _slice.call(arrayLike, 0); - }; - - try { - sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; - } - catch (err) { } - - if (!sliceCanConvertNodelists) { - toArray = function(arrayLike) { - var arr = new Array(arrayLike.length), i = arrayLike.length; - while (i--) { - arr[i] = arrayLike[i]; - } - return arr; }; - } - - /** - * Creates specified element with specified attributes - * @memberOf fabric.util - * @param {String} tagName Type of an element to create - * @param {Object} [attributes] Attributes to set on an element - * @return {HTMLElement} Newly created element - */ - function makeElement(tagName, attributes) { - var el = fabric.document.createElement(tagName); - for (var prop in attributes) { - if (prop === 'class') { - el.className = attributes[prop]; - } - else if (prop === 'for') { - el.htmlFor = attributes[prop]; - } - else { - el.setAttribute(prop, attributes[prop]); - } - } - return el; - } - - /** - * Adds class to an element - * @memberOf fabric.util - * @param {HTMLElement} element Element to add class to - * @param {String} className Class to add to an element - */ - function addClass(element, className) { - if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { - element.className += (element.className ? ' ' : '') + className; - } - } - - /** - * Wraps element with another element - * @memberOf fabric.util - * @param {HTMLElement} element Element to wrap - * @param {HTMLElement|String} wrapper Element to wrap with - * @param {Object} [attributes] Attributes to set on a wrapper - * @return {HTMLElement} wrapper - */ - function wrapElement(element, wrapper, attributes) { - if (typeof wrapper === 'string') { - wrapper = makeElement(wrapper, attributes); - } - if (element.parentNode) { - element.parentNode.replaceChild(wrapper, element); - } - wrapper.appendChild(element); - return wrapper; - } - - /** - * Returns element scroll offsets - * @memberOf fabric.util - * @param {HTMLElement} element Element to operate on - * @param {HTMLElement} upperCanvasEl Upper canvas element - * @return {Object} Object with left/top values - */ - function getScrollLeftTop(element, upperCanvasEl) { - - var firstFixedAncestor, - origElement, - left = 0, - top = 0, - docElement = fabric.document.documentElement, - body = fabric.document.body || { - scrollLeft: 0, scrollTop: 0 + try { + sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } catch (err) {} + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; }; - - origElement = element; - - while (element && element.parentNode && !firstFixedAncestor) { - - element = element.parentNode; - - if (element !== fabric.document && - fabric.util.getElementStyle(element, 'position') === 'fixed') { - firstFixedAncestor = element; - } - - if (element !== fabric.document && - origElement !== upperCanvasEl && - fabric.util.getElementStyle(element, 'position') === 'absolute') { - left = 0; - top = 0; - } - else if (element === fabric.document) { - left = body.scrollLeft || docElement.scrollLeft || 0; - top = body.scrollTop || docElement.scrollTop || 0; - } - else { - left += element.scrollLeft || 0; - top += element.scrollTop || 0; - } } - - return { left: left, top: top }; - } - - /** - * Returns offset for a given element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element Element to get offset for - * @return {Object} Object with "left" and "top" properties - */ - function getElementOffset(element) { - var docElem, - doc = element && element.ownerDocument, - box = { left: 0, top: 0 }, - offset = { left: 0, top: 0 }, - scrollLeftTop, - offsetAttributes = { - borderLeftWidth: 'left', - borderTopWidth: 'top', - paddingLeft: 'left', - paddingTop: 'top' - }; - - if (!doc) { - return { left: 0, top: 0 }; - } - - for (var attr in offsetAttributes) { - offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; - } - - docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== 'undefined' ) { - box = element.getBoundingClientRect(); - } - - scrollLeftTop = fabric.util.getScrollLeftTop(element, null); - - return { - left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, - top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top - }; - } - - /** - * Returns style attribute value of a given element - * @memberOf fabric.util - * @param {HTMLElement} element Element to get style attribute for - * @param {String} attr Style attribute to get for element - * @return {String} Style attribute value of the given element. - */ - var getElementStyle; - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { - getElementStyle = function(element, attr) { - return fabric.document.defaultView.getComputedStyle(element, null)[attr]; - }; - } - else { - getElementStyle = function(element, attr) { - var value = element.style[attr]; - if (!value && element.currentStyle) { - value = element.currentStyle[attr]; - } - return value; - }; - } - - (function () { - var style = fabric.document.documentElement.style, - selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; - - /** - * Makes element unselectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make unselectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementUnselectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = fabric.util.falseFunction; - } - if (selectProp) { - element.style[selectProp] = 'none'; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = 'on'; - } - return element; - } - - /** - * Makes element selectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make selectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementSelectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = null; - } - if (selectProp) { - element.style[selectProp] = ''; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = ''; - } - return element; - } - - fabric.util.makeElementUnselectable = makeElementUnselectable; - fabric.util.makeElementSelectable = makeElementSelectable; - })(); - - (function() { - - /** - * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading - * @memberOf fabric.util - * @param {String} url URL of a script to load - * @param {Function} callback Callback to execute when script is finished loading - */ - function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName('head')[0], - scriptEl = fabric.document.createElement('script'), - loading = true; - - /** @ignore */ - scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { - if (loading) { - if (typeof this.readyState === 'string' && - this.readyState !== 'loaded' && - this.readyState !== 'complete') return; - loading = false; - callback(e || fabric.window.event); - scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === "class") { + el.className = attributes[prop]; + } else if (prop === "for") { + el.htmlFor = attributes[prop]; + } else { + el.setAttribute(prop, attributes[prop]); + } } - }; - scriptEl.src = url; - headEl.appendChild(scriptEl); - // causes issue in Opera - // headEl.removeChild(scriptEl); + return el; } - - fabric.util.getScript = getScript; - })(); - - fabric.util.getById = getById; - fabric.util.toArray = toArray; - fabric.util.makeElement = makeElement; - fabric.util.addClass = addClass; - fabric.util.wrapElement = wrapElement; - fabric.util.getScrollLeftTop = getScrollLeftTop; - fabric.util.getElementOffset = getElementOffset; - fabric.util.getElementStyle = getElementStyle; - + function addClass(element, className) { + if (element && (" " + element.className + " ").indexOf(" " + className + " ") === -1) { + element.className += (element.className ? " " : "") + className; + } + } + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === "string") { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + function getScrollLeftTop(element, upperCanvasEl) { + var firstFixedAncestor, origElement, left = 0, top = 0, docElement = fabric.document.documentElement, body = fabric.document.body || { + scrollLeft: 0, + scrollTop: 0 + }; + origElement = element; + while (element && element.parentNode && !firstFixedAncestor) { + element = element.parentNode; + if (element !== fabric.document && fabric.util.getElementStyle(element, "position") === "fixed") { + firstFixedAncestor = element; + } + if (element !== fabric.document && origElement !== upperCanvasEl && fabric.util.getElementStyle(element, "position") === "absolute") { + left = 0; + top = 0; + } else if (element === fabric.document) { + left = body.scrollLeft || docElement.scrollLeft || 0; + top = body.scrollTop || docElement.scrollTop || 0; + } else { + left += element.scrollLeft || 0; + top += element.scrollTop || 0; + } + } + return { + left: left, + top: top + }; + } + function getElementOffset(element) { + var docElem, doc = element && element.ownerDocument, box = { + left: 0, + top: 0 + }, offset = { + left: 0, + top: 0 + }, scrollLeftTop, offsetAttributes = { + borderLeftWidth: "left", + borderTopWidth: "top", + paddingLeft: "left", + paddingTop: "top" + }; + if (!doc) { + return { + left: 0, + top: 0 + }; + } + for (var attr in offsetAttributes) { + offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; + } + docElem = doc.documentElement; + if (typeof element.getBoundingClientRect !== "undefined") { + box = element.getBoundingClientRect(); + } + scrollLeftTop = fabric.util.getScrollLeftTop(element, null); + return { + left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, + top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top + }; + } + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { + return fabric.document.defaultView.getComputedStyle(element, null)[attr]; + }; + } else { + getElementStyle = function(element, attr) { + var value = element.style[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } + return value; + }; + } + (function() { + var style = fabric.document.documentElement.style, selectProp = "userSelect" in style ? "userSelect" : "MozUserSelect" in style ? "MozUserSelect" : "WebkitUserSelect" in style ? "WebkitUserSelect" : "KhtmlUserSelect" in style ? "KhtmlUserSelect" : ""; + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== "undefined") { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = "none"; + } else if (typeof element.unselectable === "string") { + element.unselectable = "on"; + } + return element; + } + function makeElementSelectable(element) { + if (typeof element.onselectstart !== "undefined") { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ""; + } else if (typeof element.unselectable === "string") { + element.unselectable = ""; + } + return element; + } + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + (function() { + function getScript(url, callback) { + var headEl = fabric.document.getElementsByTagName("head")[0], scriptEl = fabric.document.createElement("script"), loading = true; + scriptEl.onload = scriptEl.onreadystatechange = function(e) { + if (loading) { + if (typeof this.readyState === "string" && this.readyState !== "loaded" && this.readyState !== "complete") return; + loading = false; + callback(e || fabric.window.event); + scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; + } + }; + scriptEl.src = url; + headEl.appendChild(scriptEl); + } + fabric.util.getScript = getScript; + })(); + fabric.util.getById = getById; + fabric.util.toArray = toArray; + fabric.util.makeElement = makeElement; + fabric.util.addClass = addClass; + fabric.util.wrapElement = wrapElement; + fabric.util.getScrollLeftTop = getScrollLeftTop; + fabric.util.getElementOffset = getElementOffset; + fabric.util.getElementStyle = getElementStyle; })(); - -(function(){ - - function addParamToUrl(url, param) { - return url + (/\?/.test(url) ? '&' : '?') + param; - } - - var makeXHR = (function() { - var factories = [ - function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, - function() { return new XMLHttpRequest(); } - ]; - for (var i = factories.length; i--; ) { - try { - var req = factories[i](); - if (req) { - return factories[i]; +(function() { + function addParamToUrl(url, param) { + return url + (/\?/.test(url) ? "&" : "?") + param; + } + var makeXHR = function() { + var factories = [ function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }, function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }, function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }, function() { + return new XMLHttpRequest(); + } ]; + for (var i = factories.length; i--; ) { + try { + var req = factories[i](); + if (req) { + return factories[i]; + } + } catch (err) {} } - } - catch (err) { } + }(); + function emptyFn() {} + function request(url, options) { + options || (options = {}); + var method = options.method ? options.method.toUpperCase() : "GET", onComplete = options.onComplete || function() {}, xhr = makeXHR(), body; + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + onComplete(xhr); + xhr.onreadystatechange = emptyFn; + } + }; + if (method === "GET") { + body = null; + if (typeof options.parameters === "string") { + url = addParamToUrl(url, options.parameters); + } + } + xhr.open(method, url, true); + if (method === "POST" || method === "PUT") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } + xhr.send(body); + return xhr; } - })(); - - function emptyFn() { } - - /** - * Cross-browser abstraction for sending XMLHttpRequest - * @memberOf fabric.util - * @param {String} url URL to send XMLHttpRequest to - * @param {Object} [options] Options object - * @param {String} [options.method="GET"] - * @param {Function} options.onComplete Callback to invoke when request is completed - * @return {XMLHttpRequest} request - */ - function request(url, options) { - - options || (options = { }); - - var method = options.method ? options.method.toUpperCase() : 'GET', - onComplete = options.onComplete || function() { }, - xhr = makeXHR(), - body; - - /** @ignore */ - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - onComplete(xhr); - xhr.onreadystatechange = emptyFn; - } - }; - - if (method === 'GET') { - body = null; - if (typeof options.parameters === 'string') { - url = addParamToUrl(url, options.parameters); - } - } - - xhr.open(method, url, true); - - if (method === 'POST' || method === 'PUT') { - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - } - - xhr.send(body); - return xhr; - } - - fabric.util.request = request; + fabric.util.request = request; })(); +fabric.log = function() {}; -/** - * Wrapper around `console.log` (when available) - * @param {Any} values Values to log - */ -fabric.log = function() { }; +fabric.warn = function() {}; -/** - * Wrapper around `console.warn` (when available) - * @param {Any} Values to log as a warning - */ -fabric.warn = function() { }; - -if (typeof console !== 'undefined') { - ['log', 'warn'].forEach(function(methodName) { - if (typeof console[methodName] !== 'undefined' && console[methodName].apply) { - fabric[methodName] = function() { - return console[methodName].apply(console, arguments); - }; - } - }); +if (typeof console !== "undefined") { + [ "log", "warn" ].forEach(function(methodName) { + if (typeof console[methodName] !== "undefined" && console[methodName].apply) { + fabric[methodName] = function() { + return console[methodName].apply(console, arguments); + }; + } + }); } - (function() { - - /** - * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. - * @memberOf fabric.util - * @param {Object} [options] Animation options - * @param {Function} [options.onChange] Callback; invoked on every value change - * @param {Function} [options.onComplete] Callback; invoked when value change is completed - * @param {Number} [options.startValue=0] Starting value - * @param {Number} [options.endValue=100] Ending value - * @param {Number} [options.byValue=100] Value to modify the property by - * @param {Function} [options.easing] Easing function - * @param {Number} [options.duration=500] Duration of change (in ms) - */ - function animate(options) { - - requestAnimFrame(function(timestamp) { - options || (options = { }); - - var start = timestamp || +new Date(), - duration = options.duration || 500, - finish = start + duration, time, - onChange = options.onChange || function() { }, - abort = options.abort || function() { return false; }, - easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, - startValue = 'startValue' in options ? options.startValue : 0, - endValue = 'endValue' in options ? options.endValue : 100, - byValue = options.byValue || endValue - startValue; - - options.onStart && options.onStart(); - - (function tick(ticktime) { - time = ticktime || +new Date(); - var currentTime = time > finish ? duration : (time - start); - if (abort()) { - options.onComplete && options.onComplete(); - return; - } - onChange(easing(currentTime, startValue, byValue, duration)); - if (time > finish) { - options.onComplete && options.onComplete(); - return; - } - requestAnimFrame(tick); - })(start); - }); - - } - - var _requestAnimFrame = fabric.window.requestAnimationFrame || - fabric.window.webkitRequestAnimationFrame || - fabric.window.mozRequestAnimationFrame || - fabric.window.oRequestAnimationFrame || - fabric.window.msRequestAnimationFrame || - function(callback) { - fabric.window.setTimeout(callback, 1000 / 60); - }; - /** - * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method - * @memberOf fabric.util - * @param {Function} callback Callback to invoke - * @param {DOMElement} element optional Element to associate with animation - */ - function requestAnimFrame() { - return _requestAnimFrame.apply(fabric.window, arguments); - } - - fabric.util.animate = animate; - fabric.util.requestAnimFrame = requestAnimFrame; - + function animate(options) { + requestAnimFrame(function(timestamp) { + options || (options = {}); + var start = timestamp || +new Date(), duration = options.duration || 500, finish = start + duration, time, onChange = options.onChange || function() {}, abort = options.abort || function() { + return false; + }, easing = options.easing || function(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + }, startValue = "startValue" in options ? options.startValue : 0, endValue = "endValue" in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; + options.onStart && options.onStart(); + (function tick(ticktime) { + time = ticktime || +new Date(); + var currentTime = time > finish ? duration : time - start; + if (abort()) { + options.onComplete && options.onComplete(); + return; + } + onChange(easing(currentTime, startValue, byValue, duration)); + if (time > finish) { + options.onComplete && options.onComplete(); + return; + } + requestAnimFrame(tick); + })(start); + }); + } + var _requestAnimFrame = fabric.window.requestAnimationFrame || fabric.window.webkitRequestAnimationFrame || fabric.window.mozRequestAnimationFrame || fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) { + fabric.window.setTimeout(callback, 1e3 / 60); + }; + function requestAnimFrame() { + return _requestAnimFrame.apply(fabric.window, arguments); + } + fabric.util.animate = animate; + fabric.util.requestAnimFrame = requestAnimFrame; })(); - (function() { - - function normalize(a, c, p, s) { - if (a < Math.abs(c)) { - a = c; - s = p / 4; + function normalize(a, c, p, s) { + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return { + a: a, + c: c, + p: p, + s: s + }; } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); + function elastic(opts, t, d) { + return opts.a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p); } - return { a: a, c: c, p: p, s: s }; - } - - function elastic(opts, t, d) { - return opts.a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); - } - - /** - * Cubic easing out - * @memberOf fabric.util.ease - */ - function easeOutCubic(t, b, c, d) { - return c * ((t = t / d - 1) * t * t + 1) + b; - } - - /** - * Cubic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutCubic(t, b, c, d) { - t /= d/2; - if (t < 1) { - return c / 2 * t * t * t + b; + function easeOutCubic(t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; } - return c / 2 * ((t -= 2) * t * t + 2) + b; - } - - /** - * Quartic easing in - * @memberOf fabric.util.ease - */ - function easeInQuart(t, b, c, d) { - return c * (t /= d) * t * t * t + b; - } - - /** - * Quartic easing out - * @memberOf fabric.util.ease - */ - function easeOutQuart(t, b, c, d) { - return -c * ((t = t / d - 1) * t * t * t - 1) + b; - } - - /** - * Quartic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutQuart(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t * t + b; + function easeInOutCubic(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; } - return -c / 2 * ((t -= 2) * t * t * t - 2) + b; - } - - /** - * Quintic easing in - * @memberOf fabric.util.ease - */ - function easeInQuint(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - } - - /** - * Quintic easing out - * @memberOf fabric.util.ease - */ - function easeOutQuint(t, b, c, d) { - return c * ((t = t / d - 1) * t * t * t * t + 1) + b; - } - - /** - * Quintic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutQuint(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t * t * t + b; + function easeInQuart(t, b, c, d) { + return c * (t /= d) * t * t * t + b; } - return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; - } - - /** - * Sinusoidal easing in - * @memberOf fabric.util.ease - */ - function easeInSine(t, b, c, d) { - return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - } - - /** - * Sinusoidal easing out - * @memberOf fabric.util.ease - */ - function easeOutSine(t, b, c, d) { - return c * Math.sin(t / d * (Math.PI / 2)) + b; - } - - /** - * Sinusoidal easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutSine(t, b, c, d) { - return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; - } - - /** - * Exponential easing in - * @memberOf fabric.util.ease - */ - function easeInExpo(t, b, c, d) { - return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; - } - - /** - * Exponential easing out - * @memberOf fabric.util.ease - */ - function easeOutExpo(t, b, c, d) { - return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; - } - - /** - * Exponential easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutExpo(t, b, c, d) { - if (t === 0) { - return b; + function easeOutQuart(t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; } - if (t === d) { - return b + c; + function easeInOutQuart(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } - t /= d / 2; - if (t < 1) { - return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + function easeInQuint(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; } - return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; - } - - /** - * Circular easing in - * @memberOf fabric.util.ease - */ - function easeInCirc(t, b, c, d) { - return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; - } - - /** - * Circular easing out - * @memberOf fabric.util.ease - */ - function easeOutCirc(t, b, c, d) { - return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; - } - - /** - * Circular easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutCirc(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + function easeOutQuint(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } - return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; - } - - /** - * Elastic easing in - * @memberOf fabric.util.ease - */ - function easeInElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; + function easeInOutQuint(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } - t /= d; - if (t === 1) { - return b + c; + function easeInSine(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } - if (!p) { - p = d * 0.3; + function easeOutSine(t, b, c, d) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; } - var opts = normalize(a, c, p, s); - return -elastic(opts, t, d) + b; - } - - /** - * Elastic easing out - * @memberOf fabric.util.ease - */ - function easeOutElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; + function easeInOutSine(t, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } - t /= d; - if (t === 1) { - return b + c; + function easeInExpo(t, b, c, d) { + return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } - if (!p) { - p = d * 0.3; + function easeOutExpo(t, b, c, d) { + return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } - var opts = normalize(a, c, p, s); - return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; - } - - /** - * Elastic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; + function easeInOutExpo(t, b, c, d) { + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } - t /= d / 2; - if (t === 2) { - return b + c; + function easeInCirc(t, b, c, d) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } - if (!p) { - p = d * (0.3 * 1.5); + function easeOutCirc(t, b, c, d) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } - var opts = normalize(a, c, p, s); - if (t < 1) { - return -0.5 * elastic(opts, t, d) + b; + function easeInOutCirc(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } - return opts.a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; - } - - /** - * Backwards easing in - * @memberOf fabric.util.ease - */ - function easeInBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; + function easeInElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * .3; + } + var opts = normalize(a, c, p, s); + return -elastic(opts, t, d) + b; } - return c * (t /= d) * t * ((s + 1) * t - s) + b; - } - - /** - * Backwards easing out - * @memberOf fabric.util.ease - */ - function easeOutBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; + function easeOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * .3; + } + var opts = normalize(a, c, p, s); + return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p) + opts.c + b; } - return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - } - - /** - * Backwards easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; + function easeInOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d / 2; + if (t === 2) { + return b + c; + } + if (!p) { + p = d * (.3 * 1.5); + } + var opts = normalize(a, c, p, s); + if (t < 1) { + return -.5 * elastic(opts, t, d) + b; + } + return opts.a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p) * .5 + opts.c + b; } - t /= d / 2; - if (t < 1) { - return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + function easeInBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; } - return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; - } - - /** - * Bouncing easing in - * @memberOf fabric.util.ease - */ - function easeInBounce(t, b, c, d) { - return c - easeOutBounce (d - t, 0, c, d) + b; - } - - /** - * Bouncing easing out - * @memberOf fabric.util.ease - */ - function easeOutBounce(t, b, c, d) { - if ((t /= d) < (1 / 2.75)) { - return c * (7.5625 * t * t) + b; + function easeOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } - else if (t < (2/2.75)) { - return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + function easeInOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; } - else if (t < (2.5/2.75)) { - return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + function easeInBounce(t, b, c, d) { + return c - easeOutBounce(d - t, 0, c, d) + b; } - else { - return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + function easeOutBounce(t, b, c, d) { + if ((t /= d) < 1 / 2.75) { + return c * (7.5625 * t * t) + b; + } else if (t < 2 / 2.75) { + return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b; + } else if (t < 2.5 / 2.75) { + return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b; + } else { + return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b; + } } - } - - /** - * Bouncing easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutBounce(t, b, c, d) { - if (t < d / 2) { - return easeInBounce (t * 2, 0, c, d) * 0.5 + b; + function easeInOutBounce(t, b, c, d) { + if (t < d / 2) { + return easeInBounce(t * 2, 0, c, d) * .5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b; } - return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - - /** - * Easing functions - * See Easing Equations by Robert Penner - * @namespace fabric.util.ease - */ - fabric.util.ease = { - - /** - * Quadratic easing in - * @memberOf fabric.util.ease - */ - easeInQuad: function(t, b, c, d) { - return c * (t /= d) * t + b; - }, - - /** - * Quadratic easing out - * @memberOf fabric.util.ease - */ - easeOutQuad: function(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - }, - - /** - * Quadratic easing in and out - * @memberOf fabric.util.ease - */ - easeInOutQuad: function(t, b, c, d) { - t /= (d / 2); - if (t < 1) { - return c / 2 * t * t + b; - } - return -c / 2 * ((--t) * (t - 2) - 1) + b; - }, - - /** - * Cubic easing in - * @memberOf fabric.util.ease - */ - easeInCubic: function(t, b, c, d) { - return c * (t /= d) * t * t + b; - }, - - easeOutCubic: easeOutCubic, - easeInOutCubic: easeInOutCubic, - easeInQuart: easeInQuart, - easeOutQuart: easeOutQuart, - easeInOutQuart: easeInOutQuart, - easeInQuint: easeInQuint, - easeOutQuint: easeOutQuint, - easeInOutQuint: easeInOutQuint, - easeInSine: easeInSine, - easeOutSine: easeOutSine, - easeInOutSine: easeInOutSine, - easeInExpo: easeInExpo, - easeOutExpo: easeOutExpo, - easeInOutExpo: easeInOutExpo, - easeInCirc: easeInCirc, - easeOutCirc: easeOutCirc, - easeInOutCirc: easeInOutCirc, - easeInElastic: easeInElastic, - easeOutElastic: easeOutElastic, - easeInOutElastic: easeInOutElastic, - easeInBack: easeInBack, - easeOutBack: easeOutBack, - easeInOutBack: easeInOutBack, - easeInBounce: easeInBounce, - easeOutBounce: easeOutBounce, - easeInOutBounce: easeInOutBounce - }; - -}()); - + fabric.util.ease = { + easeInQuad: function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + easeOutQuad: function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + easeInOutQuad: function(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * (--t * (t - 2) - 1) + b; + }, + easeInCubic: function(t, b, c, d) { + return c * (t /= d) * t * t + b; + }, + easeOutCubic: easeOutCubic, + easeInOutCubic: easeInOutCubic, + easeInQuart: easeInQuart, + easeOutQuart: easeOutQuart, + easeInOutQuart: easeInOutQuart, + easeInQuint: easeInQuint, + easeOutQuint: easeOutQuint, + easeInOutQuint: easeInOutQuint, + easeInSine: easeInSine, + easeOutSine: easeOutSine, + easeInOutSine: easeInOutSine, + easeInExpo: easeInExpo, + easeOutExpo: easeOutExpo, + easeInOutExpo: easeInOutExpo, + easeInCirc: easeInCirc, + easeOutCirc: easeOutCirc, + easeInOutCirc: easeInOutCirc, + easeInElastic: easeInElastic, + easeOutElastic: easeOutElastic, + easeInOutElastic: easeInOutElastic, + easeInBack: easeInBack, + easeOutBack: easeOutBack, + easeInOutBack: easeInOutBack, + easeInBounce: easeInBounce, + easeOutBounce: easeOutBounce, + easeInOutBounce: easeInOutBounce + }; +})(); (function(global) { - - 'use strict'; - - /** - * @name fabric - * @namespace - */ - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - capitalize = fabric.util.string.capitalize, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - - attributesMap = { - cx: 'left', - x: 'left', - r: 'radius', - cy: 'top', - y: 'top', - display: 'visible', - visibility: 'visible', - transform: 'transformMatrix', - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit': 'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'text-anchor': 'originX' - }, - - colorAttributes = { - stroke: 'strokeOpacity', - fill: 'fillOpacity' - }; - - function normalizeAttr(attr) { - // transform attribute names - if (attr in attributesMap) { - return attributesMap[attr]; - } - return attr; - } - - function normalizeValue(attr, value, parentAttributes) { - var isArray; - - if ((attr === 'fill' || attr === 'stroke') && value === 'none') { - value = ''; - } - else if (attr === 'fillRule') { - value = (value === 'evenodd') ? 'destination-over' : value; - } - else if (attr === 'strokeDashArray') { - value = value.replace(/,/g, ' ').split(/\s+/); - } - else if (attr === 'transformMatrix') { - if (parentAttributes && parentAttributes.transformMatrix) { - value = multiplyTransformMatrices( - parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); - } - else { - value = fabric.parseTransformAttribute(value); - } - } - else if (attr === 'visible') { - value = (value === 'none' || value === 'hidden') ? false : true; - // display=none on parent element always takes precedence over child element - if (parentAttributes.visible === false) { - value = false; - } - } - else if (attr === 'originX' /* text-anchor */) { - value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; - } - - isArray = Object.prototype.toString.call(value) === '[object Array]'; - - // TODO: need to normalize em, %, pt, etc. to px (!) - var parsed = isArray ? value.map(parseFloat) : parseFloat(value); - - return (!isArray && isNaN(parsed) ? value : parsed); - } - - /** - * @private - * @param {Object} attributes Array of attributes to parse - */ - function _setStrokeFillOpacity(attributes) { - for (var attr in colorAttributes) { - - if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') continue; - - if (attributes[attr].indexOf('url(') === 0) continue; - - var color = new fabric.Color(attributes[attr]); - attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); - - delete attributes[colorAttributes[attr]]; - } - return attributes; - } - - /** - * Parses "transform" attribute, returning an array of values - * @static - * @function - * @memberOf fabric - * @param {String} attributeValue String containing attribute value - * @return {Array} Array of 6 elements representing transformation matrix - */ - fabric.parseTransformAttribute = (function() { - function rotateMatrix(matrix, args) { - var angle = args[0]; - - matrix[0] = Math.cos(angle); - matrix[1] = Math.sin(angle); - matrix[2] = -Math.sin(angle); - matrix[3] = Math.cos(angle); - } - - function scaleMatrix(matrix, args) { - var multiplierX = args[0], - multiplierY = (args.length === 2) ? args[1] : args[0]; - - matrix[0] = multiplierX; - matrix[3] = multiplierY; - } - - function skewXMatrix(matrix, args) { - matrix[2] = args[0]; - } - - function skewYMatrix(matrix, args) { - matrix[1] = args[0]; - } - - function translateMatrix(matrix, args) { - matrix[4] = args[0]; - if (args.length === 2) { - matrix[5] = args[1]; - } - } - - // identity matrix - var iMatrix = [ - 1, // a - 0, // b - 0, // c - 1, // d - 0, // e - 0 // f - ], - - // == begin transform regexp - number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - - commaWsp = '(?:\\s+,?\\s*|,\\s*)', - - skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', - - skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', - - rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + ')' + - commaWsp + '(' + number + '))?\\s*\\))', - - scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + - '\\s*\\))', - - transform = '(?:' + - matrix + '|' + - translate + '|' + - scale + '|' + - rotate + '|' + - skewX + '|' + - skewY + - ')', - - transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - - transformList = '^\\s*(?:' + transforms + '?)\\s*$', - - // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transformList), - // == end transform regexp - - reTransform = new RegExp(transform, 'g'); - - return function(attributeValue) { - - // start with identity matrix - var matrix = iMatrix.concat(), - matrices = [ ]; - - // return if no argument was given or - // an argument does not match transform attribute regexp - if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { - return matrix; - } - - attributeValue.replace(reTransform, function(match) { - - var m = new RegExp(transform).exec(match).filter(function (match) { - return (match !== '' && match != null); - }), - operation = m[1], - args = m.slice(2).map(parseFloat); - - switch (operation) { - case 'translate': - translateMatrix(matrix, args); - break; - case 'rotate': - args[0] = fabric.util.degreesToRadians(args[0]); - rotateMatrix(matrix, args); - break; - case 'scale': - scaleMatrix(matrix, args); - break; - case 'skewX': - skewXMatrix(matrix, args); - break; - case 'skewY': - skewYMatrix(matrix, args); - break; - case 'matrix': - matrix = args; - break; - } - - // snapshot current matrix into matrices array - matrices.push(matrix.concat()); - // reset - matrix = iMatrix.concat(); - }); - - var combinedMatrix = matrices[0]; - while (matrices.length > 1) { - matrices.shift(); - combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); - } - return combinedMatrix; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, attributesMap = { + cx: "left", + x: "left", + r: "radius", + cy: "top", + y: "top", + display: "visible", + visibility: "visible", + transform: "transformMatrix", + "fill-opacity": "fillOpacity", + "fill-rule": "fillRule", + "font-family": "fontFamily", + "font-size": "fontSize", + "font-style": "fontStyle", + "font-weight": "fontWeight", + "stroke-dasharray": "strokeDashArray", + "stroke-linecap": "strokeLineCap", + "stroke-linejoin": "strokeLineJoin", + "stroke-miterlimit": "strokeMiterLimit", + "stroke-opacity": "strokeOpacity", + "stroke-width": "strokeWidth", + "text-decoration": "textDecoration", + "text-anchor": "originX" + }, colorAttributes = { + stroke: "strokeOpacity", + fill: "fillOpacity" }; - })(); - - function parseFontDeclaration(value, oStyle) { - - // TODO: support non-px font size - var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); - - if (!match) return; - - var fontStyle = match[1], - // font variant is not used - // fontVariant = match[2], - fontWeight = match[3], - fontSize = match[4], - lineHeight = match[5], - fontFamily = match[6]; - - if (fontStyle) { - oStyle.fontStyle = fontStyle; - } - if (fontWeight) { - oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); - } - if (fontSize) { - oStyle.fontSize = parseFloat(fontSize); - } - if (fontFamily) { - oStyle.fontFamily = fontFamily; - } - if (lineHeight) { - oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; - } - } - - /** - * @private - */ - function parseStyleString(style, oStyle) { - var attr, value; - style.replace(/;$/, '').split(';').forEach(function (chunk) { - var pair = chunk.split(':'); - - attr = normalizeAttr(pair[0].trim().toLowerCase()); - value = normalizeValue(attr, pair[1].trim()); - - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } - }); - } - - /** - * @private - */ - function parseStyleObject(style, oStyle) { - var attr, value; - for (var prop in style) { - if (typeof style[prop] === 'undefined') continue; - - attr = normalizeAttr(prop.toLowerCase()); - value = normalizeValue(attr, style[prop]); - - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } - } - } - - /** - * @private - */ - function getGlobalStylesForElement(element) { - var nodeName = element.nodeName, - className = element.getAttribute('class'), - id = element.getAttribute('id'), - styles = { }; - - for (var rule in fabric.cssRules) { - var ruleMatchesElement = (className && new RegExp('^\\.' + className).test(rule)) || - (id && new RegExp('^#' + id).test(rule)) || - (new RegExp('^' + nodeName).test(rule)); - - if (ruleMatchesElement) { - for (var property in fabric.cssRules[rule]) { - styles[property] = fabric.cssRules[rule][property]; + function normalizeAttr(attr) { + if (attr in attributesMap) { + return attributesMap[attr]; } - } + return attr; } - - return styles; - } - - /** - * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - fabric.parseSVGDocument = (function() { - - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // \d doesn't quite cut it (as we need to match an actual float number) - - // matches, e.g.: +14.56e-12, etc. - reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - - reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*' + - '$' - ); - - function hasAncestorWithNodeName(element, nodeName) { - while (element && (element = element.parentNode)) { - if (nodeName.test(element.nodeName)) { - return true; + function normalizeValue(attr, value, parentAttributes) { + var isArray; + if ((attr === "fill" || attr === "stroke") && value === "none") { + value = ""; + } else if (attr === "fillRule") { + value = value === "evenodd" ? "destination-over" : value; + } else if (attr === "strokeDashArray") { + value = value.replace(/,/g, " ").split(/\s+/); + } else if (attr === "transformMatrix") { + if (parentAttributes && parentAttributes.transformMatrix) { + value = multiplyTransformMatrices(parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); + } else { + value = fabric.parseTransformAttribute(value); + } + } else if (attr === "visible") { + value = value === "none" || value === "hidden" ? false : true; + if (parentAttributes.visible === false) { + value = false; + } + } else if (attr === "originX") { + value = value === "start" ? "left" : value === "end" ? "right" : "center"; } - } - return false; + isArray = Object.prototype.toString.call(value) === "[object Array]"; + var parsed = isArray ? value.map(parseFloat) : parseFloat(value); + return !isArray && isNaN(parsed) ? value : parsed; } - - return function(doc, callback, reviver) { - if (!doc) return; - - var startTime = new Date(), - descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - - if (descendants.length === 0 && fabric.isLikelyNode) { - // we're likely in node, where "o3-xml" library fails to gEBTN("*") - // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes('//*[name(.)!="svg"]'); - var arr = [ ]; - for (var i = 0, len = descendants.length; i < len; i++) { - arr[i] = descendants[i]; + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === "undefined") continue; + if (attributes[attr].indexOf("url(") === 0) continue; + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + delete attributes[colorAttributes[attr]]; } - descendants = arr; - } - - var elements = descendants.filter(function(el) { - return reAllowedSVGTagNames.test(el.tagName) && - !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement - }); - - if (!elements || (elements && !elements.length)) { - callback && callback([], {}); - return; - } - - var viewBoxAttr = doc.getAttribute('viewBox'), - widthAttr = parseFloat(doc.getAttribute('width')), - heightAttr = parseFloat(doc.getAttribute('height')), - width = null, - height = null, - viewBoxWidth, - viewBoxHeight, - minX, - minY; - - if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - minX = parseFloat(viewBoxAttr[1]); - minY = parseFloat(viewBoxAttr[2]); - viewBoxWidth = parseFloat(viewBoxAttr[3]); - viewBoxHeight = parseFloat(viewBoxAttr[4]); - } - - if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) { - width = viewBoxWidth; - height = viewBoxHeight; - } - else { - // values of width/height attributes overwrite those extracted from viewbox attribute - width = widthAttr ? widthAttr : viewBoxWidth; - height = heightAttr ? heightAttr : viewBoxHeight; - } - - var options = { - width: width, - height: height, - widthAttr: widthAttr, - heightAttr: heightAttr - }; - - fabric.gradientDefs = fabric.getGradientDefs(doc); - fabric.cssRules = fabric.getCSSRules(doc); - - // Precedence of rules: style > class > attribute - - fabric.parseElements(elements, function(instances) { - fabric.documentParsingTime = new Date() - startTime; - if (callback) { - callback(instances, options); - } - }, clone(options), reviver); - }; - })(); - - /** - * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) - * @namespace - */ - var svgCache = { - - /** - * @param {String} name - * @param {Function} callback - */ - has: function (name, callback) { - callback(false); - }, - - /** - * @param {String} url - * @param {Function} callback - */ - get: function () { - /* NOOP */ - }, - - /** - * @param {String} url - * @param {Object} object - */ - set: function () { - /* NOOP */ - } - }; - - /** - * @private - */ - function _enlivenCachedObject(cachedObject) { - - var objects = cachedObject.objects, - options = cachedObject.options; - - objects = objects.map(function (o) { - return fabric[capitalize(o.type)].fromObject(o); - }); - - return ({ objects: objects, options: options }); - } - - /** - * @private - */ - function _createSVGPattern(markup, canvas, property) { - if (canvas[property] && canvas[property].toSVG) { - markup.push( - '', - '' - ); + return attributes; } - } - - extend(fabric, { - - /** - * Initializes gradients on instances, according to gradients parsed from a document - * @param {Array} instances - */ - resolveGradients: function(instances) { - for (var i = instances.length; i--; ) { - var instanceFillValue = instances[i].get('fill'); - - if (!(/^url\(/).test(instanceFillValue)) continue; - - var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); - - if (fabric.gradientDefs[gradientId]) { - instances[i].set('fill', - fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i])); + fabric.parseTransformAttribute = function() { + function rotateMatrix(matrix, args) { + var angle = args[0]; + matrix[0] = Math.cos(angle); + matrix[1] = Math.sin(angle); + matrix[2] = -Math.sin(angle); + matrix[3] = Math.cos(angle); } - } - }, - - /** - * Parses an SVG document, returning all of the gradient declarations found in it - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element - */ - getGradientDefs: function(doc) { - var linearGradientEls = doc.getElementsByTagName('linearGradient'), - radialGradientEls = doc.getElementsByTagName('radialGradient'), - el, i, - gradientDefs = { }; - - i = linearGradientEls.length; - for (; i--; ) { - el = linearGradientEls[i]; - gradientDefs[el.getAttribute('id')] = el; - } - - i = radialGradientEls.length; - for (; i--; ) { - el = radialGradientEls[i]; - gradientDefs[el.getAttribute('id')] = el; - } - - return gradientDefs; - }, - - /** - * Returns an object of attributes' name/value, given element and an array of attribute names; - * Parses parent "g" nodes recursively upwards. - * @static - * @memberOf fabric - * @param {DOMElement} element Element to parse - * @param {Array} attributes Array of attributes to parse - * @return {Object} object containing parsed attributes' names/values - */ - parseAttributes: function(element, attributes) { - - if (!element) { - return; - } - - var value, - parentAttributes = { }; - - // if there's a parent container (`g` node), parse its attributes recursively upwards - if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) { - parentAttributes = fabric.parseAttributes(element.parentNode, attributes); - } - - var ownAttributes = attributes.reduce(function(memo, attr) { - value = element.getAttribute(attr); - if (value) { - attr = normalizeAttr(attr); - value = normalizeValue(attr, value, parentAttributes); - - memo[attr] = value; + function scaleMatrix(matrix, args) { + var multiplierX = args[0], multiplierY = args.length === 2 ? args[1] : args[0]; + matrix[0] = multiplierX; + matrix[3] = multiplierY; } - return memo; - }, { }); - - // add values parsed from style, which take precedence over attributes - // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) - ownAttributes = extend(ownAttributes, - extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); - - return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); - }, - - /** - * Transforms an array of svg elements to corresponding fabric.* instances - * @static - * @memberOf fabric - * @param {Array} elements Array of elements to parse - * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) - * @param {Object} [options] Options object - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - parseElements: function(elements, callback, options, reviver) { - new fabric.ElementsParser(elements, callback, options, reviver).parse(); - }, - - /** - * Parses "style" attribute, retuning an object with values - * @static - * @memberOf fabric - * @param {SVGElement} element Element to parse - * @return {Object} Objects with values parsed from style attribute of an element - */ - parseStyleAttribute: function(element) { - var oStyle = { }, - style = element.getAttribute('style'); - - if (!style) { - return oStyle; - } - - if (typeof style === 'string') { - parseStyleString(style, oStyle); - } - else { - parseStyleObject(style, oStyle); - } - - return oStyle; - }, - - /** - * Parses "points" attribute, returning an array of values - * @static - * @memberOf fabric - * @param points {String} points attribute string - * @return {Array} array of points - */ - parsePointsAttribute: function(points) { - - // points attribute is required and must not be empty - if (!points) return null; - - points = points.trim(); - var asPairs = points.indexOf(',') > -1; - - points = points.split(/\s+/); - var parsedPoints = [ ], i, len; - - // points could look like "10,20 30,40" or "10 20 30 40" - if (asPairs) { - i = 0; - len = points.length; - for (; i < len; i++) { - var pair = points[i].split(','); - parsedPoints.push({ - x: parseFloat(pair[0]), - y: parseFloat(pair[1]) - }); + function skewXMatrix(matrix, args) { + matrix[2] = args[0]; } - } - else { - i = 0; - len = points.length; - for (; i < len; i+=2) { - parsedPoints.push({ - x: parseFloat(points[i]), - y: parseFloat(points[i + 1]) - }); + function skewYMatrix(matrix, args) { + matrix[1] = args[0]; } - } + function translateMatrix(matrix, args) { + matrix[4] = args[0]; + if (args.length === 2) { + matrix[5] = args[1]; + } + } + var iMatrix = [ 1, 0, 0, 1, 0, 0 ], number = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)", commaWsp = "(?:\\s+,?\\s*|,\\s*)", skewX = "(?:(skewX)\\s*\\(\\s*(" + number + ")\\s*\\))", skewY = "(?:(skewY)\\s*\\(\\s*(" + number + ")\\s*\\))", rotate = "(?:(rotate)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + "))?\\s*\\))", scale = "(?:(scale)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + "))?\\s*\\))", translate = "(?:(translate)\\s*\\(\\s*(" + number + ")(?:" + commaWsp + "(" + number + "))?\\s*\\))", matrix = "(?:(matrix)\\s*\\(\\s*" + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + commaWsp + "(" + number + ")" + "\\s*\\))", transform = "(?:" + matrix + "|" + translate + "|" + scale + "|" + rotate + "|" + skewX + "|" + skewY + ")", transforms = "(?:" + transform + "(?:" + commaWsp + transform + ")*" + ")", transformList = "^\\s*(?:" + transforms + "?)\\s*$", reTransformList = new RegExp(transformList), reTransform = new RegExp(transform, "g"); + return function(attributeValue) { + var matrix = iMatrix.concat(), matrices = []; + if (!attributeValue || attributeValue && !reTransformList.test(attributeValue)) { + return matrix; + } + attributeValue.replace(reTransform, function(match) { + var m = new RegExp(transform).exec(match).filter(function(match) { + return match !== "" && match != null; + }), operation = m[1], args = m.slice(2).map(parseFloat); + switch (operation) { + case "translate": + translateMatrix(matrix, args); + break; - // odd number of points is an error - // if (parsedPoints.length % 2 !== 0) { - // return null; - // } + case "rotate": + args[0] = fabric.util.degreesToRadians(args[0]); + rotateMatrix(matrix, args); + break; - return parsedPoints; - }, + case "scale": + scaleMatrix(matrix, args); + break; - /** - * Returns CSS rules for a given SVG document - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} CSS rules of this document - */ - getCSSRules: function(doc) { - var styles = doc.getElementsByTagName('style'), - allRules = { }, - rules; + case "skewX": + skewXMatrix(matrix, args); + break; - // very crude parsing of style contents - for (var i = 0, len = styles.length; i < len; i++) { - var styleContents = styles[0].textContent; + case "skewY": + skewYMatrix(matrix, args); + break; - // remove comments - styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); - - rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); - rules = rules.map(function(rule) { return rule.trim(); }); - - rules.forEach(function(rule) { - var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/); - rule = match[1]; - var declaration = match[2].trim(), - propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); - - if (!allRules[rule]) { - allRules[rule] = { }; - } - - for (var i = 0, len = propertyValuePairs.length; i < len; i++) { - var pair = propertyValuePairs[i].split(/\s*:\s*/), - property = pair[0], - value = pair[1]; - - allRules[rule][property] = value; - } + case "matrix": + matrix = args; + break; + } + matrices.push(matrix.concat()); + matrix = iMatrix.concat(); + }); + var combinedMatrix = matrices[0]; + while (matrices.length > 1) { + matrices.shift(); + combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); + } + return combinedMatrix; + }; + }(); + function parseFontDeclaration(value, oStyle) { + var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); + if (!match) return; + var fontStyle = match[1], fontWeight = match[3], fontSize = match[4], lineHeight = match[5], fontFamily = match[6]; + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseFloat(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === "normal" ? 1 : lineHeight; + } + } + function parseStyleString(style, oStyle) { + var attr, value; + style.replace(/;$/, "").split(";").forEach(function(chunk) { + var pair = chunk.split(":"); + attr = normalizeAttr(pair[0].trim().toLowerCase()); + value = normalizeValue(attr, pair[1].trim()); + if (attr === "font") { + parseFontDeclaration(value, oStyle); + } else { + oStyle[attr] = value; + } }); - } - - return allRules; - }, - - /** - * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) - * @memberof fabric - * @param {String} url - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromURL: function(url, callback, reviver) { - - url = url.replace(/^\n\s*/, '').trim(); - - svgCache.has(url, function (hasUrl) { - if (hasUrl) { - svgCache.get(url, function (value) { - var enlivedRecord = _enlivenCachedObject(value); - callback(enlivedRecord.objects, enlivedRecord.options); - }); - } - else { - new fabric.util.request(url, { - method: 'get', - onComplete: onComplete - }); - } - }); - - function onComplete(r) { - - var xml = r.responseXML; - if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { - xml = new ActiveXObject('Microsoft.XMLDOM'); - xml.async = 'false'; - //IE chokes on DOCTYPE - xml.loadXML(r.responseText.replace(//i,'')); - } - if (!xml || !xml.documentElement) return; - - fabric.parseSVGDocument(xml.documentElement, function (results, options) { - svgCache.set(url, { - objects: fabric.util.array.invoke(results, 'toObject'), - options: options - }); - callback(results, options); - }, reviver); - } - }, - - /** - * Takes string corresponding to an SVG document, and parses it into a set of fabric objects - * @memberof fabric - * @param {String} string - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromString: function(string, callback, reviver) { - string = string.trim(); - var doc; - if (typeof DOMParser !== 'undefined') { - var parser = new DOMParser(); - if (parser && parser.parseFromString) { - doc = parser.parseFromString(string, 'text/xml'); - } - } - else if (fabric.window.ActiveXObject) { - doc = new ActiveXObject('Microsoft.XMLDOM'); - doc.async = 'false'; - //IE chokes on DOCTYPE - doc.loadXML(string.replace(//i,'')); - } - - fabric.parseSVGDocument(doc.documentElement, function (results, options) { - callback(results, options); - }, reviver); - }, - - /** - * Creates markup containing SVG font faces - * @param {Array} objects Array of fabric objects - * @return {String} - */ - createSVGFontFacesMarkup: function(objects) { - var markup = ''; - - for (var i = 0, len = objects.length; i < len; i++) { - if (objects[i].type !== 'text' || !objects[i].path) continue; - - markup += [ - '@font-face {', - 'font-family: ', objects[i].fontFamily, '; ', - 'src: url(\'', objects[i].path, '\')', - '}' - ].join(''); - } - - if (markup) { - markup = [ - '' - ].join(''); - } - - return markup; - }, - - /** - * Creates markup containing SVG referenced elements like patterns, gradients etc. - * @param {fabric.Canvas} canvas instance of fabric.Canvas - * @return {String} - */ - createSVGRefElementsMarkup: function(canvas) { - var markup = [ ]; - - _createSVGPattern(markup, canvas, 'backgroundColor'); - _createSVGPattern(markup, canvas, 'overlayColor'); - - return markup.join(''); } - }); - -})(typeof exports !== 'undefined' ? exports : this); - + function parseStyleObject(style, oStyle) { + var attr, value; + for (var prop in style) { + if (typeof style[prop] === "undefined") continue; + attr = normalizeAttr(prop.toLowerCase()); + value = normalizeValue(attr, style[prop]); + if (attr === "font") { + parseFontDeclaration(value, oStyle); + } else { + oStyle[attr] = value; + } + } + } + function getGlobalStylesForElement(element) { + var nodeName = element.nodeName, className = element.getAttribute("class"), id = element.getAttribute("id"), styles = {}; + for (var rule in fabric.cssRules) { + var ruleMatchesElement = className && new RegExp("^\\." + className).test(rule) || id && new RegExp("^#" + id).test(rule) || new RegExp("^" + nodeName).test(rule); + if (ruleMatchesElement) { + for (var property in fabric.cssRules[rule]) { + styles[property] = fabric.cssRules[rule][property]; + } + } + } + return styles; + } + fabric.parseSVGDocument = function() { + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)", reViewBoxAttrValue = new RegExp("^" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*,?" + "\\s*(" + reNum + "+)\\s*" + "$"); + function hasAncestorWithNodeName(element, nodeName) { + while (element && (element = element.parentNode)) { + if (nodeName.test(element.nodeName)) { + return true; + } + } + return false; + } + return function(doc, callback, reviver) { + if (!doc) return; + var startTime = new Date(), descendants = fabric.util.toArray(doc.getElementsByTagName("*")); + if (descendants.length === 0 && fabric.isLikelyNode) { + descendants = doc.selectNodes('//*[name(.)!="svg"]'); + var arr = []; + for (var i = 0, len = descendants.length; i < len; i++) { + arr[i] = descendants[i]; + } + descendants = arr; + } + var elements = descendants.filter(function(el) { + return reAllowedSVGTagNames.test(el.tagName) && !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); + }); + if (!elements || elements && !elements.length) { + callback && callback([], {}); + return; + } + var viewBoxAttr = doc.getAttribute("viewBox"), widthAttr = parseFloat(doc.getAttribute("width")), heightAttr = parseFloat(doc.getAttribute("height")), width = null, height = null, viewBoxWidth, viewBoxHeight, minX, minY; + if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { + minX = parseFloat(viewBoxAttr[1]); + minY = parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); + } + if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) { + width = viewBoxWidth; + height = viewBoxHeight; + } else { + width = widthAttr ? widthAttr : viewBoxWidth; + height = heightAttr ? heightAttr : viewBoxHeight; + } + var options = { + width: width, + height: height, + widthAttr: widthAttr, + heightAttr: heightAttr + }; + fabric.gradientDefs = fabric.getGradientDefs(doc); + fabric.cssRules = fabric.getCSSRules(doc); + fabric.parseElements(elements, function(instances) { + fabric.documentParsingTime = new Date() - startTime; + if (callback) { + callback(instances, options); + } + }, clone(options), reviver); + }; + }(); + var svgCache = { + has: function(name, callback) { + callback(false); + }, + get: function() {}, + set: function() {} + }; + function _enlivenCachedObject(cachedObject) { + var objects = cachedObject.objects, options = cachedObject.options; + objects = objects.map(function(o) { + return fabric[capitalize(o.type)].fromObject(o); + }); + return { + objects: objects, + options: options + }; + } + function _createSVGPattern(markup, canvas, property) { + if (canvas[property] && canvas[property].toSVG) { + markup.push('', ''); + } + } + extend(fabric, { + resolveGradients: function(instances) { + for (var i = instances.length; i--; ) { + var instanceFillValue = instances[i].get("fill"); + if (!/^url\(/.test(instanceFillValue)) continue; + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + if (fabric.gradientDefs[gradientId]) { + instances[i].set("fill", fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i])); + } + } + }, + getGradientDefs: function(doc) { + var linearGradientEls = doc.getElementsByTagName("linearGradient"), radialGradientEls = doc.getElementsByTagName("radialGradient"), el, i, gradientDefs = {}; + i = linearGradientEls.length; + for (;i--; ) { + el = linearGradientEls[i]; + gradientDefs[el.getAttribute("id")] = el; + } + i = radialGradientEls.length; + for (;i--; ) { + el = radialGradientEls[i]; + gradientDefs[el.getAttribute("id")] = el; + } + return gradientDefs; + }, + parseAttributes: function(element, attributes) { + if (!element) { + return; + } + var value, parentAttributes = {}; + if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes); + } + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + if (value) { + attr = normalizeAttr(attr); + value = normalizeValue(attr, value, parentAttributes); + memo[attr] = value; + } + return memo; + }, {}); + ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); + return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); + }, + parseElements: function(elements, callback, options, reviver) { + new fabric.ElementsParser(elements, callback, options, reviver).parse(); + }, + parseStyleAttribute: function(element) { + var oStyle = {}, style = element.getAttribute("style"); + if (!style) { + return oStyle; + } + if (typeof style === "string") { + parseStyleString(style, oStyle); + } else { + parseStyleObject(style, oStyle); + } + return oStyle; + }, + parsePointsAttribute: function(points) { + if (!points) return null; + points = points.trim(); + var asPairs = points.indexOf(",") > -1; + points = points.split(/\s+/); + var parsedPoints = [], i, len; + if (asPairs) { + i = 0; + len = points.length; + for (;i < len; i++) { + var pair = points[i].split(","); + parsedPoints.push({ + x: parseFloat(pair[0]), + y: parseFloat(pair[1]) + }); + } + } else { + i = 0; + len = points.length; + for (;i < len; i += 2) { + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); + } + } + return parsedPoints; + }, + getCSSRules: function(doc) { + var styles = doc.getElementsByTagName("style"), allRules = {}, rules; + for (var i = 0, len = styles.length; i < len; i++) { + var styleContents = styles[0].textContent; + styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ""); + rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); + rules = rules.map(function(rule) { + return rule.trim(); + }); + rules.forEach(function(rule) { + var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/); + rule = match[1]; + var declaration = match[2].trim(), propertyValuePairs = declaration.replace(/;$/, "").split(/\s*;\s*/); + if (!allRules[rule]) { + allRules[rule] = {}; + } + for (var i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), property = pair[0], value = pair[1]; + allRules[rule][property] = value; + } + }); + } + return allRules; + }, + loadSVGFromURL: function(url, callback, reviver) { + url = url.replace(/^\n\s*/, "").trim(); + svgCache.has(url, function(hasUrl) { + if (hasUrl) { + svgCache.get(url, function(value) { + var enlivedRecord = _enlivenCachedObject(value); + callback(enlivedRecord.objects, enlivedRecord.options); + }); + } else { + new fabric.util.request(url, { + method: "get", + onComplete: onComplete + }); + } + }); + function onComplete(r) { + var xml = r.responseXML; + if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + xml = new ActiveXObject("Microsoft.XMLDOM"); + xml.async = "false"; + xml.loadXML(r.responseText.replace(//i, "")); + } + if (!xml || !xml.documentElement) return; + fabric.parseSVGDocument(xml.documentElement, function(results, options) { + svgCache.set(url, { + objects: fabric.util.array.invoke(results, "toObject"), + options: options + }); + callback(results, options); + }, reviver); + } + }, + loadSVGFromString: function(string, callback, reviver) { + string = string.trim(); + var doc; + if (typeof DOMParser !== "undefined") { + var parser = new DOMParser(); + if (parser && parser.parseFromString) { + doc = parser.parseFromString(string, "text/xml"); + } + } else if (fabric.window.ActiveXObject) { + doc = new ActiveXObject("Microsoft.XMLDOM"); + doc.async = "false"; + doc.loadXML(string.replace(//i, "")); + } + fabric.parseSVGDocument(doc.documentElement, function(results, options) { + callback(results, options); + }, reviver); + }, + createSVGFontFacesMarkup: function(objects) { + var markup = ""; + for (var i = 0, len = objects.length; i < len; i++) { + if (objects[i].type !== "text" || !objects[i].path) continue; + markup += [ "@font-face {", "font-family: ", objects[i].fontFamily, "; ", "src: url('", objects[i].path, "')", "}" ].join(""); + } + if (markup) { + markup = [ '" ].join(""); + } + return markup; + }, + createSVGRefElementsMarkup: function(canvas) { + var markup = []; + _createSVGPattern(markup, canvas, "backgroundColor"); + _createSVGPattern(markup, canvas, "overlayColor"); + return markup.join(""); + } + }); +})(typeof exports !== "undefined" ? exports : this); fabric.ElementsParser = function(elements, callback, options, reviver) { - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; }; fabric.ElementsParser.prototype.parse = function() { - this.instances = new Array(this.elements.length); - this.numElements = this.elements.length; - - this.createObjects(); + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; + this.createObjects(); }; fabric.ElementsParser.prototype.createObjects = function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - (function(_this, i) { - setTimeout(function() { - _this.createObject(_this.elements[i], i); - }, 0); - })(this, i); - } + for (var i = 0, len = this.elements.length; i < len; i++) { + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } }; fabric.ElementsParser.prototype.createObject = function(el, index) { - var klass = fabric[fabric.util.string.capitalize(el.tagName)]; - if (klass && klass.fromElement) { - try { - this._createObject(klass, el, index); + var klass = fabric[fabric.util.string.capitalize(el.tagName)]; + if (klass && klass.fromElement) { + try { + this._createObject(klass, el, index); + } catch (err) { + fabric.log(err); + } + } else { + this.checkIfDone(); } - catch (err) { - fabric.log(err); - } - } - else { - this.checkIfDone(); - } }; fabric.ElementsParser.prototype._createObject = function(klass, el, index) { - if (klass.async) { - klass.fromElement(el, this.createCallback(index, el), this.options); - } - else { - var obj = klass.fromElement(el, this.options); - this.reviver && this.reviver(el, obj); - this.instances.splice(index, 0, obj); - this.checkIfDone(); - } + if (klass.async) { + klass.fromElement(el, this.createCallback(index, el), this.options); + } else { + var obj = klass.fromElement(el, this.options); + this.reviver && this.reviver(el, obj); + this.instances.splice(index, 0, obj); + this.checkIfDone(); + } }; fabric.ElementsParser.prototype.createCallback = function(index, el) { - var _this = this; - return function(obj) { - _this.reviver && _this.reviver(el, obj); - _this.instances.splice(index, 0, obj); - _this.checkIfDone(); - }; + var _this = this; + return function(obj) { + _this.reviver && _this.reviver(el, obj); + _this.instances.splice(index, 0, obj); + _this.checkIfDone(); + }; }; fabric.ElementsParser.prototype.checkIfDone = function() { - if (--this.numElements === 0) { - this.instances = this.instances.filter(function(el) { - return el != null; - }); - fabric.resolveGradients(this.instances); - this.callback(this.instances); - } + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + return el != null; + }); + fabric.resolveGradients(this.instances); + this.callback(this.instances); + } }; - (function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Point) { - fabric.warn('fabric.Point is already defined'); - return; - } - - fabric.Point = Point; - - /** - * Point class - * @class fabric.Point - * @memberOf fabric - * @constructor - * @param {Number} x - * @param {Number} y - * @return {fabric.Point} thisArg - */ - function Point(x, y) { - this.x = x; - this.y = y; - } - - Point.prototype = /** @lends fabric.Point.prototype */ { - - constructor: Point, - - /** - * Adds another point to this one and returns another one - * @param {fabric.Point} that - * @return {fabric.Point} new Point instance with added values - */ - add: function (that) { - return new Point(this.x + that.x, this.y + that.y); - }, - - /** - * Adds another point to this one - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - addEquals: function (that) { - this.x += that.x; - this.y += that.y; - return this; - }, - - /** - * Adds value to this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} new Point with added value - */ - scalarAdd: function (scalar) { - return new Point(this.x + scalar, this.y + scalar); - }, - - /** - * Adds value to this point - * @param {Number} scalar - * @param {fabric.Point} thisArg - */ - scalarAddEquals: function (scalar) { - this.x += scalar; - this.y += scalar; - return this; - }, - - /** - * Subtracts another point from this point and returns a new one - * @param {fabric.Point} that - * @return {fabric.Point} new Point object with subtracted values - */ - subtract: function (that) { - return new Point(this.x - that.x, this.y - that.y); - }, - - /** - * Subtracts another point from this point - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - */ - subtractEquals: function (that) { - this.x -= that.x; - this.y -= that.y; - return this; - }, - - /** - * Subtracts value from this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - scalarSubtract: function (scalar) { - return new Point(this.x - scalar, this.y - scalar); - }, - - /** - * Subtracts value from this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - scalarSubtractEquals: function (scalar) { - this.x -= scalar; - this.y -= scalar; - return this; - }, - - /** - * Miltiplies this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - multiply: function (scalar) { - return new Point(this.x * scalar, this.y * scalar); - }, - - /** - * Miltiplies this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - multiplyEquals: function (scalar) { - this.x *= scalar; - this.y *= scalar; - return this; - }, - - /** - * Divides this point by a value and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - divide: function (scalar) { - return new Point(this.x / scalar, this.y / scalar); - }, - - /** - * Divides this point by a value - * @param {Number} scalar - * @return {fabric.Point} thisArg - */ - divideEquals: function (scalar) { - this.x /= scalar; - this.y /= scalar; - return this; - }, - - /** - * Returns true if this point is equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - eq: function (that) { - return (this.x === that.x && this.y === that.y); - }, - - /** - * Returns true if this point is less than another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lt: function (that) { - return (this.x < that.x && this.y < that.y); - }, - - /** - * Returns true if this point is less than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lte: function (that) { - return (this.x <= that.x && this.y <= that.y); - }, - - /** - - * Returns true if this point is greater another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gt: function (that) { - return (this.x > that.x && this.y > that.y); - }, - - /** - * Returns true if this point is greater than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gte: function (that) { - return (this.x >= that.x && this.y >= that.y); - }, - - /** - * Returns new point which is the result of linear interpolation with this one and another one - * @param {fabric.Point} that - * @param {Number} t - * @return {fabric.Point} - */ - lerp: function (that, t) { - return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); - }, - - /** - * Returns distance from this point and another one - * @param {fabric.Point} that - * @return {Number} - */ - distanceFrom: function (that) { - var dx = this.x - that.x, - dy = this.y - that.y; - return Math.sqrt(dx * dx + dy * dy); - }, - - /** - * Returns the point between this point and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - midPointFrom: function (that) { - return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); - }, - - /** - * Returns a new point which is the min of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - min: function (that) { - return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); - }, - - /** - * Returns a new point which is the max of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - max: function (that) { - return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); - }, - - /** - * Returns string representation of this point - * @return {String} - */ - toString: function () { - return this.x + ',' + this.y; - }, - - /** - * Sets x/y of this point - * @param {Number} x - * @return {Number} y - */ - setXY: function (x, y) { - this.x = x; - this.y = y; - }, - - /** - * Sets x/y of this point from another point - * @param {fabric.Point} that - */ - setFromPoint: function (that) { - this.x = that.x; - this.y = that.y; - }, - - /** - * Swaps x/y of this point and another point - * @param {fabric.Point} that - */ - swap: function (that) { - var x = this.x, - y = this.y; - this.x = that.x; - this.y = that.y; - that.x = x; - that.y = y; - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - /** - * Appends a point to intersection - * @param {fabric.Point} point - */ - appendPoint: function (point) { - this.points.push(point); - }, - - /** - * Appends points to intersection - * @param {Array} points - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - } - }; - - /** - * Checks if one line intersects another - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (uB !== 0) { - var ua = uaT / uB, - ub = ubT / uB; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection('Intersection'); - result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (uaT === 0 || ubT === 0) { - result = new Intersection('Coincident'); - } - else { - result = new Intersection('Parallel'); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ - var result = new Intersection(), - length = points.length; - - for (var i = 0; i < length; i++) { - var b1 = points[i], - b2 = points[(i + 1) % length], - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i + 1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {Number} r1 - * @param {Number} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Color) { - fabric.warn('fabric.Color is already defined.'); - return; - } - - /** - * Color class - * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; - * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. - * - * @class fabric.Color - * @param {String} color optional in hex or rgb(a) format - * @return {fabric.Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} - */ - function Color(color) { - if (!color) { - this.setSource([0, 0, 0, 1]); - } - else { - this._tryParsingColor(color); - } - } - - fabric.Color = Color; - - fabric.Color.prototype = /** @lends fabric.Color.prototype */ { - - /** - * @private - * @param {String|Array} color Color value to parse - */ - _tryParsingColor: function(color) { - var source; - - if (color in Color.colorNameMap) { - color = Color.colorNameMap[color]; - } - - if (color === 'transparent') { - this.setSource([255,255,255,0]); + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Point) { + fabric.warn("fabric.Point is already defined"); return; - } - - source = Color.sourceFromHex(color); - - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (source) { - this.setSource(source); - } - }, - - /** - * Adapted from https://github.com/mjijackson - * @private - * @param {Number} r Red color value - * @param {Number} g Green color value - * @param {Number} b Blue color value - * @return {Array} Hsl color - */ - _rgbToHsl: function(r, g, b) { - r /= 255, g /= 255, b /= 255; - - var h, s, l, - max = fabric.util.array.max([r, g, b]), - min = fabric.util.array.min([r, g, b]); - - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } - else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; + } + fabric.Point = Point; + function Point(x, y) { + this.x = x; + this.y = y; + } + Point.prototype = { + constructor: Point, + add: function(that) { + return new Point(this.x + that.x, this.y + that.y); + }, + addEquals: function(that) { + this.x += that.x; + this.y += that.y; + return this; + }, + scalarAdd: function(scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + scalarAddEquals: function(scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + subtract: function(that) { + return new Point(this.x - that.x, this.y - that.y); + }, + subtractEquals: function(that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + scalarSubtract: function(scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + scalarSubtractEquals: function(scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + multiply: function(scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + multiplyEquals: function(scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + divide: function(scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + divideEquals: function(scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + eq: function(that) { + return this.x === that.x && this.y === that.y; + }, + lt: function(that) { + return this.x < that.x && this.y < that.y; + }, + lte: function(that) { + return this.x <= that.x && this.y <= that.y; + }, + gt: function(that) { + return this.x > that.x && this.y > that.y; + }, + gte: function(that) { + return this.x >= that.x && this.y >= that.y; + }, + lerp: function(that, t) { + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + distanceFrom: function(that) { + var dx = this.x - that.x, dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + midPointFrom: function(that) { + return new Point(this.x + (that.x - this.x) / 2, this.y + (that.y - this.y) / 2); + }, + min: function(that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + max: function(that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + toString: function() { + return this.x + "," + this.y; + }, + setXY: function(x, y) { + this.x = x; + this.y = y; + }, + setFromPoint: function(that) { + this.x = that.x; + this.y = that.y; + }, + swap: function(that) { + var x = this.x, y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; } - h /= 6; - } + }; +})(typeof exports !== "undefined" ? exports : this); - return [ - Math.round(h * 360), - Math.round(s * 100), - Math.round(l * 100) - ]; - }, - - /** - * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} - */ - getSource: function() { - return this._source; - }, - - /** - * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source - */ - setSource: function(source) { - this._source = source; - }, - - /** - * Returns color represenation in RGB format - * @return {String} ex: rgb(0-255,0-255,0-255) - */ - toRgb: function() { - var source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; - }, - - /** - * Returns color represenation in RGBA format - * @return {String} ex: rgba(0-255,0-255,0-255,0-1) - */ - toRgba: function() { - var source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; - }, - - /** - * Returns color represenation in HSL format - * @return {String} ex: hsl(0-360,0%-100%,0%-100%) - */ - toHsl: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; - }, - - /** - * Returns color represenation in HSLA format - * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) - */ - toHsla: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; - }, - - /** - * Returns color represenation in HEX format - * @return {String} ex: FF5555 - */ - toHex: function() { - var source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); - }, - - /** - * Gets value of alpha channel for this color - * @return {Number} 0-1 - */ - getAlpha: function() { - return this.getSource()[3]; - }, - - /** - * Sets value of alpha channel for this color - * @param {Number} alpha Alpha value 0-1 - * @return {fabric.Color} thisArg - */ - setAlpha: function(alpha) { - var source = this.getSource(); - source[3] = alpha; - this.setSource(source); - return this; - }, - - /** - * Transforms color to its grayscale representation - * @return {fabric.Color} thisArg - */ - toGrayscale: function() { - var source = this.getSource(), - average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), - currentAlpha = source[3]; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Transforms color to its black and white representation - * @param {Number} threshold - * @return {fabric.Color} thisArg - */ - toBlackWhite: function(threshold) { - var source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), - currentAlpha = source[3]; - - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Overlays color with another color - * @param {String|fabric.Color} otherColor - * @return {fabric.Color} thisArg - */ - overlayWith: function(otherColor) { - if (!(otherColor instanceof Color)) { - otherColor = new Color(otherColor); - } - - var result = [], - alpha = this.getAlpha(), - otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(); - - for (var i = 0; i < 3; i++) { - result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); - } - - result[3] = alpha; - this.setSource(result); - return this; +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Intersection) { + fabric.warn("fabric.Intersection is already defined"); + return; } - }; - - /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HEX format (ex: #FF5555, 010155, aff) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; - - /** - * Map of the 17 basic color names with HEX code - * @static - * @field - * @memberOf fabric.Color - * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units - */ - fabric.Color.colorNameMap = { - aqua: '#00FFFF', - black: '#000000', - blue: '#0000FF', - fuchsia: '#FF00FF', - gray: '#808080', - green: '#008000', - lime: '#00FF00', - maroon: '#800000', - navy: '#000080', - olive: '#808000', - orange: '#FFA500', - purple: '#800080', - red: '#FF0000', - silver: '#C0C0C0', - teal: '#008080', - white: '#FFFFFF', - yellow: '#FFFF00' - }; - - /** - * @private - * @param {Number} p - * @param {Number} q - * @param {Number} t - * @return {Number} - */ - function hue2rgb(p, q, t){ - if (t < 0) { - t += 1; + function Intersection(status) { + this.status = status; + this.points = []; } - if (t > 1) { - t -= 1; + fabric.Intersection = Intersection; + fabric.Intersection.prototype = { + appendPoint: function(point) { + this.points.push(point); + }, + appendPoints: function(points) { + this.points = this.points.concat(points); + } + }; + fabric.Intersection.intersectLineLine = function(a1, a2, b1, b2) { + var result, uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, ub = ubT / uB; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection("Intersection"); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } else { + result = new Intersection(); + } + } else { + if (uaT === 0 || ubT === 0) { + result = new Intersection("Coincident"); + } else { + result = new Intersection("Parallel"); + } + } + return result; + }; + fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { + var result = new Intersection(), length = points.length; + for (var i = 0; i < length; i++) { + var b1 = points[i], b2 = points[(i + 1) % length], inter = Intersection.intersectLineLine(a1, a2, b1, b2); + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + fabric.Intersection.intersectPolygonPolygon = function(points1, points2) { + var result = new Intersection(), length = points1.length; + for (var i = 0; i < length; i++) { + var a1 = points1[i], a2 = points1[(i + 1) % length], inter = Intersection.intersectLinePolygon(a1, a2, points2); + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + fabric.Intersection.intersectPolygonRectangle = function(points, r1, r2) { + var min = r1.min(r2), max = r1.max(r2), topRight = new fabric.Point(max.x, min.y), bottomLeft = new fabric.Point(min.x, max.y), inter1 = Intersection.intersectLinePolygon(min, topRight, points), inter2 = Intersection.intersectLinePolygon(topRight, max, points), inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), result = new Intersection(); + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Color) { + fabric.warn("fabric.Color is already defined."); + return; } - if (t < 1/6) { - return p + (q - p) * 6 * t; + function Color(color) { + if (!color) { + this.setSource([ 0, 0, 0, 1 ]); + } else { + this._tryParsingColor(color); + } } - if (t < 1/2) { - return q; + fabric.Color = Color; + fabric.Color.prototype = { + _tryParsingColor: function(color) { + var source; + if (color in Color.colorNameMap) { + color = Color.colorNameMap[color]; + } + if (color === "transparent") { + this.setSource([ 255, 255, 255, 0 ]); + return; + } + source = Color.sourceFromHex(color); + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (source) { + this.setSource(source); + } + }, + _rgbToHsl: function(r, g, b) { + r /= 255, g /= 255, b /= 255; + var h, s, l, max = fabric.util.array.max([ r, g, b ]), min = fabric.util.array.min([ r, g, b ]); + l = (max + min) / 2; + if (max === min) { + h = s = 0; + } else { + var d = max - min; + s = l > .5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + return [ Math.round(h * 360), Math.round(s * 100), Math.round(l * 100) ]; + }, + getSource: function() { + return this._source; + }, + setSource: function(source) { + this._source = source; + }, + toRgb: function() { + var source = this.getSource(); + return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")"; + }, + toRgba: function() { + var source = this.getSource(); + return "rgba(" + source[0] + "," + source[1] + "," + source[2] + "," + source[3] + ")"; + }, + toHsl: function() { + var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); + return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)"; + }, + toHsla: function() { + var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); + return "hsla(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%," + source[3] + ")"; + }, + toHex: function() { + var source = this.getSource(), r, g, b; + r = source[0].toString(16); + r = r.length === 1 ? "0" + r : r; + g = source[1].toString(16); + g = g.length === 1 ? "0" + g : g; + b = source[2].toString(16); + b = b.length === 1 ? "0" + b : b; + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + getAlpha: function() { + return this.getSource()[3]; + }, + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + toGrayscale: function() { + var source = this.getSource(), average = parseInt((source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), 10), currentAlpha = source[3]; + this.setSource([ average, average, average, currentAlpha ]); + return this; + }, + toBlackWhite: function(threshold) { + var source = this.getSource(), average = (source[0] * .3 + source[1] * .59 + source[2] * .11).toFixed(0), currentAlpha = source[3]; + threshold = threshold || 127; + average = Number(average) < Number(threshold) ? 0 : 255; + this.setSource([ average, average, average, currentAlpha ]); + return this; + }, + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + var result = [], alpha = this.getAlpha(), otherAlpha = .5, source = this.getSource(), otherSource = otherColor.getSource(); + for (var i = 0; i < 3; i++) { + result.push(Math.round(source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha)); + } + result[3] = alpha; + this.setSource(result); + return this; + } + }; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; + fabric.Color.colorNameMap = { + aqua: "#00FFFF", + black: "#000000", + blue: "#0000FF", + fuchsia: "#FF00FF", + gray: "#808080", + green: "#008000", + lime: "#00FF00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + orange: "#FFA500", + purple: "#800080", + red: "#FF0000", + silver: "#C0C0C0", + teal: "#008080", + white: "#FFFFFF", + yellow: "#FFFF00" + }; + function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; } - if (t < 2/3) { - return p + (q - p) * (2/3 - t) * 6; - } - return p; - } - - /** - * Returns new color object, when given a color in RGB format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255) - * @return {fabric.Color} - */ - fabric.Color.fromRgb = function(color) { - return Color.fromSource(Color.sourceFromRgb(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {Array} source - */ - fabric.Color.sourceFromRgb = function(color) { - var match = color.match(Color.reRGBa); - if (match) { - var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), - g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), - b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); - - return [ - parseInt(r, 10), - parseInt(g, 10), - parseInt(b, 10), - match[4] ? parseFloat(match[4]) : 1 - ]; - } - }; - - /** - * Returns new color object, when given a color in RGBA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromRgba = Color.fromRgb; - - /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) - * @memberOf fabric.Color - * @return {fabric.Color} - */ - fabric.Color.fromHsl = function(color) { - return Color.fromSource(Color.sourceFromHsl(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. - * Adapted from https://github.com/mjijackson - * @memberOf fabric.Color - * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {Array} source - * @see http://http://www.w3.org/TR/css3-color/#hsl-color - */ - fabric.Color.sourceFromHsl = function(color) { - var match = color.match(Color.reHSLa); - if (!match) return; - - var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, - s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; - - if (s === 0) { - r = g = b = l; - } - else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, - p = l * 2 - q; - - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [ - Math.round(r * 255), - Math.round(g * 255), - Math.round(b * 255), - match[4] ? parseFloat(match[4]) : 1 - ]; - }; - - /** - * Returns new color object, when given a color in HSLA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromHsla = Color.fromHsl; - - /** - * Returns new color object, when given a color in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color Color value ex: FF5555 - * @return {fabric.Color} - */ - fabric.Color.fromHex = function(color) { - return Color.fromSource(Color.sourceFromHex(color)); - }; - - /** - * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color ex: FF5555 - * @return {Array} source - */ - fabric.Color.sourceFromHex = function(color) { - if (color.match(Color.reHex)) { - var value = color.slice(color.indexOf('#') + 1), - isShortNotation = (value.length === 3), - r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), - g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), - b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); - - return [ - parseInt(r, 16), - parseInt(g, 16), - parseInt(b, 16), - 1 - ]; - } - }; - - /** - * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @static - * @memberOf fabric.Color - * @param {Array} source - * @return {fabric.Color} - */ - fabric.Color.fromSource = function(source) { - var oColor = new Color(); - oColor.setSource(source); - return oColor; - }; - -})(typeof exports !== 'undefined' ? exports : this); - + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + return [ parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ]; + } + }; + fabric.Color.fromRgba = Color.fromRgb; + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) return; + var h = (parseFloat(match[1]) % 360 + 360) % 360 / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), r, g, b; + if (s === 0) { + r = g = b = l; + } else { + var q = l <= .5 ? l * (s + 1) : l + s - l * s, p = l * 2 - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ]; + }; + fabric.Color.fromHsla = Color.fromHsl; + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + fabric.Color.sourceFromHex = function(color) { + if (color.match(Color.reHex)) { + var value = color.slice(color.indexOf("#") + 1), isShortNotation = value.length === 3, r = isShortNotation ? value.charAt(0) + value.charAt(0) : value.substring(0, 2), g = isShortNotation ? value.charAt(1) + value.charAt(1) : value.substring(2, 4), b = isShortNotation ? value.charAt(2) + value.charAt(2) : value.substring(4, 6); + return [ parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1 ]; + } + }; + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; +})(typeof exports !== "undefined" ? exports : this); (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; + function getColorStop(el) { + var style = el.getAttribute("style"), offset = el.getAttribute("offset"), color, opacity; + 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; + } + } } - else if (key === 'stop-opacity') { - opacity = value; + if (!color) { + color = el.getAttribute("stop-color") || "rgb(0,0,0)"; } - } - } - - 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(); + if (!opacity) { + opacity = el.getAttribute("stop-opacity"); } - 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_ */ - -})(); - - -/** - * Pattern class - * @class fabric.Pattern - * @see {@link http://fabricjs.com/patterns/|Pattern demo} - * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} - * @see {@link fabric.Pattern#initialize} for constructor definition - */ -fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { - - /** - * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @type String - * @default - */ - repeat: 'repeat', - - /** - * Pattern horizontal offset from object's left/top corner - * @type Number - * @default - */ - offsetX: 0, - - /** - * Pattern vertical offset from object's left/top corner - * @type Number - * @default - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Pattern} thisArg - */ - initialize: function(options) { - options || (options = { }); - - this.id = fabric.Object.__uid++; - - if (options.source) { - if (typeof options.source === 'string') { - // function string - if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { - this.source = new Function(fabric.util.getFunctionBody(options.source)); - } - else { - // img src string - var _this = this; - this.source = fabric.util.createImage(); - fabric.util.loadImage(options.source, function(img) { - _this.source = img; - }); - } - } - else { - // img element - this.source = options.source; - } - } - if (options.repeat) { - this.repeat = options.repeat; - } - if (options.offsetX) { - this.offsetX = options.offsetX; - } - if (options.offsetY) { - this.offsetY = options.offsetY; - } - }, - - /** - * Returns object representation of a pattern - * @return {Object} Object representation of a pattern instance - */ - toObject: function() { - - var source; - - // callback - if (typeof this.source === 'function') { - source = String(this.source); - } - // element - else if (typeof this.source.src === 'string') { - source = this.source.src; - } - - return { - source: source, - repeat: this.repeat, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a pattern - * @param {fabric.Object} object - * @return {String} SVG representation of a pattern - */ - toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source, - patternWidth = patternSource.width / object.getWidth(), - patternHeight = patternSource.height / object.getHeight(), - patternImgSrc = ''; - - if (patternSource.src) { - patternImgSrc = patternSource.src; - } - else if (patternSource.toDataURL) { - patternImgSrc = patternSource.toDataURL(); - } - - return '' + - '' + - ''; - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasPattern - * @param {CanvasRenderingContext2D} ctx Context to create pattern - * @return {CanvasPattern} - */ - toLive: function(ctx) { - var source = typeof this.source === 'function' - ? this.source() - : this.source; - - // if the image failed to load, return, and allow rest to continue loading - if (!source) { - return ''; - } - - // if an image - if (typeof source.src !== 'undefined') { - if (!source.complete) { - return ''; - } - if (source.naturalWidth === 0 || source.naturalHeight === 0) { - return ''; - } - } - return ctx.createPattern(source, this.repeat); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Shadow) { - fabric.warn('fabric.Shadow is already defined.'); - return; - } - - /** - * Shadow class - * @class fabric.Shadow - * @see {@link http://fabricjs.com/shadows/|Shadow demo} - * @see {@link fabric.Shadow#initialize} for constructor definition - */ - fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { - - /** - * Shadow color - * @type String - * @default - */ - color: 'rgb(0,0,0)', - - /** - * Shadow blur - * @type Number - */ - blur: 0, - - /** - * Shadow horizontal offset - * @type Number - * @default - */ - offsetX: 0, - - /** - * Shadow vertical offset - * @type Number - * @default - */ - offsetY: 0, - - /** - * Whether the shadow should affect stroke operations - * @type Boolean - * @default - */ - affectStroke: false, - - /** - * Indicates whether toObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Constructor - * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Shadow} thisArg - */ - initialize: function(options) { - if (typeof options === 'string') { - options = this._parseShadow(options); - } - - for (var prop in options) { - this[prop] = options[prop]; - } - - this.id = fabric.Object.__uid++; - }, - - /** - * @private - * @param {String} shadow Shadow value to parse - * @return {Object} Shadow object with color, offsetX, offsetY and blur - */ - _parseShadow: function(shadow) { - var shadowStr = shadow.trim(), - offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], - color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; - - return { - color: color.trim(), - offsetX: parseInt(offsetsAndBlur[1], 10) || 0, - offsetY: parseInt(offsetsAndBlur[2], 10) || 0, - blur: parseInt(offsetsAndBlur[3], 10) || 0 - }; - }, - - /** - * Returns a string representation of an instance - * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow - * @return {String} Returns CSS3 text-shadow declaration - */ - toString: function() { - return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a shadow - * @param {fabric.Object} object - * @return {String} SVG representation of a shadow - */ - toSVG: function(object) { - var mode = 'SourceAlpha'; - - if (object && (object.fill === this.color || object.stroke === this.color)) { - mode = 'SourceGraphic'; - } - - return ( - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns object representation of a shadow - * @return {Object} Object representation of a shadow instance - */ - toObject: function() { - if (this.includeDefaultValues) { + color = new fabric.Color(color).toRgb(); return { - color: this.color, - blur: this.blur, - offsetX: this.offsetX, - offsetY: this.offsetY + offset: offset, + color: color, + opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity) }; - } - var obj = { }, proto = fabric.Shadow.prototype; - if (this.color !== proto.color) { - obj.color = this.color; - } - if (this.blur !== proto.blur) { - obj.blur = this.blur; - } - if (this.offsetX !== proto.offsetX) { - obj.offsetX = this.offsetX; - } - if (this.offsetY !== proto.offsetY) { - obj.offsetY = this.offsetY; - } - return obj; } - }); - - /** - * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") - * @static - * @field - * @memberOf fabric.Shadow - */ - fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function () { - - 'use strict'; - - if (fabric.StaticCanvas) { - fabric.warn('fabric.StaticCanvas is already defined.'); - return; - } - - // aliases for faster resolution - var extend = fabric.util.object.extend, - getElementOffset = fabric.util.getElementOffset, - removeFromArray = fabric.util.removeFromArray, - - CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); - - /** - * Static canvas class - * @class fabric.StaticCanvas - * @mixes fabric.Collection - * @mixes fabric.Observable - * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} - * @see {@link fabric.StaticCanvas#initialize} for constructor definition - * @fires before:render - * @fires after:render - * @fires canvas:cleared - * @fires object:added - * @fires object:removed - */ - fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - fabric.StaticCanvas.activeInstance = this; - }, - - /** - * Background color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. - * @type {(String|fabric.Pattern)} - * @default - */ - backgroundColor: '', - - /** - * Background image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. - * Backwards incompatibility note: The "backgroundImageOpacity" - * and "backgroundImageStretch" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. - * @type fabric.Image - * @default - */ - backgroundImage: null, - - /** - * Overlay color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayColor} - * @since 1.3.9 - * @type {(String|fabric.Pattern)} - * @default - */ - overlayColor: '', - - /** - * Overlay image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. - * Backwards incompatibility note: The "overlayImageLeft" - * and "overlayImageTop" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#left} and {@link fabric.Image#top}. - * @type fabric.Image - * @default - */ - overlayImage: null, - - /** - * Indicates whether toObject/toDatalessObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Indicates whether objects' state should be saved - * @type Boolean - * @default - */ - stateful: true, - - /** - * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. - * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once - * (followed by a manual rendering after addition/deletion) - * @type Boolean - * @default - */ - renderOnAddRemove: true, - - /** - * Function that determines clipping of entire canvas area - * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} - * @type Function - * @default - */ - clipTo: null, - - /** - * Indicates whether object controls (borders/controls) are rendered above overlay image - * @type Boolean - * @default - */ - controlsAboveOverlay: false, - - /** - * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas - * @type Boolean - * @default - */ - allowTouchScrolling: false, - - /** - * Indicates whether this canvas will use image smoothing, this is on by default in browsers - * @type Boolean - * @default - */ - imageSmoothingEnabled: true, - - /** - * The transformation (in the format of Canvas transform) which focuses the viewport - * @type Array - * @default - */ - viewportTransform: [1, 0, 0, 1, 0, 0], - - /** - * Callback; invoked right before object is about to be scaled/rotated - * @param {fabric.Object} target Object that's about to be scaled/rotated - */ - onBeforeScaleRotate: function () { - /* NOOP */ - }, - - /** - * @private - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - */ - _initStatic: function(el, options) { - this._objects = []; - - this._createLowerCanvas(el); - this._initOptions(options); - this._setImageSmoothing(); - - if (options.overlayImage) { - this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); - } - if (options.backgroundImage) { - this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); - } - if (options.backgroundColor) { - this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); - } - if (options.overlayColor) { - this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); - } - this.calcOffset(); - }, - - /** - * Calculates canvas element offset relative to the document - * This method is also attached as "resize" event handler of window - * @return {fabric.Canvas} instance - * @chainable - */ - calcOffset: function () { - this._offset = getElementOffset(this.lowerCanvasEl); - return this; - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to - * @param {Function} callback callback to invoke when image is loaded and set as an overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} - * @example Normal overlayImage with left/top = 0 - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage with different properties - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched overlayImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched overlayImage #2 - width/height correspond to canvas width/height - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - */ - setOverlayImage: function (image, callback, options) { - return this.__setBgOverlayImage('overlayImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to - * @param {Function} callback Callback to invoke when image is loaded and set as background - * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} - * @example Normal backgroundImage with left/top = 0 - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage with different properties - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - */ - setBackgroundImage: function (image, callback, options) { - return this.__setBgOverlayImage('backgroundImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas - * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} - * @example Normal overlayColor - color value - * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor with repeat and offset - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setOverlayColor: function(overlayColor, callback) { - return this.__setBgOverlayColor('overlayColor', overlayColor, callback); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas - * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} - * @example Normal backgroundColor - color value - * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor with repeat and offset - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setBackgroundColor: function(backgroundColor, callback) { - return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); - }, - - /** - * @private - * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} - */ - _setImageSmoothing: function(){ - var ctx = this.getContext(); - - ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; - ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} - * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background or overlay to - * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. - */ - __setBgOverlayImage: function(property, image, callback, options) { - if (typeof image === 'string') { - fabric.util.loadImage(image, function(img) { - this[property] = new fabric.Image(img, options); - callback && callback(); - }, this); - } - else { - this[property] = image; - callback && callback(); - } - - return this; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} - * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) - * @param {(Object|String)} color Object with pattern information or color value - * @param {Function} [callback] Callback is invoked when color is set - */ - __setBgOverlayColor: function(property, color, callback) { - if (color.source) { - var _this = this; - fabric.util.loadImage(color.source, function(img) { - _this[property] = new fabric.Pattern({ - source: img, - repeat: color.repeat, - offsetX: color.offsetX, - offsetY: color.offsetY - }); - callback && callback(); - }); - } - else { - this[property] = color; - callback && callback(); - } - - return this; - }, - - /** - * @private - */ - _createCanvasElement: function() { - var element = fabric.document.createElement('canvas'); - if (!element.style) { - element.style = { }; - } - if (!element) { - throw CANVAS_INIT_ERROR; - } - this._initCanvasElement(element); - return element; - }, - - /** - * @private - * @param {HTMLElement} element - */ - _initCanvasElement: function(element) { - fabric.util.createCanvasElement(element); - - if (typeof element.getContext === 'undefined') { - throw CANVAS_INIT_ERROR; - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initOptions: function (options) { - for (var prop in options) { - this[prop] = options[prop]; - } - - this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; - - if (!this.lowerCanvasEl.style) return; - - this.lowerCanvasEl.width = this.width; - this.lowerCanvasEl.height = this.height; - - this.lowerCanvasEl.style.width = this.width + 'px'; - this.lowerCanvasEl.style.height = this.height + 'px'; - }, - - /** - * Creates a bottom canvas - * @private - * @param {HTMLElement} [canvasEl] - */ - _createLowerCanvas: function (canvasEl) { - this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); - this._initCanvasElement(this.lowerCanvasEl); - - fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); - - if (this.interactive) { - this._applyCanvasStyle(this.lowerCanvasEl); - } - - this.contextContainer = this.lowerCanvasEl.getContext('2d'); - }, - - /** - * Returns canvas width (in px) - * @return {Number} - */ - getWidth: function () { - return this.width; - }, - - /** - * Returns canvas height (in px) - * @return {Number} - */ - getHeight: function () { - return this.height; - }, - - /** - * Sets width of this canvas instance - * @param {Number} width value to set width to - * @return {fabric.Canvas} instance - * @chainable true - */ - setWidth: function (value) { - return this._setDimension('width', value); - }, - - /** - * Sets height of this canvas instance - * @param {Number} height value to set height to - * @return {fabric.Canvas} instance - * @chainable true - */ - setHeight: function (value) { - return this._setDimension('height', value); - }, - - /** - * Sets dimensions (width, height) of this canvas instance - * @param {Object} dimensions Object with width/height properties - * @param {Number} [dimensions.width] Width of canvas element - * @param {Number} [dimensions.height] Height of canvas element - * @return {fabric.Canvas} thisArg - * @chainable - */ - setDimensions: function(dimensions) { - for (var prop in dimensions) { - this._setDimension(prop, dimensions[prop]); - } - return this; - }, - - /** - * Helper for setting width/height - * @private - * @param {String} prop property (width|height) - * @param {Number} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setDimension: function (prop, value) { - this.lowerCanvasEl[prop] = value; - this.lowerCanvasEl.style[prop] = value + 'px'; - - if (this.upperCanvasEl) { - this.upperCanvasEl[prop] = value; - this.upperCanvasEl.style[prop] = value + 'px'; - } - - if (this.cacheCanvasEl) { - this.cacheCanvasEl[prop] = value; - } - - if (this.wrapperEl) { - this.wrapperEl.style[prop] = value + 'px'; - } - - this[prop] = value; - - this.calcOffset(); - this.renderAll(); - - return this; - }, - - /** - * Returns canvas zoom level - * @return {Number} - */ - getZoom: function () { - return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); - }, - - /** - * Sets viewport transform of this canvas instance - * @param {Array} vpt the transform in the form of context.transform - * @return {fabric.Canvas} instance - * @chainable true - */ - setViewportTransform: function (vpt) { - this.viewportTransform = vpt; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance, zoom centered around point - * @param {fabric.Point} point to zoom with respect to - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - zoomToPoint: function (point, value) { - // TODO: just change the scale, preserve other transformations - var before = fabric.util.transformPoint(point, this.viewportTransform); - this.viewportTransform[0] = value; - this.viewportTransform[3] = value; - var after = fabric.util.transformPoint(point, this.viewportTransform); - this.viewportTransform[4] += before.x - after.x; - this.viewportTransform[5] += before.y - after.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this; - }, - - /** - * Sets zoom level of this canvas instance - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - setZoom: function (value) { - this.zoomToPoint(new fabric.Point(0, 0), value); - return this; - }, - - /** - * Pan viewport so as to place point at top left corner of canvas - * @param {fabric.Point} point to move to - * @return {fabric.Canvas} instance - * @chainable true - */ - absolutePan: function (point) { - var wh = fabric.util.transformPoint( - new fabric.Point(this.getWidth(), this.getHeight()), - this.viewportTransform - ); - this.viewportTransform[4] = -point.x; - this.viewportTransform[5] = -point.y; - this.renderAll(); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i].setCoords(); - } - return this - }, - - /** - * Pans viewpoint relatively - * @param {fabric.Point} point (position vector) to move by - * @return {fabric.Canvas} instance - * @chainable true - */ - relativePan: function (point) { - return this.absolutePan(new fabric.Point( - -point.x - this.viewportTransform[4], - -point.y - this.viewportTransform[5] - )); - }, - - /** - * Returns <canvas> element corresponding to this instance - * @return {HTMLCanvasElement} - */ - getElement: function () { - return this.lowerCanvasEl; - }, - - /** - * Returns currently selected object, if any - * @return {fabric.Object} - */ - getActiveObject: function() { - return null; - }, - - /** - * Returns currently selected group of object, if any - * @return {fabric.Group} - */ - getActiveGroup: function() { - return null; - }, - - /** - * Given a context, renders an object on that context - * @param {CanvasRenderingContext2D} ctx Context to render object on - * @param {fabric.Object} object Object to render - * @private - */ - _draw: function (ctx, object) { - if (!object) return; - - ctx.save(); - var v = this.viewportTransform; - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - object.render(ctx); - ctx.restore(); - if (!this.controlsAboveOverlay) object._renderControls(ctx); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was added - */ - _onObjectAdded: function(obj) { - this.stateful && obj.setupState(); - obj.canvas = this; - if (obj._objects) { - obj._calcBounds(); - for (var i = 0, len = obj._objects.length; i < len; i++) { - obj._objects[i].canvas = this; - this._onObjectAdded(obj._objects[i]); - } - obj._updateObjectsCoords(); - } - obj.setCoords(); - this.fire('object:added', { target: obj }); - obj.fire('added'); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was removed - */ - _onObjectRemoved: function(obj) { - // removing active object should fire "selection:cleared" events - if (this.getActiveObject() === obj) { - this.fire('before:selection:cleared', { target: obj }); - this._discardActiveObject(); - this.fire('selection:cleared'); - } - - this.fire('object:removed', { target: obj }); - obj.fire('removed'); - }, - - /** - * Clears specified context of canvas element - * @param {CanvasRenderingContext2D} ctx Context to clear - * @return {fabric.Canvas} thisArg - * @chainable - */ - clearContext: function(ctx) { - ctx.clearRect(0, 0, this.width, this.height); - return this; - }, - - /** - * Returns context of canvas where objects are drawn - * @return {CanvasRenderingContext2D} - */ - getContext: function () { - return this.contextContainer; - }, - - /** - * Clears all contexts (background, main, top) of an instance - * @return {fabric.Canvas} thisArg - * @chainable - */ - clear: function () { - this._objects.length = 0; - if (this.discardActiveGroup) { - this.discardActiveGroup(); - } - if (this.discardActiveObject) { - this.discardActiveObject(); - } - this.clearContext(this.contextContainer); - if (this.contextTop) { - this.clearContext(this.contextTop); - } - this.fire('canvas:cleared'); - this.renderAll(); - return this; - }, - - /** - * Renders both the top canvas and the secondary container canvas. - * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas - * @return {fabric.Canvas} instance - * @chainable - */ - renderAll: function (allOnTop) { - var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], - activeGroup = this.getActiveGroup(); - - if (this.contextTop && this.selection && !this._groupSelector) { - this.clearContext(this.contextTop); - } - - if (!allOnTop) { - this.clearContext(canvasToDrawOn); - } - - this.fire('before:render'); - - if (this.clipTo) { - fabric.util.clipContext(this, canvasToDrawOn); - } - - this._renderBackground(canvasToDrawOn); - this._renderObjects(canvasToDrawOn, activeGroup); - this._renderActiveGroup(canvasToDrawOn, activeGroup); - - if (this.clipTo) { - canvasToDrawOn.restore(); - } - - this._renderOverlay(canvasToDrawOn); - - if (this.controlsAboveOverlay && this.interactive) { - this.drawControls(canvasToDrawOn); - } - - this.fire('after:render'); - - return this; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderObjects: function(ctx, activeGroup) { - var i, length; - - // fast path - if (!activeGroup) { - for (i = 0, length = this._objects.length; i < length; ++i) { - this._draw(ctx, this._objects[i]); - } - } - else { - for (i = 0, length = this._objects.length; i < length; ++i) { - if (this._objects[i] && !activeGroup.contains(this._objects[i])) { - this._draw(ctx, this._objects[i]); - } - } - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {fabric.Group} activeGroup - */ - _renderActiveGroup: function(ctx, activeGroup) { - - // delegate rendering to group selection (if one exists) - if (activeGroup) { - - //Store objects in group preserving order, then replace - var sortedObjects = []; - this.forEachObject(function (object) { - if (activeGroup.contains(object)) { - sortedObjects.push(object); - } - }); - activeGroup._set('objects', sortedObjects); - this._draw(ctx, activeGroup); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderBackground: function(ctx) { - if (this.backgroundColor) { - ctx.fillStyle = this.backgroundColor.toLive - ? this.backgroundColor.toLive(ctx) - : this.backgroundColor; - - ctx.fillRect( - this.backgroundColor.offsetX || 0, - this.backgroundColor.offsetY || 0, - this.width, - this.height); - } - if (this.backgroundImage) { - this.backgroundImage.render(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderOverlay: function(ctx) { - if (this.overlayColor) { - ctx.fillStyle = this.overlayColor.toLive - ? this.overlayColor.toLive(ctx) - : this.overlayColor; - - ctx.fillRect( - this.overlayColor.offsetX || 0, - this.overlayColor.offsetY || 0, - this.width, - this.height); - } - if (this.overlayImage) { - this.overlayImage.render(ctx); - } - }, - - /** - * Method to render only the top canvas. - * Also used to render the group selection box. - * @return {fabric.Canvas} thisArg - * @chainable - */ - renderTop: function () { - var ctx = this.contextTop || this.contextContainer; - this.clearContext(ctx); - - // we render the top context - last object - if (this.selection && this._groupSelector) { - this._drawSelection(); - } - - // delegate rendering to group selection if one exists - // used for drawing selection borders/controls - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.render(ctx); - } - - this._renderOverlay(ctx); - - this.fire('after:render'); - - return this; - }, - - /** - * Returns coordinates of a center of canvas. - * Returned value is an object with top and left properties - * @return {Object} object with "top" and "left" number values - */ - getCenter: function () { - return { - top: this.getHeight() / 2, - left: this.getWidth() / 2 - }; - }, - - /** - * Centers object horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center horizontally - * @return {fabric.Canvas} thisArg - */ - centerObjectH: function (object) { - this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObjectV: function (object) { - this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); - this.renderAll(); - return this; - }, - - /** - * Centers object vertically and horizontally. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObject: function(object) { - var center = this.getCenter(); - - this._centerObject(object, new fabric.Point(center.left, center.top)); - this.renderAll(); - return this; - }, - - /** - * @private - * @param {fabric.Object} object Object to center - * @param {fabric.Point} center Center point - * @return {fabric.Canvas} thisArg - * @chainable - */ - _centerObject: function(object, center) { - object.setPositionByOrigin(center, 'center', 'center'); - return this; - }, - - /** - * Returs dataless JSON representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} json string - */ - toDatalessJSON: function (propertiesToInclude) { - return this.toDatalessObject(propertiesToInclude); - }, - - /** - * Returns object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function (propertiesToInclude) { - return this._toObjectMethod('toObject', propertiesToInclude); - }, - - /** - * Returns dataless object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function (propertiesToInclude) { - return this._toObjectMethod('toDatalessObject', propertiesToInclude); - }, - - /** - * @private - */ - _toObjectMethod: function (methodName, propertiesToInclude) { - - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this.discardActiveGroup(); - } - - var data = { - objects: this._toObjects(methodName, propertiesToInclude) - }; - - extend(data, this.__serializeBgOverlay()); - - fabric.util.populateWithProperties(this, data, propertiesToInclude); - - if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { - originX: 'center', - originY: 'center' - })); - activeGroup.forEachObject(function(o) { - o.set('active', true); - }); - - if (this._currentTransform) { - this._currentTransform.target = this.getActiveGroup(); - } - } - - - return data; - }, - - /** - * @private - */ - _toObjects: function(methodName, propertiesToInclude) { - return this.getObjects().map(function(instance) { - return this._toObject(instance, methodName, propertiesToInclude); - }, this); - }, - - /** - * @private - */ - _toObject: function(instance, methodName, propertiesToInclude) { - var originalValue; - - if (!this.includeDefaultValues) { - originalValue = instance.includeDefaultValues; - instance.includeDefaultValues = false; - } - var object = instance[methodName](propertiesToInclude); - if (!this.includeDefaultValues) { - instance.includeDefaultValues = originalValue; - } - return object; - }, - - /** - * @private - */ - __serializeBgOverlay: function() { - var data = { - background: (this.backgroundColor && this.backgroundColor.toObject) - ? this.backgroundColor.toObject() - : this.backgroundColor - }; - - if (this.overlayColor) { - data.overlay = this.overlayColor.toObject - ? this.overlayColor.toObject() - : this.overlayColor; - } - if (this.backgroundImage) { - data.backgroundImage = this.backgroundImage.toObject(); - } - if (this.overlayImage) { - data.overlayImage = this.overlayImage.toObject(); - } - - return data; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of canvas - * @function - * @param {Object} [options] Options object for SVG output - * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included - * @param {Object} [options.viewBox] SVG viewbox object - * @param {Number} [options.viewBox.x] x-cooridnate of viewbox - * @param {Number} [options.viewBox.y] y-coordinate of viewbox - * @param {Number} [options.viewBox.width] Width of viewbox - * @param {Number} [options.viewBox.height] Height of viewbox - * @param {String} [options.encoding=UTF-8] Encoding of SVG output - * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. - * @return {String} SVG string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} - * @example Normal SVG output - * var svg = canvas.toSVG(); - * @example SVG output without preamble (without <?xml ../>) - * var svg = canvas.toSVG({suppressPreamble: true}); - * @example SVG output with viewBox attribute - * var svg = canvas.toSVG({ - * viewBox: { - * x: 100, - * y: 100, - * width: 200, - * height: 300 - * } - * }); - * @example SVG output with different encoding (default: UTF-8) - * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); - * @example Modify SVG output with reviver function - * var svg = canvas.toSVG(null, function(svg) { - * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); - * }); - */ - toSVG: function(options, reviver) { - options || (options = { }); - - var markup = []; - - this._setSVGPreamble(markup, options); - this._setSVGHeader(markup, options); - - this._setSVGBgOverlayColor(markup, 'backgroundColor'); - this._setSVGBgOverlayImage(markup, 'backgroundImage'); - - this._setSVGObjects(markup, reviver); - - this._setSVGBgOverlayColor(markup, 'overlayColor'); - this._setSVGBgOverlayImage(markup, 'overlayImage'); - - markup.push(''); - - return markup.join(''); - }, - - /** - * @private - */ - _setSVGPreamble: function(markup, options) { - if (!options.suppressPreamble) { - markup.push( - '', - '\n' - ); - } - }, - - /** - * @private - */ - _setSVGHeader: function(markup, options) { - markup.push( - '', - 'Created with Fabric.js ', fabric.version, '', - '', - fabric.createSVGFontFacesMarkup(this.getObjects()), - fabric.createSVGRefElementsMarkup(this), - '' - ); - }, - - /** - * @private - */ - _setSVGObjects: function(markup, reviver) { - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this.discardActiveGroup(); - } - for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { - markup.push(objects[i].toSVG(reviver)); - } - if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); - activeGroup.forEachObject(function(o) { - o.set('active', true); - }); - } - }, - - /** - * @private - */ - _setSVGBgOverlayImage: function(markup, property) { - if (this[property] && this[property].toSVG) { - markup.push(this[property].toSVG()); - } - }, - - /** - * @private - */ - _setSVGBgOverlayColor: function(markup, property) { - if (this[property] && this[property].source) { - markup.push( - '' - ); - } - else if (this[property] && property === 'overlayColor') { - markup.push( - '' - ); - } - }, - /* _TO_SVG_END_ */ - - /** - * Moves an object to the bottom of the stack of drawn objects - * @param {fabric.Object} object Object to send to back - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendToBack: function (object) { - removeFromArray(this._objects, object); - this._objects.unshift(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @param {fabric.Object} object Object to send - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringToFront: function (object) { - removeFromArray(this._objects, object); - this._objects.push(object); - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object down in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendBackwards: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on the bottom of stack - if (idx !== 0) { - var newIdx = this._findNewLowerIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewLowerIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse down the stack looking for the nearest intersecting object - for (var i = idx - 1; i >= 0; --i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx - 1; - } - - return newIdx; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringForward: function (object, intersecting) { - var idx = this._objects.indexOf(object); - - // if object is not on top of stack (last item in an array) - if (idx !== this._objects.length - 1) { - var newIdx = this._findNewUpperIndex(object, idx, intersecting); - - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - this.renderAll && this.renderAll(); - } - return this; - }, - - /** - * @private - */ - _findNewUpperIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse up the stack looking for the nearest intersecting object - for (var i = idx + 1; i < this._objects.length; ++i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx + 1; - } - - return newIdx; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Number} index Position to move to - * @return {fabric.Canvas} thisArg - * @chainable - */ - moveTo: function (object, index) { - removeFromArray(this._objects, object); - this._objects.splice(index, 0, object); - return this.renderAll && this.renderAll(); - }, - - /** - * Clears a canvas element and removes all event listeners - * @return {fabric.Canvas} thisArg - * @chainable - */ - dispose: function () { - this.clear(); - this.interactive && this.removeListeners(); - return this; - }, - - /** - * Returns a string representation of an instance - * @return {String} string representation of an instance - */ - toString: function () { - return '#'; + function getLinearCoords(el) { + return { + x1: el.getAttribute("x1") || 0, + y1: el.getAttribute("y1") || 0, + x2: el.getAttribute("x2") || "100%", + y2: el.getAttribute("y2") || 0 + }; } - }); - - extend(fabric.StaticCanvas.prototype, fabric.Observable); - extend(fabric.StaticCanvas.prototype, fabric.Collection); - extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); - - extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { - - /** - * @static - * @type String - * @default - */ - EMPTY_JSON: '{"objects": [], "background": "white"}', - - /** - * Provides a way to check support of some of the canvas methods - * (either those of HTMLCanvasElement itself, or rendering context) - * - * @param methodName {String} Method to check support for; - * 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 - */ - supports: function (methodName) { - var el = fabric.util.createCanvasElement(); - - if (!el || !el.getContext) { - return null; - } - - var ctx = el.getContext('2d'); - if (!ctx) { - return null; - } - - switch (methodName) { - - case 'getImageData': - return typeof ctx.getImageData !== 'undefined'; - - case 'setLineDash': - return typeof ctx.setLineDash !== 'undefined'; - - case 'toDataURL': - return typeof el.toDataURL !== 'undefined'; - - case 'toDataURLWithQuality': - try { - el.toDataURL('image/jpeg', 0); - return true; - } - catch (e) { } - return false; - - default: - return null; - } + 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%" + }; } - }); - - /** - * Returns JSON representation of canvas - * @function - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} JSON string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} - * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} - * @example JSON without additional properties - * var json = canvas.toJSON(); - * @example JSON with additional properties included - * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); - * @example JSON without default values - * canvas.includeDefaultValues = false; - * var json = canvas.toJSON(); - */ - fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; - -})(); - - -/** - * BaseBrush class - * @class fabric.BaseBrush - * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} - */ -fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { - - /** - * Color of a brush - * @type String - * @default - */ - color: 'rgb(0, 0, 0)', - - /** - * Width of a brush - * @type Number - * @default - */ - width: 1, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), - * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Line endings style of a brush (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'round', - - /** - * Corner style of a brush (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'round', - - /** - * Sets shadow of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Object} thisArg - * @chainable - */ - setShadow: function(options) { - this.shadow = new fabric.Shadow(options); - return this; - }, - - /** - * Sets brush styles - * @private - */ - _setBrushStyles: function() { - var ctx = this.canvas.contextTop; - - ctx.strokeStyle = this.color; - ctx.lineWidth = this.width; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - }, - - /** - * Sets brush shadow styles - * @private - */ - _setShadow: function() { - if (!this.shadow) return; - - var ctx = this.canvas.contextTop; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * Removes brush shadow styles - * @private - */ - _resetShadow: function() { - var ctx = this.canvas.contextTop; - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - } -}); - - -(function() { - - var utilMin = fabric.util.array.min, - utilMax = fabric.util.array.max; - - /** - * PencilBrush class - * @class fabric.PencilBrush - * @extends fabric.BaseBrush - */ - fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.PencilBrush} Instance of a pencil brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this._points = [ ]; - }, - - /** - * Inovoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this._prepareForDrawing(pointer); - // capture coordinates immediately - // this allows to draw dots (when movement never occurs) - this._captureDrawingPath(pointer); - this._render(); - }, - - /** - * Inovoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this._captureDrawingPath(pointer); - // redraw curve - // clear top canvas - this.canvas.clearContext(this.canvas.contextTop); - this._render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - this._finalizeAndAddPath(); - }, - - /** - * @param {Object} pointer - */ - _prepareForDrawing: function(pointer) { - - var p = new fabric.Point(pointer.x, pointer.y); - - this._reset(); - this._addPoint(p); - - this.canvas.contextTop.moveTo(p.x, p.y); - }, - - /** - * @private - * @param {fabric.Point} point - */ - _addPoint: function(point) { - this._points.push(point); - }, - - /** - * Clear points array and set contextTop canvas - * style. - * - * @private - * - */ - _reset: function() { - this._points.length = 0; - - this._setBrushStyles(); - this._setShadow(); - }, - - /** - * @private - * - * @param point {pointer} (fabric.util.pointer) actual mouse position - * related to the canvas. - */ - _captureDrawingPath: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); - this._addPoint(pointerPoint); - }, - - /** - * Draw a smooth path on the topCanvas using quadraticCurveTo - * - * @private - */ - _render: function() { - var ctx = this.canvas.contextTop; - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - ctx.beginPath(); - - var p1 = this._points[0], - p2 = this._points[1]; - - //if we only have 2 points in the path and they are the same - //it means that the user only clicked the canvas without moving the mouse - //then we should be drawing a dot. A path isn't drawn between two identical dots - //that's why we set them apart a bit - if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; - } - ctx.moveTo(p1.x, p1.y); - - for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi + 1 & pi + 2 as the - // end point and p1 as our control point. - var midPoint = p1.midPointFrom(p2); - ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); - - p1 = this._points[i]; - p2 = this._points[i + 1]; - } - // Draw last line as a straight line while - // we wait for the next point to be able to calculate - // the bezier control point - ctx.lineTo(p1.x, p1.y); - ctx.stroke(); - ctx.restore(); - }, - - /** - * Return an SVG path based on our captured points and their bounding box - * - * @private - */ - _getSVGPathData: function() { - this.box = this.getPathBoundingBox(this._points); - return this.convertPointsToSVGPath( - this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); - }, - - /** - * Returns bounding box of a path based on given points - * @param {Array} points - * @return {Object} object with minx, miny, maxx, maxy - */ - getPathBoundingBox: function(points) { - var xBounds = [], - yBounds = [], - p1 = points[0], - p2 = points[1], - startPoint = p1; - - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // with startPoint, p1 as control point, midpoint as end point - xBounds.push(startPoint.x); - xBounds.push(midPoint.x); - yBounds.push(startPoint.y); - yBounds.push(midPoint.y); - - p1 = points[i]; - p2 = points[i + 1]; - startPoint = midPoint; - } - - xBounds.push(p1.x); - yBounds.push(p1.y); - - return { - minx: utilMin(xBounds), - miny: utilMin(yBounds), - maxx: utilMax(xBounds), - maxy: utilMax(yBounds) - }; - }, - - /** - * Converts points to SVG path - * @param {Array} points Array of points - * @return {String} SVG path - */ - convertPointsToSVGPath: function(points, minX, maxX, minY) { - var path = [], - p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), - p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); - - path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); - p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); - if ((i + 1) < points.length) { - p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); - } - } - path.push('L ', p1.x, ' ', p1.y, ' '); - return path; - }, - - /** - * Creates fabric.Path object to add on canvas - * @param {String} pathData Path data - * @return {fabric.Path} path to add on canvas - */ - createPath: function(pathData) { - var path = new fabric.Path(pathData); - path.fill = null; - path.stroke = this.color; - path.strokeWidth = this.width; - path.strokeLineCap = this.strokeLineCap; - path.strokeLineJoin = this.strokeLineJoin; - - if (this.shadow) { - this.shadow.affectStroke = true; - path.setShadow(this.shadow); - } - - return path; - }, - - /** - * On mouseup after drawing the path on contextTop canvas - * we use the points captured to create an new fabric path object - * and add it to the fabric canvas. - * - */ - _finalizeAndAddPath: function() { - var ctx = this.canvas.contextTop; - ctx.closePath(); - - var pathData = this._getSVGPathData().join(''); - if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { - // do not create 0 width/height paths, as they are - // rendered inconsistently across browsers - // Firefox 4, for example, renders a dot, - // whereas Chrome 10 renders nothing - this.canvas.renderAll(); - return; - } - - // set path origin coordinates based on our bounding box - var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2, - originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2; - - this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); - - var path = this.createPath(pathData); - path.set({ - left: originLeft, - top: originTop, - originX: 'center', - originY: 'center' - }); - - this.canvas.add(path); - path.setCoords(); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderAll(); - - // fire event 'path' created - this.canvas.fire('path:created', { path: path }); - } - }); -})(); - - -/** - * CircleBrush class - * @class fabric.CircleBrush - */ -fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { - - /** - * Width of a brush - * @type Number - * @default - */ - width: 10, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.CircleBrush} Instance of a circle brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.points = [ ]; - }, - /** - * Invoked inside on mouse down and mouse move - * @param {Object} pointer - */ - drawDot: function(pointer) { - var point = this.addPoint(pointer), - ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - ctx.fillStyle = point.fill; - ctx.beginPath(); - ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - }, - - /** - * Invoked on mouse down - */ - onMouseDown: function(pointer) { - this.points.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - this.drawDot(pointer); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.drawDot(pointer); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var circles = [ ]; - - for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i], - circle = new fabric.Circle({ - radius: point.radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: point.fill - }); - - this.shadow && circle.setShadow(this.shadow); - - circles.push(circle); - } - var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @param {Object} pointer - * @return {fabric.Point} Just added pointer point - */ - addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y), - - circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2, - - circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); - - pointerPoint.radius = circleRadius; - pointerPoint.fill = circleColor; - - this.points.push(pointerPoint); - - return pointerPoint; - } -}); - - -/** - * SprayBrush class - * @class fabric.SprayBrush - */ -fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { - - /** - * Width of a spray - * @type Number - * @default - */ - width: 10, - - /** - * Density of a spray (number of dots per chunk) - * @type Number - * @default - */ - density: 20, - - /** - * Width of spray dots - * @type Number - * @default - */ - dotWidth: 1, - - /** - * Width variance of spray dots - * @type Number - * @default - */ - dotWidthVariance: 1, - - /** - * Whether opacity of a dot should be random - * @type Boolean - * @default - */ - randomOpacity: false, - - /** - * Whether overlapping dots (rectangles) should be removed (for performance reasons) - * @type Boolean - * @default - */ - optimizeOverlapping: true, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.SprayBrush} Instance of a spray brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.sprayChunks = [ ]; - }, - - /** - * Invoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this.sprayChunks.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var rects = [ ]; - - for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - var sprayChunk = this.sprayChunks[i]; - - for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { - - var rect = new fabric.Rect({ - width: sprayChunk[j].width, - height: sprayChunk[j].width, - left: sprayChunk[j].x + 1, - top: sprayChunk[j].y + 1, - originX: 'center', - originY: 'center', - fill: this.color - }); - - this.shadow && rect.setShadow(this.shadow); - rects.push(rect); - } - } - - if (this.optimizeOverlapping) { - rects = this._getOptimizedRects(rects); - } - - var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - _getOptimizedRects: function(rects) { - - // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key; - - for (var i = 0, len = rects.length; i < len; i++) { - key = rects[i].left + '' + rects[i].top; - if (!uniqueRects[key]) { - uniqueRects[key] = rects[i]; - } - } - var uniqueRectsArray = [ ]; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - - /** - * Renders brush - */ - render: function() { - var ctx = this.canvas.contextTop; - ctx.fillStyle = this.color; - - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { - var point = this.sprayChunkPoints[i]; - if (typeof point.opacity !== 'undefined') { - ctx.globalAlpha = point.opacity; - } - ctx.fillRect(point.x, point.y, point.width, point.width); - } - ctx.restore(); - }, - - /** - * @param {Object} pointer - */ - addSprayChunk: function(pointer) { - this.sprayChunkPoints = [ ]; - - var x, y, width, radius = this.width / 2; - - for (var i = 0; i < this.density; i++) { - - x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); - y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); - - if (this.dotWidthVariance) { - width = fabric.util.getRandomInt( - // bottom clamp width to 1 - Math.max(1, this.dotWidth - this.dotWidthVariance), - this.dotWidth + this.dotWidthVariance); - } - else { - width = this.dotWidth; - } - - var point = new fabric.Point(x, y); - point.width = width; - - if (this.randomOpacity) { - point.opacity = fabric.util.getRandomInt(0, 100) / 100; - } - - this.sprayChunkPoints.push(point); - } - - this.sprayChunks.push(this.sprayChunkPoints); - } -}); - - -/** - * PatternBrush class - * @class fabric.PatternBrush - * @extends fabric.BaseBrush - */ -fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { - - getPatternSrc: function() { - - var dotWidth = 20, - dotDistance = 5, - patternCanvas = fabric.document.createElement('canvas'), - patternCtx = patternCanvas.getContext('2d'); - - patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; - - patternCtx.fillStyle = this.color; - patternCtx.beginPath(); - patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); - patternCtx.closePath(); - patternCtx.fill(); - - return patternCanvas; - }, - - getPatternSrcFunction: function() { - return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); - }, - - /** - * Creates "pattern" instance property - */ - getPattern: function() { - return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); - }, - - /** - * Sets brush styles - */ - _setBrushStyles: function() { - this.callSuper('_setBrushStyles'); - this.canvas.contextTop.strokeStyle = this.getPattern(); - }, - - /** - * Creates path - */ - createPath: function(pathData) { - var path = this.callSuper('createPath', pathData); - path.stroke = new fabric.Pattern({ - source: this.source || this.getPatternSrcFunction() - }); - return path; - } -}); - - -(function() { - - var getPointer = fabric.util.getPointer, - degreesToRadians = fabric.util.degreesToRadians, - radiansToDegrees = fabric.util.radiansToDegrees, - atan2 = Math.atan2, - abs = Math.abs, - - STROKE_OFFSET = 0.5; - - /** - * Canvas class - * @class fabric.Canvas - * @extends fabric.StaticCanvas - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas} - * @see {@link fabric.Canvas#initialize} for constructor definition - * - * @fires object:modified - * @fires object:rotating - * @fires object:scaling - * @fires object:moving - * @fires object:selected - * - * @fires before:selection:cleared - * @fires selection:cleared - * @fires selection:created - * - * @fires path:created - * @fires mouse:down - * @fires mouse:move - * @fires mouse:up - * @fires mouse:over - * @fires mouse:out - * - */ - fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - this._initInteractive(); - this._createCacheCanvas(); - - fabric.Canvas.activeInstance = this; - }, - - /** - * When true, objects can be transformed by one side (unproportionally) - * @type Boolean - * @default - */ - uniScaleTransform: false, - - /** - * When true, objects use center point as the origin of scale transformation. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, objects use center point as the origin of rotate transformation. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: false, - - /** - * Indicates that canvas is interactive. This property should not be changed. - * @type Boolean - * @default - */ - interactive: true, - - /** - * Indicates whether group selection should be enabled - * @type Boolean - * @default - */ - selection: true, - - /** - * Color of selection - * @type String - * @default - */ - selectionColor: 'rgba(100, 100, 255, 0.3)', // blue - - /** - * Default dash array pattern - * If not empty the selection border is dashed - * @type Array - */ - selectionDashArray: [ ], - - /** - * Color of the border of selection (usually slightly darker than color of selection itself) - * @type String - * @default - */ - selectionBorderColor: 'rgba(255, 255, 255, 0.3)', - - /** - * Width of a line used in object/group selection - * @type Number - * @default - */ - selectionLineWidth: 1, - - /** - * Default cursor value used when hovering over an object on canvas - * @type String - * @default - */ - hoverCursor: 'move', - - /** - * Default cursor value used when moving an object on canvas - * @type String - * @default - */ - moveCursor: 'move', - - /** - * Default cursor value used for the entire canvas - * @type String - * @default - */ - defaultCursor: 'default', - - /** - * Cursor value used during free drawing - * @type String - * @default - */ - freeDrawingCursor: 'crosshair', - - /** - * Cursor value used for rotation point - * @type String - * @default - */ - rotationCursor: 'crosshair', - - /** - * Default element class that's given to wrapper (div) element of canvas - * @type String - * @default - */ - containerClass: 'canvas-container', - - /** - * When true, object detection happens on per-pixel basis rather than on per-bounding-box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * Number of pixels around target pixel to tolerate (consider active) during object detection - * @type Number - * @default - */ - targetFindTolerance: 0, - - /** - * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. - * @type Boolean - * @default - */ - skipTargetFind: false, - - /** - * @private - */ - _initInteractive: function() { - this._currentTransform = null; - this._groupSelector = null; - this._initWrapperElement(); - this._createUpperCanvas(); - this._initEventListeners(); - - this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); - - this.calcOffset(); - }, - - /** - * Resets the current transform to its original values and chooses the type of resizing based on the event - * @private - * @param {Event} e Event object fired on mousemove - */ - _resetCurrentTransform: function(e) { - var t = this._currentTransform; - - t.target.set({ - scaleX: t.original.scaleX, - scaleY: t.original.scaleY, - left: t.original.left, - top: t.original.top - }); - - if (this._shouldCenterTransform(e, t.target)) { - if (t.action === 'rotate') { - this._setOriginToCenter(t.target); - } - else { - if (t.originX !== 'center') { - if (t.originX === 'right') { - t.mouseXSign = -1; + fabric.Gradient = fabric.util.createClass({ + 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; } - else { - t.mouseXSign = 1; + this.coords = coords; + this.gradientUnits = options.gradientUnits || "objectBoundingBox"; + this.colorStops = options.colorStops.slice(); + }, + 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() + }); } - } - if (t.originY !== 'center') { - if (t.originY === 'bottom') { - t.mouseYSign = -1; + return this; + }, + toObject: function() { + return { + type: this.type, + coords: this.coords, + gradientUnits: this.gradientUnits, + colorStops: this.colorStops + }; + }, + toSVG: function(object, normalize) { + var coords = fabric.util.object.clone(this.coords), markup; + 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); } - else { - t.mouseYSign = 1; + if (this.type === "linear") { + markup = [ "' ]; + } else if (this.type === "radial") { + markup = [ "' ]; } - } - - t.originX = 'center'; - t.originY = 'center'; + for (var i = 0; i < this.colorStops.length; i++) { + markup.push("'); + } + markup.push(this.type === "linear" ? "" : ""); + return markup.join(""); + }, + 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; } - } - else { - t.originX = t.original.originX; - t.originY = t.original.originY; - } - }, - - /** - * Checks if point is contained within an area of given object - * @param {Event} e Event object - * @param {fabric.Object} target Object to test against - * @return {Boolean} true if point is contained within an area of given object - */ - containsPoint: function (e, target) { - var pointer = this.getPointer(e, true), - xy = this._normalizePointer(target, pointer); - - // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html - // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html - return (target.containsPoint(xy) || target._findTargetCorner(pointer)); - }, - - /** - * @private - */ - _normalizePointer: function (object, pointer) { - var activeGroup = this.getActiveGroup(), - x = pointer.x, - y = pointer.y, - isObjectInGroup = ( - activeGroup && - object.type !== 'group' && - activeGroup.contains(object)), - lt; - - if (isObjectInGroup) { - lt = new fabric.Point(activeGroup.left, activeGroup.top); - lt = fabric.util.transformPoint(lt, this.viewportTransform, true); - x -= lt.x; - y -= lt.y; - } - return { x: x, y: y }; - }, - - /** - * Returns true if object is transparent at a certain location - * @param {fabric.Object} target Object to check - * @param {Number} x Left coordinate - * @param {Number} y Top coordinate - * @return {Boolean} - */ - isTargetTransparent: function (target, x, y) { - var hasBorders = target.hasBorders, - transparentCorners = target.transparentCorners; - - target.hasBorders = target.transparentCorners = false; - - this._draw(this.contextCache, target); - - target.hasBorders = hasBorders; - target.transparentCorners = transparentCorners; - - var isTransparent = fabric.util.isTransparent( - this.contextCache, x, y, this.targetFindTolerance); - - this.clearContext(this.contextCache); - - return isTransparent; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _shouldClearSelection: function (e, target) { - var activeGroup = this.getActiveGroup(), - activeObject = this.getActiveObject(); - - return ( - !target - || - (target && - activeGroup && - !activeGroup.contains(target) && - activeGroup !== target && - !e.shiftKey) - || - (target && !target.evented) - || - (target && - !target.selectable && - activeObject && - activeObject !== target) - ); - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _shouldCenterTransform: function (e, target) { - if (!target) return; - - var t = this._currentTransform, - centerTransform; - - if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { - centerTransform = this.centeredScaling || target.centeredScaling; - } - else if (t.action === 'rotate') { - centerTransform = this.centeredRotation || target.centeredRotation; - } - - return centerTransform ? !e.altKey : e.altKey; - }, - - /** - * @private - */ - _getOriginFromCorner: function(target, corner) { - var origin = { - x: target.originX, - y: target.originY - }; - - if (corner === 'ml' || corner === 'tl' || corner === 'bl') { - origin.x = 'right'; - } - else if (corner === 'mr' || corner === 'tr' || corner === 'br') { - origin.x = 'left'; - } - - if (corner === 'tl' || corner === 'mt' || corner === 'tr') { - origin.y = 'bottom'; - } - else if (corner === 'bl' || corner === 'mb' || corner === 'br') { - origin.y = 'top'; - } - - return origin; - }, - - /** - * @private - */ - _getActionFromCorner: function(target, corner) { - var action = 'drag'; - if (corner) { - action = (corner === 'ml' || corner === 'mr') - ? 'scaleX' - : (corner === 'mt' || corner === 'mb') - ? 'scaleY' - : corner === 'mtr' - ? 'rotate' - : 'scale'; - } - return action; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _setupCurrentTransform: function (e, target) { - if (!target) return; - - var pointer = this.getPointer(e), - corner = target._findTargetCorner(this.getPointer(e, true)), - action = this._getActionFromCorner(target, corner), - origin = this._getOriginFromCorner(target, corner); - - this._currentTransform = { - target: target, - action: action, - scaleX: target.scaleX, - scaleY: target.scaleY, - offsetX: pointer.x - target.left, - offsetY: pointer.y - target.top, - originX: origin.x, - originY: origin.y, - ex: pointer.x, - ey: pointer.y, - left: target.left, - top: target.top, - theta: degreesToRadians(target.angle), - width: target.width * target.scaleX, - mouseXSign: 1, - mouseYSign: 1 - }; - - this._currentTransform.original = { - left: target.left, - top: target.top, - scaleX: target.scaleX, - scaleY: target.scaleY, - originX: origin.x, - originY: origin.y - }; - - this._resetCurrentTransform(e); - }, - - /** - * Translates object by "setting" its left/top - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - */ - _translateObject: function (x, y) { - var target = this._currentTransform.target; - - if (!target.get('lockMovementX')) { - target.set('left', x - this._currentTransform.offsetX); - } - if (!target.get('lockMovementY')) { - target.set('top', y - this._currentTransform.offsetY); - } - }, - - /** - * Scales object by invoking its scaleX/scaleY methods - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. - * When not provided, an object is scaled by both dimensions equally - */ - _scaleObject: function (x, y, by) { - var t = this._currentTransform, - target = t.target, - lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); - - if (lockScalingX && lockScalingY) return; - - // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), - localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); - - this._setLocalMouse(localMouse, t); - - // Actually scale the object - this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by); - - // Make sure the constraints apply - target.setPositionByOrigin(constraintPosition, t.originX, t.originY); - }, - - /** - * @private - */ - _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by) { - var target = transform.target; - - transform.newScaleX = target.scaleX; - transform.newScaleY = target.scaleY; - - if (by === 'equally' && !lockScalingX && !lockScalingY) { - this._scaleObjectEqually(localMouse, target, transform); - } - else if (!by) { - transform.newScaleX = localMouse.x / (target.width + target.strokeWidth); - transform.newScaleY = localMouse.y / (target.height + target.strokeWidth); - - lockScalingX || target.set('scaleX', transform.newScaleX); - lockScalingY || target.set('scaleY', transform.newScaleY); - } - else if (by === 'x' && !target.get('lockUniScaling')) { - transform.newScaleX = localMouse.x / (target.width + target.strokeWidth); - lockScalingX || target.set('scaleX', transform.newScaleX); - } - else if (by === 'y' && !target.get('lockUniScaling')) { - transform.newScaleY = localMouse.y / (target.height + target.strokeWidth); - lockScalingY || target.set('scaleY', transform.newScaleY); - } - - this._flipObject(transform); - }, - - /** - * @private - */ - _scaleObjectEqually: function(localMouse, target, transform) { - - var dist = localMouse.y + localMouse.x, - lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + - (target.width + (target.strokeWidth)) * transform.original.scaleX; - - // We use transform.scaleX/Y instead of target.scaleX/Y - // because the object may have a min scale and we'll loose the proportions - transform.newScaleX = transform.original.scaleX * dist / lastDist; - transform.newScaleY = transform.original.scaleY * dist / lastDist; - - target.set('scaleX', transform.newScaleX); - target.set('scaleY', transform.newScaleY); - }, - - /** - * @private - */ - _flipObject: function(transform) { - if (transform.newScaleX < 0) { - if (transform.originX === 'left') { - transform.originX = 'right'; + }); + fabric.util.object.extend(fabric.Gradient, { + fromElement: function(el, instance) { + 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 + }); + }, + forObject: function(obj, options) { + options || (options = {}); + _convertPercentUnitsToValues(obj, options); + return new fabric.Gradient(options); } - else if (transform.originX === 'right') { - transform.originX = 'left'; + }); + 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); } - } - - if (transform.newScaleY < 0) { - if (transform.originY === 'top') { - transform.originY = 'bottom'; - } - else if (transform.originY === 'bottom') { - transform.originY = 'top'; - } - } - }, - - /** - * @private - */ - _setLocalMouse: function(localMouse, t) { - var target = t.target; - - if (t.originX === 'right') { - localMouse.x *= -1; - } - else if (t.originX === 'center') { - localMouse.x *= t.mouseXSign * 2; - - if (localMouse.x < 0) { - t.mouseXSign = -t.mouseXSign; - } - } - - if (t.originY === 'bottom') { - localMouse.y *= -1; - } - else if (t.originY === 'center') { - localMouse.y *= t.mouseYSign * 2; - - if (localMouse.y < 0) { - t.mouseYSign = -t.mouseYSign; - } - } - - // adjust the mouse coordinates when dealing with padding - if (abs(localMouse.x) > target.padding) { - if (localMouse.x < 0) { - localMouse.x += target.padding; - } - else { - localMouse.x -= target.padding; - } - } - else { // mouse is within the padding, set to 0 - localMouse.x = 0; - } - - if (abs(localMouse.y) > target.padding) { - if (localMouse.y < 0) { - localMouse.y += target.padding; - } - else { - localMouse.y -= target.padding; - } - } - else { - localMouse.y = 0; - } - }, - - /** - * Rotates object by invoking its rotate method - * @private - * @param x {Number} pointer's x coordinate - * @param y {Number} pointer's y coordinate - */ - _rotateObject: function (x, y) { - - var t = this._currentTransform; - - if (t.target.get('lockRotation')) return; - - var lastAngle = atan2(t.ey - t.top, t.ex - t.left), - curAngle = atan2(y - t.top, x - t.left), - angle = radiansToDegrees(curAngle - lastAngle + t.theta); - - // normalize angle to positive value - if (angle < 0) { - angle = 360 + angle; - } - - t.target.angle = angle; - }, - - /** - * @private - */ - _setCursor: function (value) { - this.upperCanvasEl.style.cursor = value; - }, - - /** - * @private - */ - _resetObjectTransform: function (target) { - target.scaleX = 1; - target.scaleY = 1; - target.setAngle(0); - }, - - /** - * @private - */ - _drawSelection: function () { - var ctx = this.contextTop, - groupSelector = this._groupSelector, - left = groupSelector.left, - top = groupSelector.top, - aleft = abs(left), - atop = abs(top); - - ctx.fillStyle = this.selectionColor; - - ctx.fillRect( - groupSelector.ex - ((left > 0) ? 0 : -left), - groupSelector.ey - ((top > 0) ? 0 : -top), - aleft, - atop - ); - - ctx.lineWidth = this.selectionLineWidth; - ctx.strokeStyle = this.selectionBorderColor; - - // selection border - if (this.selectionDashArray.length > 1) { - - var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), - py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); - - ctx.beginPath(); - - fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); - - ctx.closePath(); - ctx.stroke(); - } - else { - ctx.strokeRect( - groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), - groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), - aleft, - atop - ); - } - }, - - /** - * @private - */ - _isLastRenderedObject: function(e) { - return ( - this.controlsAboveOverlay && - this.lastRenderedObjectWithControlsAboveOverlay && - this.lastRenderedObjectWithControlsAboveOverlay.visible && - this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && - this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); - }, - - /** - * Method that determines what object we are clicking on - * @param {Event} e mouse event - * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through - */ - findTarget: function (e, skipGroup) { - if (this.skipTargetFind) return; - - if (this._isLastRenderedObject(e)) { - return this.lastRenderedObjectWithControlsAboveOverlay; - } - - // first check current group (if one exists) - var activeGroup = this.getActiveGroup(); - if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { - return activeGroup; - } - - var target = this._searchPossibleTargets(e); - this._fireOverOutEvents(target); - - return target; - }, - - /** - * @private - */ - _fireOverOutEvents: function(target) { - if (target) { - if (this._hoveredTarget !== target) { - this.fire('mouse:over', { target: target }); - target.fire('mouseover'); - if (this._hoveredTarget) { - this.fire('mouse:out', { target: this._hoveredTarget }); - this._hoveredTarget.fire('mouseout'); - } - this._hoveredTarget = target; - } - } - else if (this._hoveredTarget) { - this.fire('mouse:out', { target: this._hoveredTarget }); - this._hoveredTarget.fire('mouseout'); - this._hoveredTarget = null; - } - }, - - /** - * @private - */ - _checkTarget: function(e, obj, pointer) { - if (obj && - obj.visible && - obj.evented && - this.containsPoint(e, obj)){ - if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { - var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); - if (!isTransparent) { - return true; - } - } - else { - return true; - } - } - }, - - /** - * @private - */ - _searchPossibleTargets: function(e) { - - // Cache all targets where their bounding box contains point. - var target, - pointer = this.getPointer(e, true); - - var i = this._objects.length; - - while (i--) { - if (this._checkTarget(e, this._objects[i], pointer)){ - this.relatedTarget = this._objects[i]; - target = this._objects[i]; - break; - } - } - - return target; - }, - - /** - * Returns pointer coordinates relative to canvas. - * @param {Event} e - * @return {Object} object with "x" and "y" number values - */ - getPointer: function (e, ignoreZoom, upperCanvasEl) { - if (!upperCanvasEl) { - upperCanvasEl = this.upperCanvasEl; - } - var pointer = getPointer(e, upperCanvasEl), - bounds = upperCanvasEl.getBoundingClientRect(), - cssScale; - - pointer.x = pointer.x - this._offset.left; - pointer.y = pointer.y - this._offset.top; - if (!ignoreZoom) { - pointer = fabric.util.transformPoint( - pointer, - fabric.util.invertTransform(this.viewportTransform) - ); - } - - if (bounds.width === 0 || bounds.height === 0) { - // If bounds are not available (i.e. not visible), do not apply scale. - cssScale = { width: 1, height: 1 }; - } - else { - cssScale = { - width: upperCanvasEl.width / bounds.width, - height: upperCanvasEl.height / bounds.height - }; - } - return { - x: pointer.x * cssScale.width, - y: pointer.y * cssScale.height - }; - }, - - /** - * @private - * @param {HTMLElement|String} canvasEl Canvas element - * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized - */ - _createUpperCanvas: function () { - var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); - - this.upperCanvasEl = this._createCanvasElement(); - fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); - - this.wrapperEl.appendChild(this.upperCanvasEl); - - this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); - this._applyCanvasStyle(this.upperCanvasEl); - this.contextTop = this.upperCanvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createCacheCanvas: function () { - this.cacheCanvasEl = this._createCanvasElement(); - this.cacheCanvasEl.setAttribute('width', this.width); - this.cacheCanvasEl.setAttribute('height', this.height); - this.contextCache = this.cacheCanvasEl.getContext('2d'); - }, - - /** - * @private - * @param {Number} width - * @param {Number} height - */ - _initWrapperElement: function () { - this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { - 'class': this.containerClass - }); - fabric.util.setStyle(this.wrapperEl, { - width: this.getWidth() + 'px', - height: this.getHeight() + 'px', - position: 'relative' - }); - fabric.util.makeElementUnselectable(this.wrapperEl); - }, - - /** - * @private - * @param {Element} element - */ - _applyCanvasStyle: function (element) { - var width = this.getWidth() || element.width, - height = this.getHeight() || element.height; - - fabric.util.setStyle(element, { - position: 'absolute', - width: width + 'px', - height: height + 'px', - left: 0, - top: 0 - }); - element.width = width; - element.height = height; - fabric.util.makeElementUnselectable(element); - }, - - /** - * Copys the the entire inline style from one element (fromEl) to another (toEl) - * @private - * @param {Element} fromEl Element style is copied from - * @param {Element} toEl Element copied style is applied to - */ - _copyCanvasStyle: function (fromEl, toEl) { - toEl.style.cssText = fromEl.style.cssText; - }, - - /** - * Returns context of canvas where object selection is drawn - * @return {CanvasRenderingContext2D} - */ - getSelectionContext: function() { - return this.contextTop; - }, - - /** - * Returns <canvas> element on which object selection is drawn - * @return {HTMLCanvasElement} - */ - getSelectionElement: function () { - return this.upperCanvasEl; - }, - - /** - * @private - * @param {Object} object - */ - _setActiveObject: function(object) { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = object; - object.set('active', true); - }, - - /** - * Sets given object as the only active object on canvas - * @param {fabric.Object} object Object to set as an active one - * @param {Event} [e] Event (passed along when firing "object:selected") - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveObject: function (object, e) { - this._setActiveObject(object); - this.renderAll(); - this.fire('object:selected', { target: object, e: e }); - object.fire('selected', { e: e }); - return this; - }, - - /** - * Returns currently active object - * @return {fabric.Object} active object - */ - getActiveObject: function () { - return this._activeObject; - }, - - /** - * @private - */ - _discardActiveObject: function() { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = null; - }, - - /** - * Discards currently active object - * @return {fabric.Canvas} thisArg - * @chainable - */ - discardActiveObject: function (e) { - this._discardActiveObject(); - this.renderAll(); - this.fire('selection:cleared', { e: e }); - return this; - }, - - /** - * @private - * @param {fabric.Group} group - */ - _setActiveGroup: function(group) { - this._activeGroup = group; - if (group) { - group.canvas = this; - group._calcBounds(); - group._updateObjectsCoords(); - group.setCoords(); - group.set('active', true); - } - }, - - /** - * Sets active group to a speicified one - * @param {fabric.Group} group Group to set as a current one - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveGroup: function (group, e) { - this._setActiveGroup(group); - if (group) { - this.fire('object:selected', { target: group, e: e }); - group.fire('selected', { e: e }); - } - return this; - }, - - /** - * Returns currently active group - * @return {fabric.Group} Current group - */ - getActiveGroup: function () { - return this._activeGroup; - }, - - /** - * @private - */ - _discardActiveGroup: function() { - var g = this.getActiveGroup(); - if (g) { - g.destroy(); - } - this.setActiveGroup(null); - }, - - /** - * Discards currently active group - * @return {fabric.Canvas} thisArg - */ - discardActiveGroup: function (e) { - this._discardActiveGroup(); - this.fire('selection:cleared', { e: e }); - return this; - }, - - /** - * Deactivates all objects on canvas, removing any active group or object - * @return {fabric.Canvas} thisArg - */ - deactivateAll: function () { - var allObjects = this.getObjects(), - i = 0, - len = allObjects.length; - for ( ; i < len; i++) { - allObjects[i].set('active', false); - } - this._discardActiveGroup(); - this._discardActiveObject(); - return this; - }, - - /** - * Deactivates all objects and dispatches appropriate events - * @return {fabric.Canvas} thisArg - */ - deactivateAllWithDispatch: function (e) { - var activeObject = this.getActiveGroup() || this.getActiveObject(); - if (activeObject) { - this.fire('before:selection:cleared', { target: activeObject, e: e }); - } - this.deactivateAll(); - if (activeObject) { - this.fire('selection:cleared', { e: e }); - } - return this; - }, - - /** - * Draws objects' controls (borders/controls) - * @param {CanvasRenderingContext2D} ctx Context to render controls on - */ - drawControls: function(ctx) { - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - this._drawGroupControls(ctx, activeGroup); - } - else { - this._drawObjectsControls(ctx); - } - }, - - /** - * @private - */ - _drawGroupControls: function(ctx, activeGroup) { - activeGroup._renderControls(ctx); - }, - - /** - * @private - */ - _drawObjectsControls: function(ctx) { - for (var i = 0, len = this._objects.length; i < len; ++i) { - if (!this._objects[i] || !this._objects[i].active) continue; - this._objects[i]._renderControls(ctx); - this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; - } } - }); - - // copying static properties manually to work around Opera's bug, - // where "prototype" property is enumerable and overrides existing prototype - for (var prop in fabric.StaticCanvas) { - if (prop !== 'prototype') { - fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + 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); + } + } + function _convertValuesToPercentUnits(object, options) { + for (var prop in options) { + normalize(options, prop, object); + 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) + "%"; + } + } } - } - - if (fabric.isTouchSupported) { - /** @ignore */ - fabric.Canvas.prototype._setCursorFromEvent = function() { }; - } - - /** - * @class fabric.Element - * @alias fabric.Canvas - * @deprecated Use {@link fabric.Canvas} instead. - * @constructor - */ - fabric.Element = fabric.Canvas; })(); - -(function(){ - - var cursorOffset = { - mt: 0, // n - tr: 1, // ne - mr: 2, // e - br: 3, // se - mb: 4, // s - bl: 5, // sw - ml: 6, // w - tl: 7 // nw - }, - addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener; - - fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { - - /** - * Map of cursor style values for each of the object controls - * @private - */ - cursorMap: [ - 'n-resize', - 'ne-resize', - 'e-resize', - 'se-resize', - 's-resize', - 'sw-resize', - 'w-resize', - 'nw-resize' - ], - - /** - * Adds mouse listeners to canvas - * @private - */ - _initEventListeners: function () { - - this._bindEvents(); - - addListener(fabric.window, 'resize', this._onResize); - - // mouse events - addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); - - // touch events - addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof Event !== 'undefined' && 'add' in Event) { - Event.add(this.upperCanvasEl, 'gesture', this._onGesture); - Event.add(this.upperCanvasEl, 'drag', this._onDrag); - Event.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); - Event.add(this.upperCanvasEl, 'shake', this._onShake); - } +fabric.Pattern = fabric.util.createClass({ + repeat: "repeat", + offsetX: 0, + offsetY: 0, + initialize: function(options) { + options || (options = {}); + this.id = fabric.Object.__uid++; + if (options.source) { + if (typeof options.source === "string") { + if (typeof fabric.util.getFunctionBody(options.source) !== "undefined") { + this.source = new Function(fabric.util.getFunctionBody(options.source)); + } else { + var _this = this; + this.source = fabric.util.createImage(); + fabric.util.loadImage(options.source, function(img) { + _this.source = img; + }); + } + } else { + this.source = options.source; + } + } + if (options.repeat) { + this.repeat = options.repeat; + } + if (options.offsetX) { + this.offsetX = options.offsetX; + } + if (options.offsetY) { + this.offsetY = options.offsetY; + } }, - - /** - * @private - */ - _bindEvents: function() { - this._onMouseDown = this._onMouseDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseUp = this._onMouseUp.bind(this); - this._onResize = this._onResize.bind(this); - this._onGesture = this._onGesture.bind(this); - this._onDrag = this._onDrag.bind(this); - this._onShake = this._onShake.bind(this); - this._onOrientationChange = this._onOrientationChange.bind(this); - this._onMouseWheel = this._onMouseWheel.bind(this); - }, - - /** - * Removes all event listeners - */ - removeListeners: function() { - removeListener(fabric.window, 'resize', this._onResize); - - removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); - - removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof Event !== 'undefined' && 'remove' in Event) { - Event.remove(this.upperCanvasEl, 'gesture', this._onGesture); - Event.remove(this.upperCanvasEl, 'drag', this._onDrag); - Event.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); - Event.remove(this.upperCanvasEl, 'shake', this._onShake); - } - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js gesture - * @param {Event} [self] Inner Event object - */ - _onGesture: function(e, s) { - this.__onTransformGesture && this.__onTransformGesture(e, s); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js drag - * @param {Event} [self] Inner Event object - */ - _onDrag: function(e, s) { - this.__onDrag && this.__onDrag(e, s); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js wheel event - * @param {Event} [self] Inner Event object - */ - _onMouseWheel: function(e, s) { - this.__onMouseWheel && this.__onMouseWheel(e, s); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js orientation change - * @param {Event} [self] Inner Event object - */ - _onOrientationChange: function(e,s) { - this.__onOrientationChange && this.__onOrientationChange(e,s); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onShake: function(e,s) { - this.__onShake && this.__onShake(e,s); - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseDown: function (e) { - this.__onMouseDown(e); - - addListener(fabric.document, 'touchend', this._onMouseUp); - addListener(fabric.document, 'touchmove', this._onMouseMove); - - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (e.type === 'touchstart') { - // Unbind mousedown to prevent double triggers from touch devices - removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - } - else { - addListener(fabric.document, 'mouseup', this._onMouseUp); - addListener(fabric.document, 'mousemove', this._onMouseMove); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mouseup - */ - _onMouseUp: function (e) { - this.__onMouseUp(e); - - removeListener(fabric.document, 'mouseup', this._onMouseUp); - removeListener(fabric.document, 'touchend', this._onMouseUp); - - removeListener(fabric.document, 'mousemove', this._onMouseMove); - removeListener(fabric.document, 'touchmove', this._onMouseMove); - - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (e.type === 'touchend') { - // Wait 400ms before rebinding mousedown to prevent double triggers - // from touch devices - var _this = this; - setTimeout(function() { - addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); - }, 400); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mousemove - */ - _onMouseMove: function (e) { - !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); - this.__onMouseMove(e); - }, - - /** - * @private - */ - _onResize: function () { - this.calcOffset(); - }, - - /** - * Decides whether the canvas should be redrawn in mouseup and mousedown events. - * @private - * @param {Object} target - * @param {Object} pointer - */ - _shouldRender: function(target, pointer) { - var activeObject = this.getActiveGroup() || this.getActiveObject(); - - return !!( - (target && ( - target.isMoving || - target !== activeObject)) - || - (!target && !!activeObject) - || - (!target && !activeObject && !this._groupSelector) - || - (pointer && - this._previousPointer && - this.selection && ( - pointer.x !== this._previousPointer.x || - pointer.y !== this._previousPointer.y)) - ); - }, - - /** - * Method that defines the actions when mouse is released on canvas. - * The method resets the currentTransform parameters, store the image corner - * position in the image object and render the canvas on top. - * @private - * @param {Event} e Event object fired on mouseup - */ - __onMouseUp: function (e) { - var target; - - if (this.isDrawingMode && this._isCurrentlyDrawing) { - this._onMouseUpInDrawingMode(e); - return; - } - - if (this._currentTransform) { - this._finalizeCurrentTransform(); - target = this._currentTransform.target; - } - else { - target = this.findTarget(e, true); - } - - var shouldRender = this._shouldRender(target, this.getPointer(e)); - - this._maybeGroupObjects(e); - - if (target) { - target.isMoving = false; - } - - shouldRender && this.renderAll(); - - this._handleCursorAndEvent(e, target); - }, - - _handleCursorAndEvent: function(e, target) { - this._setCursorFromEvent(e, target); - - // TODO: why are we doing this? - var _this = this; - setTimeout(function () { - _this._setCursorFromEvent(e, target); - }, 50); - - this.fire('mouse:up', { target: target, e: e }); - target && target.fire('mouseup', { e: e }); - }, - - /** - * @private - */ - _finalizeCurrentTransform: function() { - - var transform = this._currentTransform, - target = transform.target; - - if (target._scaling) { - target._scaling = false; - } - - target.setCoords(); - - // only fire :modified event if target coordinates were changed during mousedown-mouseup - if (this.stateful && target.hasStateChanged()) { - this.fire('object:modified', { target: target }); - target.fire('modified'); - } - - this._restoreOriginXY(target); - }, - - /** - * @private - * @param {Object} target Object to restore - */ - _restoreOriginXY: function(target) { - if (this._previousOriginX && this._previousOriginY) { - - var originPoint = target.translateToOriginPoint( - target.getCenterPoint(), - this._previousOriginX, - this._previousOriginY); - - target.originX = this._previousOriginX; - target.originY = this._previousOriginY; - - target.left = originPoint.x; - target.top = originPoint.y; - - this._previousOriginX = null; - this._previousOriginY = null; - } - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseDownInDrawingMode: function(e) { - this._isCurrentlyDrawing = true; - this.discardActiveObject(e).renderAll(); - if (this.clipTo) { - fabric.util.clipContext(this, this.contextTop); - } - var ivt = fabric.util.invertTransform(this.viewportTransform); - var pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); - this.freeDrawingBrush.onMouseDown(pointer); - this.fire('mouse:down', { e: e }); - }, - - /** - * @private - * @param {Event} e Event object fired on mousemove - */ - _onMouseMoveInDrawingMode: function(e) { - if (this._isCurrentlyDrawing) { - var ivt = fabric.util.invertTransform(this.viewportTransform), - pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); - this.freeDrawingBrush.onMouseMove(pointer); - } - this.upperCanvasEl.style.cursor = this.freeDrawingCursor; - this.fire('mouse:move', { e: e }); - }, - - /** - * @private - * @param {Event} e Event object fired on mouseup - */ - _onMouseUpInDrawingMode: function(e) { - this._isCurrentlyDrawing = false; - if (this.clipTo) { - this.contextTop.restore(); - } - this.freeDrawingBrush.onMouseUp(); - this.fire('mouse:up', { e: e }); - }, - - /** - * Method that defines the actions when mouse is clic ked on canvas. - * The method inits the currentTransform parameters and renders all the - * canvas so the current image can be placed on the top canvas and the rest - * in on the container one. - * @private - * @param {Event} e Event object fired on mousedown - */ - __onMouseDown: function (e) { - - // accept only left clicks - var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; - if (!isLeftClick && !fabric.isTouchSupported) return; - - if (this.isDrawingMode) { - this._onMouseDownInDrawingMode(e); - return; - } - - // ignore if some object is being transformed at this moment - if (this._currentTransform) return; - - var target = this.findTarget(e), - pointer = this.getPointer(e, true); - - // save pointer for check in __onMouseUp event - this._previousPointer = pointer; - - var shouldRender = this._shouldRender(target, pointer), - shouldGroup = this._shouldGroup(e, target); - - if (this._shouldClearSelection(e, target)) { - this._clearSelection(e, target, pointer); - } - else if (shouldGroup) { - this._handleGrouping(e, target); - target = this.getActiveGroup(); - } - - if (target && target.selectable && !shouldGroup) { - this._beforeTransform(e, target); - this._setupCurrentTransform(e, target); - } - // we must renderAll so that active image is placed on the top canvas - shouldRender && this.renderAll(); - - this.fire('mouse:down', { target: target, e: e }); - target && target.fire('mousedown', { e: e }); - }, - - /** - * @private - */ - _beforeTransform: function(e, target) { - var corner; - - this.stateful && target.saveState(); - - // determine if it's a drag or rotate case - if ((corner = target._findTargetCorner(this.getPointer(e)))) { - this.onBeforeScaleRotate(target); - } - - if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { - this.deactivateAll(); - this.setActiveObject(target, e); - } - }, - - /** - * @private - */ - _clearSelection: function(e, target, pointer) { - this.deactivateAllWithDispatch(e); - - if (target && target.selectable) { - this.setActiveObject(target, e); - } - else if (this.selection) { - this._groupSelector = { - ex: pointer.x, - ey: pointer.y, - top: 0, - left: 0 + toObject: function() { + var source; + if (typeof this.source === "function") { + source = String(this.source); + } else if (typeof this.source.src === "string") { + source = this.source.src; + } + return { + source: source, + repeat: this.repeat, + offsetX: this.offsetX, + offsetY: this.offsetY }; - } }, - - /** - * @private - * @param {Object} target Object for that origin is set to center - */ - _setOriginToCenter: function(target) { - this._previousOriginX = this._currentTransform.target.originX; - this._previousOriginY = this._currentTransform.target.originY; - - var center = target.getCenterPoint(); - - target.originX = 'center'; - target.originY = 'center'; - - target.left = center.x; - target.top = center.y; - - this._currentTransform.left = target.left; - this._currentTransform.top = target.top; - }, - - /** - * @private - * @param {Object} target Object for that center is set to origin - */ - _setCenterToOrigin: function(target) { - var originPoint = target.translateToOriginPoint( - target.getCenterPoint(), - this._previousOriginX, - this._previousOriginY); - - target.originX = this._previousOriginX; - target.originY = this._previousOriginY; - - target.left = originPoint.x; - target.top = originPoint.y; - - this._previousOriginX = null; - this._previousOriginY = null; - }, - - /** - * Method that defines the actions when mouse is hovering the canvas. - * The currentTransform parameter will definde whether the user is rotating/scaling/translating - * an image or neither of them (only hovering). A group selection is also possible and would cancel - * all any other type of action. - * In case of an image transformation only the top canvas will be rendered. - * @private - * @param {Event} e Event object fired on mousemove - */ - __onMouseMove: function (e) { - - var target, pointer; - - if (this.isDrawingMode) { - this._onMouseMoveInDrawingMode(e); - return; - } - - var groupSelector = this._groupSelector; - - // We initially clicked in an empty area, so we draw a box for multiple selection - if (groupSelector) { - pointer = this.getPointer(e, true); - - groupSelector.left = pointer.x - groupSelector.ex; - groupSelector.top = pointer.y - groupSelector.ey; - - this.renderTop(); - } - else if (!this._currentTransform) { - - target = this.findTarget(e); - - if (!target || target && !target.selectable) { - this.upperCanvasEl.style.cursor = this.defaultCursor; + toSVG: function(object) { + var patternSource = typeof this.source === "function" ? this.source() : this.source, patternWidth = patternSource.width / object.getWidth(), patternHeight = patternSource.height / object.getHeight(), patternImgSrc = ""; + if (patternSource.src) { + patternImgSrc = patternSource.src; + } else if (patternSource.toDataURL) { + patternImgSrc = patternSource.toDataURL(); } - else { - this._setCursorFromEvent(e, target); + return '' + '' + ""; + }, + toLive: function(ctx) { + var source = typeof this.source === "function" ? this.source() : this.source; + if (!source) { + return ""; } - } - else { - this._transformObject(e); - } - - this.fire('mouse:move', { target: target, e: e }); - target && target.fire('mousemove', { e: e }); - }, - - /** - * @private - * @param {Event} e Event fired on mousemove - */ - _transformObject: function(e) { - var pointer = this.getPointer(e), - transform = this._currentTransform; - - transform.reset = false, - transform.target.isMoving = true; - - this._beforeScaleTransform(e, transform); - this._performTransformAction(e, transform, pointer); - - this.renderAll(); - }, - - /** - * @private - */ - _performTransformAction: function(e, transform, pointer) { - var x = pointer.x, - y = pointer.y, - target = transform.target, - action = transform.action; - - if (action === 'rotate') { - this._rotateObject(x, y); - this._fire('rotating', target, e); - } - else if (action === 'scale') { - this._onScale(e, transform, x, y); - this._fire('scaling', target, e); - } - else if (action === 'scaleX') { - this._scaleObject(x, y, 'x'); - this._fire('scaling', target, e); - } - else if (action === 'scaleY') { - this._scaleObject(x, y, 'y'); - this._fire('scaling', target, e); - } - else { - this._translateObject(x, y); - this._fire('moving', target, e); - this._setCursor(this.moveCursor); - } - }, - - /** - * @private - */ - _fire: function(eventName, target, e) { - this.fire('object:' + eventName, { target: target, e: e }); - target.fire(eventName, { e: e }); - }, - - /** - * @private - */ - _beforeScaleTransform: function(e, transform) { - if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { - var centerTransform = this._shouldCenterTransform(e, transform.target); - - // Switch from a normal resize to center-based - if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || - // Switch from center-based resize to normal one - (!centerTransform && transform.originX === 'center' && transform.originY === 'center') - ) { - this._resetCurrentTransform(e); - transform.reset = true; + if (typeof source.src !== "undefined") { + if (!source.complete) { + return ""; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ""; + } } - } - }, - - /** - * @private - */ - _onScale: function(e, transform, x, y) { - // rotate object only if shift key is not pressed - // and if it is not a group we are transforming - if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) { - transform.currentAction = 'scale'; - this._scaleObject(x, y); - } - else { - // Switch from a normal resize to proportional - if (!transform.reset && transform.currentAction === 'scale') { - this._resetCurrentTransform(e, transform.target); - } - - transform.currentAction = 'scaleEqually'; - this._scaleObject(x, y, 'equally'); - } - }, - - /** - * Sets the cursor depending on where the canvas is being hovered. - * Note: very buggy in Opera - * @param {Event} e Event object - * @param {Object} target Object that the mouse is hovering, if so. - */ - _setCursorFromEvent: function (e, target) { - var style = this.upperCanvasEl.style; - - if (!target || !target.selectable) { - style.cursor = this.defaultCursor; - return false; - } - else { - var activeGroup = this.getActiveGroup(), - // only show proper corner when group selection is not active - corner = target._findTargetCorner - && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(this.getPointer(e, true)); - - if (!corner) { - style.cursor = target.hoverCursor || this.hoverCursor; - } - else { - this._setCornerCursor(corner, target); - } - } - return true; - }, - - /** - * @private - */ - _setCornerCursor: function(corner, target) { - var style = this.upperCanvasEl.style; - - if (corner in cursorOffset) { - style.cursor = this._getRotatedCornerCursor(corner, target); - } - else if (corner === 'mtr' && target.hasRotatingPoint) { - style.cursor = this.rotationCursor; - } - else { - style.cursor = this.defaultCursor; - return false; - } - }, - - /** - * @private - */ - _getRotatedCornerCursor: function(corner, target) { - var n = Math.round((target.getAngle() % 360) / 45); - - if (n < 0) { - n += 8; // full circle ahead - } - n += cursorOffset[corner]; - // normalize n to be from 0 to 7 - n %= 8; - - return this.cursorMap[n]; + return ctx.createPattern(source, this.repeat); } - }); -})(); - - -(function(){ - - var min = Math.min, - max = Math.max; - - fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - * @return {Boolean} - */ - _shouldGroup: function(e, target) { - var activeObject = this.getActiveObject(); - return e.shiftKey && - (this.getActiveGroup() || (activeObject && activeObject !== target)) - && this.selection; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _handleGrouping: function (e, target) { - - if (target === this.getActiveGroup()) { - - // if it's a group, find target again, this time skipping group - target = this.findTarget(e, true); - - // if even object is not found, bail out - if (!target || target.isType('group')) { - return; - } - } - if (this.getActiveGroup()) { - this._updateActiveGroup(target, e); - } - else { - this._createActiveGroup(target, e); - } - - if (this._activeGroup) { - this._activeGroup.saveCoords(); - } - }, - - /** - * @private - */ - _updateActiveGroup: function(target, e) { - var activeGroup = this.getActiveGroup(); - - if (activeGroup.contains(target)) { - - activeGroup.removeWithUpdate(target); - this._resetObjectTransform(activeGroup); - target.set('active', false); - - if (activeGroup.size() === 1) { - // remove group alltogether if after removal it only contains 1 object - this.discardActiveGroup(e); - // activate last remaining object - this.setActiveObject(activeGroup.item(0)); - return; - } - } - else { - activeGroup.addWithUpdate(target); - this._resetObjectTransform(activeGroup); - } - this.fire('selection:created', { target: activeGroup, e: e }); - activeGroup.set('active', true); - }, - - /** - * @private - */ - _createActiveGroup: function(target, e) { - - if (this._activeObject && target !== this._activeObject) { - - var group = this._createGroup(target); - - this.setActiveGroup(group); - this._activeObject = null; - - this.fire('selection:created', { target: group, e: e }); - } - - target.set('active', true); - }, - - /** - * @private - * @param {Object} target - */ - _createGroup: function(target) { - - var objects = this.getObjects(), - isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), - groupObjects = isActiveLower - ? [ this._activeObject, target ] - : [ target, this._activeObject ]; - - return new fabric.Group(groupObjects, { - originX: 'center', - originY: 'center' - }); - }, - - /** - * @private - * @param {Event} e mouse event - */ - _groupSelectedObjects: function (e) { - - var group = this._collectObjects(); - - // do not create group for 1 element only - if (group.length === 1) { - this.setActiveObject(group[0], e); - } - else if (group.length > 1) { - group = new fabric.Group(group.reverse(), { - originX: 'center', - originY: 'center' - }); - this.setActiveGroup(group, e); - group.saveCoords(); - this.fire('selection:created', { target: group }); - this.renderAll(); - } - }, - - /** - * @private - */ - _collectObjects: function() { - var group = [ ], - currentObject, - x1 = this._groupSelector.ex, - y1 = this._groupSelector.ey, - x2 = x1 + this._groupSelector.left, - y2 = y1 + this._groupSelector.top, - selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), - selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), - isClick = x1 === x2 && y1 === y2; - - for (var i = this._objects.length; i--; ) { - currentObject = this._objects[i]; - - if (!currentObject || !currentObject.selectable || !currentObject.visible) continue; - - if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || - currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || - currentObject.containsPoint(selectionX1Y1) || - currentObject.containsPoint(selectionX2Y2) - ) { - currentObject.set('active', true); - group.push(currentObject); - - // only add one object if it's a click - if (isClick) break; - } - } - - return group; - }, - - /** - * @private - */ - _maybeGroupObjects: function(e) { - if (this.selection && this._groupSelector) { - this._groupSelectedObjects(e); - } - - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.setObjectsCoords().setCoords(); - activeGroup.isMoving = false; - this._setCursor(this.defaultCursor); - } - - // clear selection and current transformation - this._groupSelector = null; - this._currentTransform = null; - } - }); - -})(); - - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately - * @param {Object} [options] Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} - * @example Generate jpeg dataURL with lower quality - * var dataURL = canvas.toDataURL({ - * format: 'jpeg', - * quality: 0.8 - * }); - * @example Generate cropped png dataURL (clipping of canvas) - * var dataURL = canvas.toDataURL({ - * format: 'png', - * left: 100, - * top: 100, - * width: 200, - * height: 200 - * }); - * @example Generate double scaled png dataURL - * var dataURL = canvas.toDataURL({ - * format: 'png', - * multiplier: 2 - * }); - */ - toDataURL: function (options) { - options || (options = { }); - - var format = options.format || 'png', - quality = options.quality || 1, - multiplier = options.multiplier || 1, - cropping = { - left: options.left, - top: options.top, - width: options.width, - height: options.height - }; - - if (multiplier !== 1) { - return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); - } - else { - return this.__toDataURL(format, quality, cropping); - } - }, - - /** - * @private - */ - __toDataURL: function(format, quality, cropping) { - - this.renderAll(true); - - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, - croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (format === 'jpg') { - format = 'jpeg'; - } - - var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) - ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) - : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - if (croppedCanvasEl) { - croppedCanvasEl = null; - } - - return data; - }, - - /** - * @private - */ - __getCroppedCanvas: function(canvasEl, cropping) { - - var croppedCanvasEl, - croppedCtx, - shouldCrop = 'left' in cropping || - 'top' in cropping || - 'width' in cropping || - 'height' in cropping; - - if (shouldCrop) { - - croppedCanvasEl = fabric.util.createCanvasElement(); - croppedCtx = croppedCanvasEl.getContext('2d'); - - croppedCanvasEl.width = cropping.width || this.width; - croppedCanvasEl.height = cropping.height || this.height; - - croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); - } - - return croppedCanvasEl; - }, - - /** - * @private - */ - __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { - - var origWidth = this.getWidth(), - origHeight = this.getHeight(), - scaledWidth = origWidth * multiplier, - scaledHeight = origHeight * multiplier, - activeObject = this.getActiveObject(), - activeGroup = this.getActiveGroup(), - - ctx = this.contextTop || this.contextContainer; - - if (multiplier > 1) { - this.setWidth(scaledWidth).setHeight(scaledHeight); - } - ctx.scale(multiplier, multiplier); - - if (cropping.left) { - cropping.left *= multiplier; - } - if (cropping.top) { - cropping.top *= multiplier; - } - if (cropping.width) { - cropping.width *= multiplier; - } - else if (multiplier < 1) { - cropping.width = scaledWidth; - } - if (cropping.height) { - cropping.height *= multiplier; - } - else if (multiplier < 1) { - cropping.height = scaledHeight; - } - - if (activeGroup) { - // not removing group due to complications with restoring it with correct state afterwords - this._tempRemoveBordersControlsFromGroup(activeGroup); - } - else if (activeObject && this.deactivateAll) { - this.deactivateAll(); - } - - this.renderAll(true); - - var data = this.__toDataURL(format, quality, cropping); - - // restoring width, height for `renderAll` to draw - // background properly (while context is scaled) - this.width = origWidth; - this.height = origHeight; - - ctx.scale(1 / multiplier, 1 / multiplier); - this.setWidth(origWidth).setHeight(origHeight); - - if (activeGroup) { - this._restoreBordersControlsOnGroup(activeGroup); - } - else if (activeObject && this.setActiveObject) { - this.setActiveObject(activeObject); - } - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - return data; - }, - - /** - * Exports canvas element to a dataurl image (allowing to change image size via multiplier). - * @deprecated since 1.0.13 - * @param {String} format (png|jpeg) - * @param {Number} multiplier - * @param {Number} quality (0..1) - * @return {String} - */ - toDataURLWithMultiplier: function (format, multiplier, quality) { - return this.toDataURL({ - format: format, - multiplier: multiplier, - quality: quality - }); - }, - - /** - * @private - */ - _tempRemoveBordersControlsFromGroup: function(group) { - group.origHasControls = group.hasControls; - group.origBorderColor = group.borderColor; - - group.hasControls = true; - group.borderColor = 'rgba(0,0,0,0)'; - - group.forEachObject(function(o) { - o.origBorderColor = o.borderColor; - o.borderColor = 'rgba(0,0,0,0)'; - }); - }, - - /** - * @private - */ - _restoreBordersControlsOnGroup: function(group) { - group.hideControls = group.origHideControls; - group.borderColor = group.origBorderColor; - - group.forEachObject(function(o) { - o.borderColor = o.origBorderColor; - delete o.origBorderColor; - }); - } }); - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Populates canvas with data from the specified dataless JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} - * @deprecated since 1.2.2 - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - */ - loadFromDatalessJSON: function (json, callback, reviver) { - return this.loadFromJSON(json, callback, reviver); - }, - - /** - * Populates canvas with data from the specified JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toJSON} - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} - * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} - * @example loadFromJSON - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); - * @example loadFromJSON with reviver - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { - * // `o` = json object - * // `object` = fabric.Object instance - * // ... do some stuff ... - * }); - */ - loadFromJSON: function (json, callback, reviver) { - if (!json) return; - - // serialize if it wasn't already - var serialized = (typeof json === 'string') - ? JSON.parse(json) - : json; - - this.clear(); - - var _this = this; - this._enlivenObjects(serialized.objects, function () { - _this._setBgOverlay(serialized, callback); - }, reviver); - - return this; - }, - - /** - * @private - * @param {Object} serialized Object with background and overlay information - * @param {Function} callback Invoked after all background and overlay images/patterns loaded - */ - _setBgOverlay: function(serialized, callback) { - var _this = this, - loaded = { - backgroundColor: false, - overlayColor: false, - backgroundImage: false, - overlayImage: false - }; - - if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { - callback && callback(); - return; - } - - var cbIfLoaded = function () { - if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { - _this.renderAll(); - callback && callback(); - } - }; - - this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); - this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); - this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); - this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); - - cbIfLoaded(); - }, - - /** - * @private - * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) - * @param {(Object|String)} value Value to set - * @param {Object} loaded Set loaded property to true if property is set - * @param {Object} callback Callback function to invoke after property is set - */ - __setBgOverlay: function(property, value, loaded, callback) { - var _this = this; - - if (!value) { - loaded[property] = true; - return; - } - - if (property === 'backgroundImage' || property === 'overlayImage') { - fabric.Image.fromObject(value, function(img) { - _this[property] = img; - loaded[property] = true; - callback && callback(); - }); - } - else { - this['set' + fabric.util.string.capitalize(property, true)](value, function() { - loaded[property] = true; - callback && callback(); - }); - } - }, - - /** - * @private - * @param {Array} objects - * @param {Function} callback - * @param {Function} [reviver] - */ - _enlivenObjects: function (objects, callback, reviver) { - var _this = this; - - if (!objects || objects.length === 0) { - callback && callback(); - return; - } - - var renderOnAddRemove = this.renderOnAddRemove; - this.renderOnAddRemove = false; - - fabric.util.enlivenObjects(objects, function(enlivenedObjects) { - enlivenedObjects.forEach(function(obj, index) { - _this.insertAt(obj, index, true); - }); - - _this.renderOnAddRemove = renderOnAddRemove; - callback && callback(); - }, null, reviver); - }, - - /** - * @private - * @param {String} format - * @param {Function} callback - */ - _toDataURL: function (format, callback) { - this.clone(function (clone) { - callback(clone.toDataURL(format)); - }); - }, - - /** - * @private - * @param {String} format - * @param {Number} multiplier - * @param {Function} callback - */ - _toDataURLWithMultiplier: function (format, multiplier, callback) { - this.clone(function (clone) { - callback(clone.toDataURLWithMultiplier(format, multiplier)); - }); - }, - - /** - * Clones canvas instance - * @param {Object} [callback] Receives cloned instance as a first argument - * @param {Array} [properties] Array of properties to include in the cloned canvas and children - */ - clone: function (callback, properties) { - var data = JSON.stringify(this.toJSON(properties)); - this.cloneWithoutData(function(clone) { - clone.loadFromJSON(data, function() { - callback && callback(clone); - }); - }); - }, - - /** - * Clones canvas instance without cloning existing data. - * This essentially copies canvas dimensions, clipping properties, etc. - * but leaves data empty (so that you can populate it with your own) - * @param {Object} [callback] Receives cloned instance as a first argument - */ - cloneWithoutData: function(callback) { - var el = fabric.document.createElement('canvas'); - - el.width = this.getWidth(); - el.height = this.getHeight(); - - var clone = new fabric.Canvas(el); - clone.clipTo = this.clipTo; - if (this.backgroundImage) { - clone.setBackgroundImage(this.backgroundImage.src, function() { - clone.renderAll(); - callback && callback(clone); - }); - clone.backgroundImageOpacity = this.backgroundImageOpacity; - clone.backgroundImageStretch = this.backgroundImageStretch; - } - else { - callback && callback(clone); - } - } -}); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians, - radiansToDegrees = fabric.util.radiansToDegrees; - - fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { - - /** - * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports - * 2 finger gestures. - * - * @param e Event object by Event.js - * @param self Event proxy object by Event.js - */ - __onTransformGesture: function(e, self) { - - if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || 'gesture' !== self.gesture) { - return; - } - - var target = this.findTarget(e); - if ('undefined' !== typeof target) { - this.onBeforeScaleRotate(target); - this._rotateObjectByAngle(self.rotation); - this._scaleObjectBy(self.scale); - } - - this.fire('touch:gesture', { target: target, e: e, self: self }); - }, - - /** - * Method that defines actions when an Event.js drag is detected. - * - * @param e Event object by Event.js - * @param self Event proxy object by Event.js - */ - __onDrag: function(e, self) { - this.fire('touch:drag', { e: e, self: self }); - }, - - /** - * Method that defines actions when an Event.js orientation event is detected. - * - * @param e Event object by Event.js - * @param self Event proxy object by Event.js - */ - __onOrientationChange: function(e, self) { - this.fire('touch:orientation', { e: e, self: self }); - }, - - /** - * Method that defines actions when an Event.js shake event is detected. - * - * @param e Event object by Event.js - * @param self Event proxy object by Event.js - */ - __onShake: function(e, self) { - this.fire('touch:shake', { e: e, self: self }); - }, - - /** - * Scales an object by a factor - * @param s {Number} The scale factor to apply to the current scale level - * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. - * When not provided, an object is scaled by both dimensions equally - */ - _scaleObjectBy: function(s, by) { - var t = this._currentTransform, - target = t.target, - lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); - - if (lockScalingX && lockScalingY) return; - - target._scaling = true; - - if (!by) { - if (!lockScalingX) { - target.set('scaleX', t.scaleX * s); - } - if (!lockScalingY) { - target.set('scaleY', t.scaleY * s); - } - } - else if (by === 'x' && !target.get('lockUniScaling')) { - lockScalingX || target.set('scaleX', t.scaleX * s); - } - else if (by === 'y' && !target.get('lockUniScaling')) { - lockScalingY || target.set('scaleY', t.scaleY * s); - } - }, - - /** - * Rotates object by an angle - * @param curAngle {Number} the angle of rotation in degrees - */ - _rotateObjectByAngle: function(curAngle) { - var t = this._currentTransform; - - if (t.target.get('lockRotation')) return; - t.target.angle = radiansToDegrees(degreesToRadians(curAngle) + t.theta); - } - }); -})(); - - (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - toFixed = fabric.util.toFixed, - capitalize = fabric.util.string.capitalize, - degreesToRadians = fabric.util.degreesToRadians, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Object) { - return; - } - - /** - * Root object class from which all 2d shape classes inherit from - * @class fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} - * @see {@link fabric.Object#initialize} for constructor definition - * - * @fires added - * @fires removed - * - * @fires selected - * @fires modified - * @fires rotating - * @fires scaling - * @fires moving - * - * @fires mousedown - * @fires mouseup - */ - fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { - - /** - * Retrieves object's {@link fabric.Object#clipTo|clipping function} - * @method getClipTo - * @memberOf fabric.Object.prototype - * @return {Function} - */ - - /** - * Sets object's {@link fabric.Object#clipTo|clipping function} - * @method setClipTo - * @memberOf fabric.Object.prototype - * @param {Function} clipTo Clipping function - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method getTransformMatrix - * @memberOf fabric.Object.prototype - * @return {Array} transformMatrix - */ - - /** - * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method setTransformMatrix - * @memberOf fabric.Object.prototype - * @param {Array} transformMatrix - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#visible|visible} state - * @method getVisible - * @memberOf fabric.Object.prototype - * @return {Boolean} True if visible - */ - - /** - * Sets object's {@link fabric.Object#visible|visible} state - * @method setVisible - * @memberOf fabric.Object.prototype - * @param {Boolean} value visible value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#shadow|shadow} - * @method getShadow - * @memberOf fabric.Object.prototype - * @return {Object} Shadow instance - */ - - /** - * Retrieves object's {@link fabric.Object#stroke|stroke} - * @method getStroke - * @memberOf fabric.Object.prototype - * @return {String} stroke value - */ - - /** - * Sets object's {@link fabric.Object#stroke|stroke} - * @method setStroke - * @memberOf fabric.Object.prototype - * @param {String} value stroke value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method getStrokeWidth - * @memberOf fabric.Object.prototype - * @return {Number} strokeWidth value - */ - - /** - * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method setStrokeWidth - * @memberOf fabric.Object.prototype - * @param {Number} value strokeWidth value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originX|originX} - * @method getOriginX - * @memberOf fabric.Object.prototype - * @return {String} originX value - */ - - /** - * Sets object's {@link fabric.Object#originX|originX} - * @method setOriginX - * @memberOf fabric.Object.prototype - * @param {String} value originX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originY|originY} - * @method getOriginY - * @memberOf fabric.Object.prototype - * @return {String} originY value - */ - - /** - * Sets object's {@link fabric.Object#originY|originY} - * @method setOriginY - * @memberOf fabric.Object.prototype - * @param {String} value originY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#fill|fill} - * @method getFill - * @memberOf fabric.Object.prototype - * @return {String} Fill value - */ - - /** - * Sets object's {@link fabric.Object#fill|fill} - * @method setFill - * @memberOf fabric.Object.prototype - * @param {String} value Fill value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#opacity|opacity} - * @method getOpacity - * @memberOf fabric.Object.prototype - * @return {Number} Opacity value (0-1) - */ - - /** - * Sets object's {@link fabric.Object#opacity|opacity} - * @method setOpacity - * @memberOf fabric.Object.prototype - * @param {Number} value Opacity value (0-1) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) - * @method getAngle - * @memberOf fabric.Object.prototype - * @return {Number} - */ - - /** - * Sets object's {@link fabric.Object#angle|angle} - * @method setAngle - * @memberOf fabric.Object.prototype - * @param {Number} value Angle value (in degrees) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#top|top position} - * @method getTop - * @memberOf fabric.Object.prototype - * @return {Number} Top value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#top|top position} - * @method setTop - * @memberOf fabric.Object.prototype - * @param {Number} value Top value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#left|left position} - * @method getLeft - * @memberOf fabric.Object.prototype - * @return {Number} Left value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#left|left position} - * @method setLeft - * @memberOf fabric.Object.prototype - * @param {Number} value Left value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleX|scaleX} value - * @method getScaleX - * @memberOf fabric.Object.prototype - * @return {Number} scaleX value - */ - - /** - * Sets object's {@link fabric.Object#scaleX|scaleX} value - * @method setScaleX - * @memberOf fabric.Object.prototype - * @param {Number} value scaleX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleY|scaleY} value - * @method getScaleY - * @memberOf fabric.Object.prototype - * @return {Number} scaleY value - */ - - /** - * Sets object's {@link fabric.Object#scaleY|scaleY} value - * @method setScaleY - * @memberOf fabric.Object.prototype - * @param {Number} value scaleY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipX|flipX} value - * @method getFlipX - * @memberOf fabric.Object.prototype - * @return {Boolean} flipX value - */ - - /** - * Sets object's {@link fabric.Object#flipX|flipX} value - * @method setFlipX - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipY|flipY} value - * @method getFlipY - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value - */ - - /** - * Sets object's {@link fabric.Object#flipY|flipY} value - * @method setFlipY - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Type of an object (rect, circle, path, etc.) - * @type String - * @default - */ - type: 'object', - - /** - * Horizontal origin of transformation of an object (one of "left", "right", "center") - * @type String - * @default - */ - originX: 'left', - - /** - * Vertical origin of transformation of an object (one of "top", "bottom", "center") - * @type String - * @default - */ - originY: 'top', - - /** - * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} - * @type Number - * @default - */ - top: 0, - - /** - * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} - * @type Number - * @default - */ - left: 0, - - /** - * Object width - * @type Number - * @default - */ - width: 0, - - /** - * Object height - * @type Number - * @default - */ - height: 0, - - /** - * Object scale factor (horizontal) - * @type Number - * @default - */ - scaleX: 1, - - /** - * Object scale factor (vertical) - * @type Number - * @default - */ - scaleY: 1, - - /** - * When true, an object is rendered as flipped horizontally - * @type Boolean - * @default - */ - flipX: false, - - /** - * When true, an object is rendered as flipped vertically - * @type Boolean - * @default - */ - flipY: false, - - /** - * Opacity of an object - * @type Number - * @default - */ - opacity: 1, - - /** - * Angle of rotation of an object (in degrees) - * @type Number - * @default - */ - angle: 0, - - /** - * Size of object's controlling corners (in pixels) - * @type Number - * @default - */ - cornerSize: 12, - - /** - * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) - * @type Boolean - * @default - */ - transparentCorners: true, - - /** - * Default cursor value used when hovering over this object on canvas - * @type String - * @default - */ - hoverCursor: null, - - /** - * Padding between object and its controlling borders (in pixels) - * @type Number - * @default - */ - padding: 0, - - /** - * Color of controlling borders of an object (when it's active) - * @type String - * @default - */ - borderColor: 'rgba(102,153,255,0.75)', - - /** - * Color of controlling corners of an object (when it's active) - * @type String - * @default - */ - cornerColor: 'rgba(102,153,255,0.5)', - - /** - * When true, this object will use center point as the origin of transformation - * when being scaled via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, this object will use center point as the origin of transformation - * when being rotated via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: true, - - /** - * Color of object's fill - * @type String - * @default - */ - fill: 'rgb(0,0,0)', - - /** - * Fill rule used to fill an object - * @type String - * @default - */ - fillRule: 'source-over', - - /** - * Background color of an object. Only works with text objects at the moment. - * @type String - * @default - */ - backgroundColor: '', - - /** - * When defined, an object is rendered via stroke and this property specifies its color - * @type String - * @default - */ - stroke: null, - - /** - * Width of a stroke used to render this object - * @type Number - * @default - */ - strokeWidth: 1, - - /** - * Array specifying dash pattern of an object's stroke (stroke must be defined) - * @type Array - */ - strokeDashArray: null, - - /** - * Line endings style of an object's stroke (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'butt', - - /** - * Corner style of an object's stroke (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'miter', - - /** - * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke - * @type Number - * @default - */ - strokeMiterLimit: 10, - - /** - * Shadow object representing shadow of this shape - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Opacity of object's controlling borders when object is active and moving - * @type Number - * @default - */ - borderOpacityWhenMoving: 0.4, - - /** - * Scale factor of object's controlling borders - * @type Number - * @default - */ - borderScaleFactor: 1, - - /** - * Transform matrix (similar to SVG's transform matrix) - * @type Array - */ - transformMatrix: null, - - /** - * Minimum allowed scale value of an object - * @type Number - * @default - */ - minScaleLimit: 0.01, - - /** - * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). - * But events still fire on it. - * @type Boolean - * @default - */ - selectable: true, - - /** - * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 - * @type Boolean - * @default - */ - evented: true, - - /** - * When set to `false`, an object is not rendered on canvas - * @type Boolean - * @default - */ - visible: true, - - /** - * When set to `false`, object's controls are not displayed and can not be used to manipulate object - * @type Boolean - * @default - */ - hasControls: true, - - /** - * When set to `false`, object's controlling borders are not rendered - * @type Boolean - * @default - */ - hasBorders: true, - - /** - * When set to `false`, object's controlling rotating point will not be visible or selectable - * @type Boolean - * @default - */ - hasRotatingPoint: true, - - /** - * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) - * @type Number - * @default - */ - rotatingPointOffset: 40, - - /** - * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * When `false`, default object's values are not included in its serialization - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Function that determines clipping of an object (context is passed as a first argument) - * Note that context origin is at the object's center point (not left/top corner) - * @type Function - */ - clipTo: null, - - /** - * When `true`, object horizontal movement is locked - * @type Boolean - * @default - */ - lockMovementX: false, - - /** - * When `true`, object vertical movement is locked - * @type Boolean - * @default - */ - lockMovementY: false, - - /** - * When `true`, object rotation is locked - * @type Boolean - * @default - */ - lockRotation: false, - - /** - * When `true`, object horizontal scaling is locked - * @type Boolean - * @default - */ - lockScalingX: false, - - /** - * When `true`, object vertical scaling is locked - * @type Boolean - * @default - */ - lockScalingY: false, - - /** - * When `true`, object non-uniform scaling is locked - * @type Boolean - * @default - */ - lockUniScaling: false, - - /** - * List of properties to consider when checking if state - * of an object is changed (fabric.Object#hasStateChanged) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: ( - 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + - 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + - 'angle opacity fill fillRule shadow clipTo visible backgroundColor' - ).split(' '), - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * @private - */ - _initGradient: function(options) { - if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { - this.set('fill', new fabric.Gradient(options.fill)); - } - }, - - /** - * @private - */ - _initPattern: function(options) { - if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { - this.set('fill', new fabric.Pattern(options.fill)); - } - if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { - this.set('stroke', new fabric.Pattern(options.stroke)); - } - }, - - /** - * @private - */ - _initClipping: function(options) { - if (!options.clipTo || typeof options.clipTo !== 'string') return; - - var functionBody = fabric.util.getFunctionBody(options.clipTo); - if (typeof functionBody !== 'undefined') { - this.clipTo = new Function('ctx', functionBody); - } - }, - - /** - * Sets object's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this.set(prop, options[prop]); - } - this._initGradient(options); - this._initPattern(options); - this._initClipping(options); - }, - - /** - * Transforms context when rendering an object - * @param {CanvasRenderingContext2D} ctx Context - * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node - */ - transform: function(ctx, fromLeft) { - if (this.group) { - this.group.transform(ctx, fromLeft); - } - ctx.globalAlpha = this.opacity; - - var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - ctx.scale( - this.scaleX * (this.flipX ? -1 : 1), - this.scaleY * (this.flipY ? -1 : 1) - ); - }, - - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor - }; - - if (!this.includeDefaultValues) { - object = this._removeDefaultValues(object); - } - - fabric.util.populateWithProperties(this, object, propertiesToInclude); - - return object; - }, - - /** - * Returns (dataless) object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - // will be overwritten by subclasses - return this.toObject(propertiesToInclude); - }, - - /** - * @private - * @param {Object} object - */ - _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype, - stateProperties = prototype.stateProperties; - - stateProperties.forEach(function(prop) { - if (object[prop] === prototype[prop]) { - delete object[prop]; + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Shadow) { + fabric.warn("fabric.Shadow is already defined."); + return; + } + fabric.Shadow = fabric.util.createClass({ + color: "rgb(0,0,0)", + blur: 0, + offsetX: 0, + offsetY: 0, + affectStroke: false, + includeDefaultValues: true, + initialize: function(options) { + if (typeof options === "string") { + options = this._parseShadow(options); + } + for (var prop in options) { + this[prop] = options[prop]; + } + this.id = fabric.Object.__uid++; + }, + _parseShadow: function(shadow) { + var shadowStr = shadow.trim(), offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, "") || "rgb(0,0,0)"; + return { + color: color.trim(), + offsetX: parseInt(offsetsAndBlur[1], 10) || 0, + offsetY: parseInt(offsetsAndBlur[2], 10) || 0, + blur: parseInt(offsetsAndBlur[3], 10) || 0 + }; + }, + toString: function() { + return [ this.offsetX, this.offsetY, this.blur, this.color ].join("px "); + }, + toSVG: function(object) { + var mode = "SourceAlpha"; + if (object && (object.fill === this.color || object.stroke === this.color)) { + mode = "SourceGraphic"; + } + return '' + '' + '' + "" + "" + '' + "" + ""; + }, + toObject: function() { + if (this.includeDefaultValues) { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + } + var obj = {}, proto = fabric.Shadow.prototype; + if (this.color !== proto.color) { + obj.color = this.color; + } + if (this.blur !== proto.blur) { + obj.blur = this.blur; + } + if (this.offsetX !== proto.offsetX) { + obj.offsetX = this.offsetX; + } + if (this.offsetY !== proto.offsetY) { + obj.offsetY = this.offsetY; + } + return obj; } - }); + }); + fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; +})(typeof exports !== "undefined" ? exports : this); - return object; - }, - - /** - * Returns a string representation of an instance - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Basic getter - * @param {String} property Property name - * @return {Any} value of a property - */ - get: function(property) { - return this[property]; - }, - - /** - * @private - */ - _setObject: function(obj) { - for (var prop in obj) { - this._set(prop, obj[prop]); - } - }, - - /** - * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. - * @param {String|Object} key Property name or object (if object, iterate over the object properties) - * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) - * @return {fabric.Object} thisArg - * @chainable - */ - set: function(key, value) { - if (typeof key === 'object') { - this._setObject(key); - } - else { - if (typeof value === 'function' && key !== 'clipTo') { - this._set(key, value(this.get(key))); +(function() { + "use strict"; + if (fabric.StaticCanvas) { + fabric.warn("fabric.StaticCanvas is already defined."); + return; + } + var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element"); + fabric.StaticCanvas = fabric.util.createClass({ + initialize: function(el, options) { + options || (options = {}); + this._initStatic(el, options); + fabric.StaticCanvas.activeInstance = this; + }, + backgroundColor: "", + backgroundImage: null, + overlayColor: "", + overlayImage: null, + includeDefaultValues: true, + stateful: true, + renderOnAddRemove: true, + clipTo: null, + controlsAboveOverlay: false, + allowTouchScrolling: false, + imageSmoothingEnabled: true, + viewportTransform: [ 1, 0, 0, 1, 0, 0 ], + onBeforeScaleRotate: function() {}, + _initStatic: function(el, options) { + this._objects = []; + this._createLowerCanvas(el); + this._initOptions(options); + this._setImageSmoothing(); + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); + } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); + } + if (options.overlayColor) { + this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); + } + this.calcOffset(); + }, + calcOffset: function() { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + setOverlayImage: function(image, callback, options) { + return this.__setBgOverlayImage("overlayImage", image, callback, options); + }, + setBackgroundImage: function(image, callback, options) { + return this.__setBgOverlayImage("backgroundImage", image, callback, options); + }, + setOverlayColor: function(overlayColor, callback) { + return this.__setBgOverlayColor("overlayColor", overlayColor, callback); + }, + setBackgroundColor: function(backgroundColor, callback) { + return this.__setBgOverlayColor("backgroundColor", backgroundColor, callback); + }, + _setImageSmoothing: function() { + var ctx = this.getContext(); + ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; + }, + __setBgOverlayImage: function(property, image, callback, options) { + if (typeof image === "string") { + fabric.util.loadImage(image, function(img) { + this[property] = new fabric.Image(img, options); + callback && callback(); + }, this); + } else { + this[property] = image; + callback && callback(); + } + return this; + }, + __setBgOverlayColor: function(property, color, callback) { + if (color.source) { + var _this = this; + fabric.util.loadImage(color.source, function(img) { + _this[property] = new fabric.Pattern({ + source: img, + repeat: color.repeat, + offsetX: color.offsetX, + offsetY: color.offsetY + }); + callback && callback(); + }); + } else { + this[property] = color; + callback && callback(); + } + return this; + }, + _createCanvasElement: function() { + var element = fabric.document.createElement("canvas"); + if (!element.style) { + element.style = {}; + } + if (!element) { + throw CANVAS_INIT_ERROR; + } + this._initCanvasElement(element); + return element; + }, + _initCanvasElement: function(element) { + fabric.util.createCanvasElement(element); + if (typeof element.getContext === "undefined") { + throw CANVAS_INIT_ERROR; + } + }, + _initOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; + if (!this.lowerCanvasEl.style) return; + this.lowerCanvasEl.width = this.width; + this.lowerCanvasEl.height = this.height; + this.lowerCanvasEl.style.width = this.width + "px"; + this.lowerCanvasEl.style.height = this.height + "px"; + }, + _createLowerCanvas: function(canvasEl) { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + this._initCanvasElement(this.lowerCanvasEl); + fabric.util.addClass(this.lowerCanvasEl, "lower-canvas"); + if (this.interactive) { + this._applyCanvasStyle(this.lowerCanvasEl); + } + this.contextContainer = this.lowerCanvasEl.getContext("2d"); + }, + getWidth: function() { + return this.width; + }, + getHeight: function() { + return this.height; + }, + setWidth: function(value) { + return this._setDimension("width", value); + }, + setHeight: function(value) { + return this._setDimension("height", value); + }, + setDimensions: function(dimensions) { + for (var prop in dimensions) { + this._setDimension(prop, dimensions[prop]); + } + return this; + }, + _setDimension: function(prop, value) { + this.lowerCanvasEl[prop] = value; + this.lowerCanvasEl.style[prop] = value + "px"; + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + this.upperCanvasEl.style[prop] = value + "px"; + } + if (this.cacheCanvasEl) { + this.cacheCanvasEl[prop] = value; + } + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value + "px"; + } + this[prop] = value; + this.calcOffset(); + this.renderAll(); + return this; + }, + getZoom: function() { + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + setViewportTransform: function(vpt) { + this.viewportTransform = vpt; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + zoomToPoint: function(point, value) { + var before = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + setZoom: function(value) { + this.zoomToPoint(new fabric.Point(0, 0), value); + return this; + }, + absolutePan: function(point) { + var wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.viewportTransform); + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + relativePan: function(point) { + return this.absolutePan(new fabric.Point(-point.x - this.viewportTransform[4], -point.y - this.viewportTransform[5])); + }, + getElement: function() { + return this.lowerCanvasEl; + }, + getActiveObject: function() { + return null; + }, + getActiveGroup: function() { + return null; + }, + _draw: function(ctx, object) { + if (!object) return; + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + object.render(ctx); + ctx.restore(); + if (!this.controlsAboveOverlay) object._renderControls(ctx); + }, + _onObjectAdded: function(obj) { + this.stateful && obj.setupState(); + obj.canvas = this; + if (obj._objects) { + obj._calcBounds(); + for (var i = 0, len = obj._objects.length; i < len; i++) { + obj._objects[i].canvas = this; + this._onObjectAdded(obj._objects[i]); + } + obj._updateObjectsCoords(); + } + obj.setCoords(); + this.fire("object:added", { + target: obj + }); + obj.fire("added"); + }, + _onObjectRemoved: function(obj) { + if (this.getActiveObject() === obj) { + this.fire("before:selection:cleared", { + target: obj + }); + this._discardActiveObject(); + this.fire("selection:cleared"); + } + this.fire("object:removed", { + target: obj + }); + obj.fire("removed"); + }, + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + getContext: function() { + return this.contextContainer; + }, + clear: function() { + this._objects.length = 0; + if (this.discardActiveGroup) { + this.discardActiveGroup(); + } + if (this.discardActiveObject) { + this.discardActiveObject(); + } + this.clearContext(this.contextContainer); + if (this.contextTop) { + this.clearContext(this.contextTop); + } + this.fire("canvas:cleared"); + this.renderAll(); + return this; + }, + renderAll: function(allOnTop) { + var canvasToDrawOn = this[allOnTop === true && this.interactive ? "contextTop" : "contextContainer"], activeGroup = this.getActiveGroup(); + if (this.contextTop && this.selection && !this._groupSelector) { + this.clearContext(this.contextTop); + } + if (!allOnTop) { + this.clearContext(canvasToDrawOn); + } + this.fire("before:render"); + if (this.clipTo) { + fabric.util.clipContext(this, canvasToDrawOn); + } + this._renderBackground(canvasToDrawOn); + this._renderObjects(canvasToDrawOn, activeGroup); + this._renderActiveGroup(canvasToDrawOn, activeGroup); + if (this.clipTo) { + canvasToDrawOn.restore(); + } + this._renderOverlay(canvasToDrawOn); + if (this.controlsAboveOverlay && this.interactive) { + this.drawControls(canvasToDrawOn); + } + this.fire("after:render"); + return this; + }, + _renderObjects: function(ctx, activeGroup) { + var i, length; + if (!activeGroup) { + for (i = 0, length = this._objects.length; i < length; ++i) { + this._draw(ctx, this._objects[i]); + } + } else { + for (i = 0, length = this._objects.length; i < length; ++i) { + if (this._objects[i] && !activeGroup.contains(this._objects[i])) { + this._draw(ctx, this._objects[i]); + } + } + } + }, + _renderActiveGroup: function(ctx, activeGroup) { + if (activeGroup) { + var sortedObjects = []; + this.forEachObject(function(object) { + if (activeGroup.contains(object)) { + sortedObjects.push(object); + } + }); + activeGroup._set("objects", sortedObjects); + this._draw(ctx, activeGroup); + } + }, + _renderBackground: function(ctx) { + if (this.backgroundColor) { + ctx.fillStyle = this.backgroundColor.toLive ? this.backgroundColor.toLive(ctx) : this.backgroundColor; + ctx.fillRect(this.backgroundColor.offsetX || 0, this.backgroundColor.offsetY || 0, this.width, this.height); + } + if (this.backgroundImage) { + this.backgroundImage.render(ctx); + } + }, + _renderOverlay: function(ctx) { + if (this.overlayColor) { + ctx.fillStyle = this.overlayColor.toLive ? this.overlayColor.toLive(ctx) : this.overlayColor; + ctx.fillRect(this.overlayColor.offsetX || 0, this.overlayColor.offsetY || 0, this.width, this.height); + } + if (this.overlayImage) { + this.overlayImage.render(ctx); + } + }, + renderTop: function() { + var ctx = this.contextTop || this.contextContainer; + this.clearContext(ctx); + if (this.selection && this._groupSelector) { + this._drawSelection(); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.render(ctx); + } + this._renderOverlay(ctx); + this.fire("after:render"); + return this; + }, + getCenter: function() { + return { + top: this.getHeight() / 2, + left: this.getWidth() / 2 + }; + }, + centerObjectH: function(object) { + this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); + this.renderAll(); + return this; + }, + centerObjectV: function(object) { + this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); + this.renderAll(); + return this; + }, + centerObject: function(object) { + var center = this.getCenter(); + this._centerObject(object, new fabric.Point(center.left, center.top)); + this.renderAll(); + return this; + }, + _centerObject: function(object, center) { + object.setPositionByOrigin(center, "center", "center"); + return this; + }, + toDatalessJSON: function(propertiesToInclude) { + return this.toDatalessObject(propertiesToInclude); + }, + toObject: function(propertiesToInclude) { + return this._toObjectMethod("toObject", propertiesToInclude); + }, + toDatalessObject: function(propertiesToInclude) { + return this._toObjectMethod("toDatalessObject", propertiesToInclude); + }, + _toObjectMethod: function(methodName, propertiesToInclude) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this.discardActiveGroup(); + } + var data = { + objects: this._toObjects(methodName, propertiesToInclude) + }; + extend(data, this.__serializeBgOverlay()); + fabric.util.populateWithProperties(this, data, propertiesToInclude); + if (activeGroup) { + this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { + originX: "center", + originY: "center" + })); + activeGroup.forEachObject(function(o) { + o.set("active", true); + }); + if (this._currentTransform) { + this._currentTransform.target = this.getActiveGroup(); + } + } + return data; + }, + _toObjects: function(methodName, propertiesToInclude) { + return this.getObjects().map(function(instance) { + return this._toObject(instance, methodName, propertiesToInclude); + }, this); + }, + _toObject: function(instance, methodName, propertiesToInclude) { + var originalValue; + if (!this.includeDefaultValues) { + originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + var object = instance[methodName](propertiesToInclude); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + return object; + }, + __serializeBgOverlay: function() { + var data = { + background: this.backgroundColor && this.backgroundColor.toObject ? this.backgroundColor.toObject() : this.backgroundColor + }; + if (this.overlayColor) { + data.overlay = this.overlayColor.toObject ? this.overlayColor.toObject() : this.overlayColor; + } + if (this.backgroundImage) { + data.backgroundImage = this.backgroundImage.toObject(); + } + if (this.overlayImage) { + data.overlayImage = this.overlayImage.toObject(); + } + return data; + }, + toSVG: function(options, reviver) { + options || (options = {}); + var markup = []; + this._setSVGPreamble(markup, options); + this._setSVGHeader(markup, options); + this._setSVGBgOverlayColor(markup, "backgroundColor"); + this._setSVGBgOverlayImage(markup, "backgroundImage"); + this._setSVGObjects(markup, reviver); + this._setSVGBgOverlayColor(markup, "overlayColor"); + this._setSVGBgOverlayImage(markup, "overlayImage"); + markup.push(""); + return markup.join(""); + }, + _setSVGPreamble: function(markup, options) { + if (!options.suppressPreamble) { + markup.push('', '\n'); + } + }, + _setSVGHeader: function(markup, options) { + markup.push("', "Created with Fabric.js ", fabric.version, "", "", fabric.createSVGFontFacesMarkup(this.getObjects()), fabric.createSVGRefElementsMarkup(this), ""); + }, + _setSVGObjects: function(markup, reviver) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this.discardActiveGroup(); + } + for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + if (activeGroup) { + this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); + activeGroup.forEachObject(function(o) { + o.set("active", true); + }); + } + }, + _setSVGBgOverlayImage: function(markup, property) { + if (this[property] && this[property].toSVG) { + markup.push(this[property].toSVG()); + } + }, + _setSVGBgOverlayColor: function(markup, property) { + if (this[property] && this[property].source) { + markup.push('"); + } else if (this[property] && property === "overlayColor") { + markup.push('"); + } + }, + sendToBack: function(object) { + removeFromArray(this._objects, object); + this._objects.unshift(object); + return this.renderAll && this.renderAll(); + }, + bringToFront: function(object) { + removeFromArray(this._objects, object); + this._objects.push(object); + return this.renderAll && this.renderAll(); + }, + sendBackwards: function(object, intersecting) { + var idx = this._objects.indexOf(object); + if (idx !== 0) { + var newIdx = this._findNewLowerIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx; + if (intersecting) { + newIdx = idx; + for (var i = idx - 1; i >= 0; --i) { + var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); + if (isIntersecting) { + newIdx = i; + break; + } + } + } else { + newIdx = idx - 1; + } + return newIdx; + }, + bringForward: function(object, intersecting) { + var idx = this._objects.indexOf(object); + if (idx !== this._objects.length - 1) { + var newIdx = this._findNewUpperIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx; + if (intersecting) { + newIdx = idx; + for (var i = idx + 1; i < this._objects.length; ++i) { + var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); + if (isIntersecting) { + newIdx = i; + break; + } + } + } else { + newIdx = idx + 1; + } + return newIdx; + }, + moveTo: function(object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderAll && this.renderAll(); + }, + dispose: function() { + this.clear(); + this.interactive && this.removeListeners(); + return this; + }, + toString: function() { + return "#"; } - else { - this._set(key, value); + }); + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + extend(fabric.StaticCanvas, { + EMPTY_JSON: '{"objects": [], "background": "white"}', + supports: function(methodName) { + var el = fabric.util.createCanvasElement(); + if (!el || !el.getContext) { + return null; + } + var ctx = el.getContext("2d"); + if (!ctx) { + return null; + } + switch (methodName) { + case "getImageData": + return typeof ctx.getImageData !== "undefined"; + + case "setLineDash": + return typeof ctx.setLineDash !== "undefined"; + + case "toDataURL": + return typeof el.toDataURL !== "undefined"; + + case "toDataURLWithQuality": + try { + el.toDataURL("image/jpeg", 0); + return true; + } catch (e) {} + return false; + + default: + return null; + } } - } - return this; + }); + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; +})(); + +fabric.BaseBrush = fabric.util.createClass({ + color: "rgb(0, 0, 0)", + width: 1, + shadow: null, + strokeLineCap: "round", + strokeLineJoin: "round", + setShadow: function(options) { + this.shadow = new fabric.Shadow(options); + return this; }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Object} thisArg - */ - _set: function(key, value) { - var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); - - if (shouldConstrainValue) { - value = this._constrainScale(value); - } - if (key === 'scaleX' && value < 0) { - this.flipX = !this.flipX; - value *= -1; - } - else if (key === 'scaleY' && value < 0) { - this.flipY = !this.flipY; - value *= -1; - } - else if (key === 'width' || key === 'height') { - this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); - } - else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { - value = new fabric.Shadow(value); - } - - this[key] = value; - - return this; - }, - - /** - * Toggles specified property from `true` to `false` or from `false` to `true` - * @param {String} property Property to toggle - * @return {fabric.Object} thisArg - * @chainable - */ - toggle: function(property) { - var value = this.get(property); - if (typeof value === 'boolean') { - this.set(property, !value); - } - return this; - }, - - /** - * Sets sourcePath of an object - * @param {String} value Value to set sourcePath to - * @return {fabric.Object} thisArg - * @chainable - */ - setSourcePath: function(value) { - this.sourcePath = value; - return this; - }, - - /** - * Retrieves viewportTransform from Object's canvas if possible - * @method getViewportTransform - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value // TODO - */ - getViewportTransform: function() { - if (this.canvas && this.canvas.viewportTransform) - return this.canvas.viewportTransform; - return [1, 0, 0, 1, 0, 0]; - }, - - /** - * Renders an object on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if width/height are zeros or object is not visible - if (this.width === 0 || this.height === 0 || !this.visible) return; - - ctx.save(); - - //setup fill rule for current object - this._setupFillRule(ctx); - - this._transform(ctx, noTransform); - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - - var m = this.transformMatrix; - if (m && this.group) { - ctx.translate(-this.group.width/2, -this.group.height/2); - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx, noTransform); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - this._restoreFillRule(ctx); - - ctx.restore(); - }, - - _transform: function(ctx, noTransform) { - var m = this.transformMatrix; - - if (m && !this.group) { - ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - }, - - _setStrokeStyles: function(ctx) { - if (this.stroke) { - ctx.lineWidth = this.strokeWidth; + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; - ctx.miterLimit = this.strokeMiterLimit; - ctx.strokeStyle = this.stroke.toLive - ? this.stroke.toLive(ctx) - : this.stroke; - } }, - - _setFillStyles: function(ctx) { - if (this.fill) { - ctx.fillStyle = this.fill.toLive - ? this.fill.toLive(ctx) - : this.fill; - } + _setShadow: function() { + if (!this.shadow) return; + var ctx = this.canvas.contextTop; + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; }, + _resetShadow: function() { + var ctx = this.canvas.contextTop; + ctx.shadowColor = ""; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + } +}); - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - var v = this.getViewportTransform(); - - ctx.save(); - if (this.active && !noTransform) { - var center; - if (this.group) { - center = fabric.util.transformPoint(this.group.getCenterPoint(), v); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.group.angle)); +(function() { + var utilMin = fabric.util.array.min, utilMax = fabric.util.array.max; + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, { + initialize: function(canvas) { + this.canvas = canvas; + this._points = []; + }, + onMouseDown: function(pointer) { + this._prepareForDrawing(pointer); + this._captureDrawingPath(pointer); + this._render(); + }, + onMouseMove: function(pointer) { + this._captureDrawingPath(pointer); + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + }, + onMouseUp: function() { + this._finalizeAndAddPath(); + }, + _prepareForDrawing: function(pointer) { + var p = new fabric.Point(pointer.x, pointer.y); + this._reset(); + this._addPoint(p); + this.canvas.contextTop.moveTo(p.x, p.y); + }, + _addPoint: function(point) { + this._points.push(point); + }, + _reset: function() { + this._points.length = 0; + this._setBrushStyles(); + this._setShadow(); + }, + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + this._addPoint(pointerPoint); + }, + _render: function() { + var ctx = this.canvas.contextTop; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.beginPath(); + var p1 = this._points[0], p2 = this._points[1]; + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + p1.x -= .5; + p2.x += .5; + } + ctx.moveTo(p1.x, p1.y); + for (var i = 1, len = this._points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + _getSVGPathData: function() { + this.box = this.getPathBoundingBox(this._points); + return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy); + }, + getPathBoundingBox: function(points) { + var xBounds = [], yBounds = [], p1 = points[0], p2 = points[1], startPoint = p1; + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + xBounds.push(startPoint.x); + xBounds.push(midPoint.x); + yBounds.push(startPoint.y); + yBounds.push(midPoint.y); + p1 = points[i]; + p2 = points[i + 1]; + startPoint = midPoint; + } + xBounds.push(p1.x); + yBounds.push(p1.y); + return { + minx: utilMin(xBounds), + miny: utilMin(yBounds), + maxx: utilMax(xBounds), + maxy: utilMax(yBounds) + }; + }, + convertPointsToSVGPath: function(points, minX, maxX, minY) { + var path = [], p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + path.push("M ", points[0].x - minX, " ", points[0].y - minY, " "); + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + path.push("Q ", p1.x, " ", p1.y, " ", midPoint.x, " ", midPoint.y, " "); + p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); + if (i + 1 < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); + } + } + path.push("L ", p1.x, " ", p1.y, " "); + return path; + }, + createPath: function(pathData) { + var path = new fabric.Path(pathData); + path.fill = null; + path.stroke = this.color; + path.strokeWidth = this.width; + path.strokeLineCap = this.strokeLineCap; + path.strokeLineJoin = this.strokeLineJoin; + if (this.shadow) { + this.shadow.affectStroke = true; + path.setShadow(this.shadow); + } + return path; + }, + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + var pathData = this._getSVGPathData().join(""); + if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") { + this.canvas.renderAll(); + return; + } + var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2, originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2; + this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); + var path = this.createPath(pathData); + path.set({ + left: originLeft, + top: originTop, + originX: "center", + originY: "center" + }); + this.canvas.add(path); + path.setCoords(); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderAll(); + this.canvas.fire("path:created", { + path: path + }); } - center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); - if (this.group) { - center.x *= this.group.scaleX; - center.y *= this.group.scaleY; - } - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - this.drawControls(ctx); - } - ctx.restore(); + }); +})(); + +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, { + width: 10, + initialize: function(canvas) { + this.canvas = canvas; + this.points = []; }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setShadow: function(ctx) { - if (!this.shadow) return; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _removeShadow: function(ctx) { - if (!this.shadow) return; - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderFill: function(ctx) { - if (!this.fill) return; - - if (this.fill.toLive) { + drawDot: function(pointer) { + var point = this.addPoint(pointer), ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; ctx.save(); - ctx.translate( - -this.width / 2 + this.fill.offsetX || 0, - -this.height / 2 + this.fill.offsetY || 0); - } - if (this.fillRule === 'destination-over') { - ctx.fill('evenodd'); - } - else { - ctx.fill(); - } - 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) return; - - ctx.save(); - 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 (supportsLineDash) { - 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); - ctx.restore(); - }, - - /** - * Clones an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {fabric.Object} clone of an instance - */ - clone: function(callback, propertiesToInclude) { - if (this.constructor.fromObject) { - return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - } - return new fabric.Object(this.toObject(propertiesToInclude)); - }, - - /** - * Creates an instance of fabric.Image out of an object - * @param callback {Function} callback, invoked with an instance as a first argument - * @return {fabric.Object} thisArg - */ - cloneAsImage: function(callback) { - var dataUrl = this.toDataURL(); - fabric.util.loadImage(dataUrl, function(img) { - if (callback) { - callback(new fabric.Image(img)); - } - }); - return this; - }, - - /** - * Converts an object into a data-url-like string - * @param {Object} options Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - */ - toDataURL: function(options) { - options || (options = { }); - - var el = fabric.util.createCanvasElement(), - boundingRect = this.getBoundingRect(); - - el.width = boundingRect.width; - el.height = boundingRect.height; - - fabric.util.wrapElement(el, 'div'); - var canvas = new fabric.Canvas(el); - - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (options.format === 'jpg') { - options.format = 'jpeg'; - } - - if (options.format === 'jpeg') { - canvas.backgroundColor = '#fff'; - } - - var origParams = { - active: this.get('active'), - left: this.getLeft(), - top: this.getTop() - }; - - this.set('active', false); - this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); - - var originalCanvas = this.canvas; - canvas.add(this); - var data = canvas.toDataURL(options); - - this.set(origParams).setCoords(); - this.canvas = originalCanvas; - - canvas.dispose(); - canvas = null; - - return data; - }, - - /** - * Returns true if specified type is identical to the type of an instance - * @param type {String} type Type to check against - * @return {Boolean} - */ - isType: function(type) { - return this.type === type; - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 0; - }, - - /** - * Returns a JSON representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} JSON - */ - toJSON: function(propertiesToInclude) { - // delegate, not alias - return this.toObject(propertiesToInclude); - }, - - /** - * Sets gradient (fill or stroke) of an object - * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 - * @param {String} property Property name 'stroke' or 'fill' - * @param {Object} [options] Options object - * @param {String} [options.type] Type of gradient 'radial' or 'linear' - * @param {Number} [options.x1=0] x-coordinate of start point - * @param {Number} [options.y1=0] y-coordinate of start point - * @param {Number} [options.x2=0] x-coordinate of end point - * @param {Number} [options.y2=0] y-coordinate of end point - * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) - * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) - * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} - * @example Set linear gradient - * object.setGradient('fill', { - * type: 'linear', - * x1: -object.width / 2, - * y1: 0, - * x2: object.width / 2, - * y2: 0, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - * @example Set radial gradient - * object.setGradient('fill', { - * type: 'radial', - * x1: 0, - * y1: 0, - * x2: 0, - * y2: 0, - * r1: object.width / 2, - * r2: 10, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - */ - setGradient: function(property, options) { - options || (options = { }); - - var gradient = { colorStops: [] }; - - gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); - gradient.coords = { - x1: options.x1, - y1: options.y1, - x2: options.x2, - y2: options.y2 - }; - - if (options.r1 || options.r2) { - gradient.coords.r1 = options.r1; - gradient.coords.r2 = options.r2; - } - - for (var position in options.colorStops) { - var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - - return this.set(property, fabric.Gradient.forObject(this, gradient)); - }, - - /** - * Sets pattern fill of an object - * @param {Object} options Options object - * @param {(String|HTMLImageElement)} options.source Pattern source - * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner - * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} - * @example Set pattern - * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { - * object.setPatternFill({ - * source: img, - * repeat: 'repeat' - * }); - * canvas.renderAll(); - * }); - */ - setPatternFill: function(options) { - return this.set('fill', new fabric.Pattern(options)); - }, - - /** - * Sets {@link fabric.Object#shadow|shadow} of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @param {String} [options.color=rgb(0,0,0)] Shadow color - * @param {Number} [options.blur=0] Shadow blur - * @param {Number} [options.offsetX=0] Shadow horizontal offset - * @param {Number} [options.offsetY=0] Shadow vertical offset - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} - * @example Set shadow with string notation - * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); - * canvas.renderAll(); - * @example Set shadow with object notation - * object.setShadow({ - * color: 'red', - * blur: 10, - * offsetX: 20, - * offsetY: 20 - * }); - * canvas.renderAll(); - */ - setShadow: function(options) { - return this.set('shadow', new fabric.Shadow(options)); - }, - - /** - * Sets "color" of an instance (alias of `set('fill', …)`) - * @param {String} color Color value - * @return {fabric.Object} thisArg - * @chainable - */ - setColor: function(color) { - this.set('fill', color); - return this; - }, - - /** - * Sets "angle" of an instance - * @param {Number} angle Angle value - * @return {fabric.Object} thisArg - * @chainable - */ - setAngle: function(angle) { - var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; - - if (shouldCenterOrigin) { - this._setOriginToCenter(); - } - - this.set('angle', angle); - - if (shouldCenterOrigin) { - this._resetOrigin(); - } - - return this; - }, - - /** - * Centers object horizontally on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerH: function () { - this.canvas.centerObjectH(this); - return this; - }, - - /** - * Centers object vertically on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerV: function () { - this.canvas.centerObjectV(this); - return this; - }, - - /** - * Centers object vertically and horizontally on canvas to which is was added last - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - center: function () { - this.canvas.centerObject(this); - return this; - }, - - /** - * Removes object from canvas to which it was added last - * @return {fabric.Object} thisArg - * @chainable - */ - remove: function() { - this.canvas.remove(this); - return this; - }, - - /** - * Returns coordinates of a pointer relative to an object - * @param {Event} e Event to operate upon - * @param {Object} [pointer] Pointer to operate upon (instead of event) - * @return {Object} Coordinates of a pointer (x, y) - */ - getLocalPointer: function(e, pointer) { - pointer = pointer || this.canvas.getPointer(e); - var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); - return { - x: pointer.x - objectLeftTop.x, - y: pointer.y - objectLeftTop.y - }; - }, - - /** - * Sets canvas globalCompositeOperation for specific object - * custom composition operation for the particular object can be specifed using fillRule property - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _setupFillRule: function (ctx) { - if (this.fillRule) { - this._prevFillRule = ctx.globalCompositeOperation; - ctx.globalCompositeOperation = this.fillRule; - } - }, - - /** - * Restores previously saved canvas globalCompositeOperation after obeject rendering - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _restoreFillRule: function (ctx) { - if (this.fillRule && this._prevFillRule) { - ctx.globalCompositeOperation = this._prevFillRule; - } - } - }); - - fabric.util.createAccessors(fabric.Object); - - /** - * Alias for {@link fabric.Object.prototype.setAngle} - * @alias rotate -> setAngle - * @memberof fabric.Object - */ - fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; - - extend(fabric.Object.prototype, fabric.Observable); - - /** - * Defines the number of fraction digits to use when serializing object values. - * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. - * @static - * @memberof fabric.Object - * @constant - * @type Number - */ - fabric.Object.NUM_FRACTION_DIGITS = 2; - - /** - * Unique id used internally when creating SVG elements - * @static - * @memberof fabric.Object - * @type Number - */ - fabric.Object.__uid = 0; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToCenterPoint: function(point, originX, originY) { - var cx = point.x, - cy = point.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - if (originX === 'left') { - cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - - if (originY === 'top') { - cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the reverse rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); - }, - - /** - * Translates the coordinates from center to origin coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to center of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToOriginPoint: function(center, originX, originY) { - var x = center.x, - y = center.y, - strokeWidth = this.stroke ? this.strokeWidth : 0; - - // Get the point coordinates - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - - // Apply the rotation to the point (it's already scaled properly) - return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); - }, - - /** - * Returns the real center coordinates of the object - * @return {fabric.Point} - */ - getCenterPoint: function() { - var leftTop = new fabric.Point(this.left, this.top); - return this.translateToCenterPoint(leftTop, this.originX, this.originY); - }, - - /** - * Returns the coordinates of the object based on center coordinates - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @return {fabric.Point} - */ - // getOriginPoint: function(center) { - // return this.translateToOriginPoint(center, this.originX, this.originY); - // }, - - /** - * Returns the coordinates of the object as if it has a different origin - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); - return this.translateToOriginPoint(center, originX, originY); - }, - - /** - * Returns the point in local coordinates - * @param {fabric.Point} point The point relative to the global coordinate system - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - toLocalPoint: function(point, originX, originY) { - var center = this.getCenterPoint(), - strokeWidth = this.stroke ? this.strokeWidth : 0, - x, y; - - if (originX && originY) { - if (originX === 'left') { - x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else if (originX === 'right') { - x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; - } - else { - x = center.x; - } - - if (originY === 'top') { - y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else if (originY === 'bottom') { - y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; - } - else { - y = center.y; - } - } - else { - x = this.left; - y = this.top; - } - - return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) - .subtractEquals(new fabric.Point(x, y)); - }, - - /** - * Returns the point in global coordinates - * @param {fabric.Point} The point relative to the local coordinate system - * @return {fabric.Point} - */ - // toGlobalPoint: function(point) { - // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); - // }, - - /** - * Sets the position of the object taking into consideration the object's origin - * @param {fabric.Point} point The new position of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {void} - */ - setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY), - position = this.translateToOriginPoint(center, this.originX, this.originY); - - this.set('left', position.x); - this.set('top', position.y); - }, - - /** - * @param {String} to One of 'left', 'center', 'right' - */ - adjustPosition: function(to) { - var angle = degreesToRadians(this.angle), - hypotHalf = this.getWidth() / 2, - xHalf = Math.cos(angle) * hypotHalf, - yHalf = Math.sin(angle) * hypotHalf, - hypotFull = this.getWidth(), - xFull = Math.cos(angle) * hypotFull, - yFull = Math.sin(angle) * hypotFull; - - if (this.originX === 'center' && to === 'left' || - this.originX === 'right' && to === 'center') { - // move half left - this.left -= xHalf; - this.top -= yHalf; - } - else if (this.originX === 'left' && to === 'center' || - this.originX === 'center' && to === 'right') { - // move half right - this.left += xHalf; - this.top += yHalf; - } - else if (this.originX === 'left' && to === 'right') { - // move full right - this.left += xFull; - this.top += yFull; - } - else if (this.originX === 'right' && to === 'left') { - // move full left - this.left -= xFull; - this.top -= yFull; - } - - this.setCoords(); - this.originX = to; - }, - - /** - * @private - * Sets the origin/position of the object to it's center point - * @return {void} - */ - _setOriginToCenter: function() { - this._originalOriginX = this.originX; - this._originalOriginY = this.originY; - - var center = this.getCenterPoint(); - - this.originX = 'center'; - this.originY = 'center'; - - this.left = center.x; - this.top = center.y; - }, - - /** - * @private - * Resets the origin/position of the object to it's original origin - * @return {void} - */ - _resetOrigin: function() { - var originPoint = this.translateToOriginPoint( - this.getCenterPoint(), - this._originalOriginX, - this._originalOriginY); - - this.originX = this._originalOriginX; - this.originY = this._originalOriginY; - - this.left = originPoint.x; - this.top = originPoint.y; - - this._originalOriginX = null; - this._originalOriginY = null; - }, - - /** - * @private - */ - _getLeftTopCoords: function() { - return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); - } - }); - -})(); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Object containing coordinates of object's controls - * @type Object - * @default - */ - oCoords: null, - - /** - * Checks if object intersects with an area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object intersects with an area formed by 2 points - */ - intersectsWithRect: function(pointTL, pointBR) { - var oCoords = this.oCoords, - tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y), - intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object intersects with another object - * @param {Object} other Object to test - * @return {Boolean} true if object intersects with another object - */ - intersectsWithObject: function(other) { - // extracts coords - function getCoords(oCoords) { - return { - tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), - tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), - bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br: new fabric.Point(oCoords.br.x, oCoords.br.y) - }; - } - var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords), - intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); - - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object is fully contained within area of another object - * @param {Object} other Object to test - * @return {Boolean} true if object is fully contained within area of another object - */ - isContainedWithinObject: function(other) { - var boundingRect = other.getBoundingRect(), - point1 = new fabric.Point(boundingRect.left, boundingRect.top), - point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); - - return this.isContainedWithinRect(point1, point2); - }, - - /** - * Checks if object is fully contained within area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object is fully contained within area formed by 2 points - */ - isContainedWithinRect: function(pointTL, pointBR) { - var boundingRect = this.getBoundingRect(); - - return ( - boundingRect.left >= pointTL.x && - boundingRect.left + boundingRect.width <= pointBR.x && - boundingRect.top >= pointTL.y && - boundingRect.top + boundingRect.height <= pointBR.y - ); - }, - - /** - * Checks if point is inside the object - * @param {fabric.Point} point Point to check against - * @return {Boolean} true if point is inside the object - */ - containsPoint: function(point) { - var lines = this._getImageLines(this.oCoords), - xPoints = this._findCrossPoints(point, lines); - - // if xPoints is odd then point is inside the object - return (xPoints !== 0 && xPoints % 2 === 1); - }, - - /** - * Method that returns an object with the object edges in it, given the coordinates of the corners - * @private - * @param {Object} oCoords Coordinates of the object corners - */ - _getImageLines: function(oCoords) { - return { - topline: { - o: oCoords.tl, - d: oCoords.tr - }, - rightline: { - o: oCoords.tr, - d: oCoords.br - }, - bottomline: { - o: oCoords.br, - d: oCoords.bl - }, - leftline: { - o: oCoords.bl, - d: oCoords.tl - } - }; - }, - - /** - * Helper method to determine how many cross points are between the 4 object edges - * and the horizontal line determined by a point on canvas - * @private - * @param {fabric.Point} point Point to check - * @param {Object} oCoords Coordinates of the object being evaluated - */ - _findCrossPoints: function(point, oCoords) { - var b1, b2, a1, a2, xi, yi, - xcount = 0, - iLine; - - for (var lineKey in oCoords) { - iLine = oCoords[lineKey]; - // optimisation 1: line below point. no cross - if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { - continue; - } - // optimisation 2: line above point. no cross - if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { - continue; - } - // optimisation 3: vertical line case - if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { - xi = iLine.o.x; - yi = point.y; - } - // calculate the intersection point - else { - b1 = 0; - b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y - b1 * point.x; - a2 = iLine.o.y - b2 * iLine.o.x; - - xi = - (a1 - a2) / (b1 - b2); - yi = a1 + b1 * xi; - } - // dont count xi < point.x cases - if (xi >= point.x) { - xcount += 1; - } - // optimisation 4: specific for square images - if (xcount === 2) { - break; - } - } - return xcount; - }, - - /** - * Returns width of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} width value - */ - getBoundingRectWidth: function() { - return this.getBoundingRect().width; - }, - - /** - * Returns height of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} height value - */ - getBoundingRectHeight: function() { - return this.getBoundingRect().height; - }, - - /** - * Returns coordinates of object's bounding rectangle (left, top, width, height) - * @return {Object} Object with left, top, width, height properties - */ - getBoundingRect: function() { - this.oCoords || this.setCoords(); - - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], - minX = fabric.util.array.min(xCoords), - maxX = fabric.util.array.max(xCoords), - width = Math.abs(minX - maxX), - - yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], - minY = fabric.util.array.min(yCoords), - maxY = fabric.util.array.max(yCoords), - height = Math.abs(minY - maxY); - - return { - left: minX, - top: minY, - width: width, - height: height - }; - }, - - /** - * Returns width of an object - * @return {Number} width value - */ - getWidth: function() { - return this.width * this.scaleX; - }, - - /** - * Returns height of an object - * @return {Number} height value - */ - getHeight: function() { - return this.height * this.scaleY; - }, - - /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @param {Number} value - * @return {Number} - */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) { - return -this.minScaleLimit; - } - else { - return this.minScaleLimit; - } - } - return value; - }, - - /** - * Scales an object (equally by x and y) - * @param value {Number} scale factor - * @return {fabric.Object} thisArg - * @chainable - */ - scale: function(value) { - value = this._constrainScale(value); - - if (value < 0) { - this.flipX = !this.flipX; - this.flipY = !this.flipY; - value *= -1; - } - - this.scaleX = value; - this.scaleY = value; - this.setCoords(); - return this; - }, - - /** - * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) - * @param value {Number} new width value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToWidth: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); - return this.scale(value / this.width / boundingRectFactor); - }, - - /** - * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) - * @param value {Number} new height value - * @return {fabric.Object} thisArg - * @chainable - */ - scaleToHeight: function(value) { - // adjust to bounding rect factor so that rotated shapes would fit as well - var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); - return this.scale(value / this.height / boundingRectFactor); - }, - - /** - * Sets corner position coordinates based on current angle, width and height - * @return {fabric.Object} thisArg - * @chainable - */ - setCoords: function() { - - var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - theta = degreesToRadians(this.angle), - vpt = this.getViewportTransform(); - - var f = function (p) { - return fabric.util.transformPoint(p, vpt); - }; - - this.currentWidth = (this.width + strokeWidth) * this.scaleX; - this.currentHeight = (this.height + strokeWidth) * this.scaleY; - - // If width is negative, make postive. Fixes path selection issue - if (this.currentWidth < 0) { - this.currentWidth = Math.abs(this.currentWidth); - } - - var _hypotenuse = Math.sqrt( - Math.pow(this.currentWidth / 2, 2) + - Math.pow(this.currentHeight / 2, 2)), - - _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), - - // offset added for rotate and scale actions - offsetX = Math.cos(_angle + theta) * _hypotenuse, - offsetY = Math.sin(_angle + theta) * _hypotenuse, - sinTh = Math.sin(theta), - cosTh = Math.cos(theta), - coords = this.getCenterPoint(), - wh = new fabric.Point(this.currentWidth, this.currentHeight), - _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), - _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), - _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), - _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), - tl = f(_tl), - tr = f(_tr), - br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), - bl = f(_bl), - ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), - mt = f(_mt), - mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), - mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), - mtr = f(new fabric.Point(_mt.x, _mt.y)); - - // padding - var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), - padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); - tl = tl.add(new fabric.Point(-padX, -padY)); - tr = tr.add(new fabric.Point(padY, -padX)); - br = br.add(new fabric.Point(padX, padY)); - bl = bl.add(new fabric.Point(-padY, padX)); - ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); - mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); - mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); - mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); - mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); - - // debugging - - // setTimeout(function() { - // canvas.contextTop.fillStyle = 'green'; - // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); - // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); - // canvas.contextTop.fillRect(br.x, br.y, 3, 3); - // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); - // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); - // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); - // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); - // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); - // }, 50); - - this.oCoords = { - // corners - tl: tl, tr: tr, br: br, bl: bl, - // middle - ml: ml, mt: mt, mr: mr, mb: mb, - // rotating point - mtr: mtr - }; - - // set coordinates of the draggable boxes in the corners used to scale/rotate the image - this._setCornerCoords && this._setCornerCoords(); - - return this; - } - }); -})(); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Moves an object to the bottom of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - sendToBack: function() { - if (this.group) { - fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); - } - else { - this.canvas.sendToBack(this); - } - return this; - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - bringToFront: function() { - if (this.group) { - fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); - } - else { - this.canvas.bringToFront(this); - } - return this; - }, - - /** - * Moves an object down in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - sendBackwards: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); - } - else { - this.canvas.sendBackwards(this, intersecting); - } - return this; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - bringForward: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); - } - else { - this.canvas.bringForward(this, intersecting); - } - return this; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {Number} index New position of object - * @return {fabric.Object} thisArg - * @chainable - */ - moveTo: function(index) { - if (this.group) { - fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); - } - else { - this.canvas.moveTo(this, index); - } - return this; - } -}); - - -/* _TO_SVG_START_ */ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns styles-string for svg-export - * @return {String} - */ - getSvgStyles: function() { - - var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none', - - stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none', - - strokeWidth = this.strokeWidth ? this.strokeWidth : '0', - strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', - strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', - strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', - strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', - opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - - visibility = this.visible ? '' : ' visibility: hidden;', - filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; - - return [ - 'stroke: ', stroke, '; ', - 'stroke-width: ', strokeWidth, '; ', - 'stroke-dasharray: ', strokeDashArray, '; ', - 'stroke-linecap: ', strokeLineCap, '; ', - 'stroke-linejoin: ', strokeLineJoin, '; ', - 'stroke-miterlimit: ', strokeMiterLimit, '; ', - 'fill: ', fill, '; ', - 'opacity: ', opacity, ';', - filter, - visibility - ].join(''); - }, - - /** - * Returns transform-string for svg-export - * @return {String} - */ - getSvgTransform: function() { - var toFixed = fabric.util.toFixed, - angle = this.getAngle(), - center = this.getCenterPoint(), - - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - translatePart = 'translate(' + - toFixed(center.x, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(center.y, NUM_FRACTION_DIGITS) + - ')', - - anglePart = angle !== 0 - ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') - : '', - - scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (' scale(' + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ')'), - - flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', - - flipYPart = this.flipY ? 'matrix(1 0 0 -1 0 0)' : ''; - - return [ - translatePart, anglePart, scalePart, flipXPart, flipYPart - ].join(''); - }, - - /** - * @private - */ - _createBaseSVGMarkup: function() { - 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)); - } - if (this.shadow) { - markup.push(this.shadow.toSVG(this)); - } - return markup; - } -}); -/* _TO_SVG_END_ */ - - -/* - Depends on `stateProperties` -*/ -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns true if object state (one of its state properties) was changed - * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called - */ - hasStateChanged: function() { - return this.stateProperties.some(function(prop) { - return this.get(prop) !== this.originalState[prop]; - }, this); - }, - - /** - * Saves state of an object - * @param {Object} [options] Object with additional `stateProperties` array to include when saving state - * @return {fabric.Object} thisArg - */ - saveState: function(options) { - this.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - - if (options && options.stateProperties) { - options.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - } - - return this; - }, - - /** - * Setups state of an object - * @return {fabric.Object} thisArg - */ - setupState: function() { - this.originalState = { }; - this.saveState(); - - return this; - } -}); - - -(function(){ - - var degreesToRadians = fabric.util.degreesToRadians, - isVML = typeof G_vmlCanvasManager !== 'undefined'; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * The object interactivity controls. - * @private - */ - _controlsVisibility: null, - - /** - * Determines which corner has been clicked - * @private - * @param {Object} pointer The pointer indicating the mouse position - * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found - */ - _findTargetCorner: function(pointer) { - if (!this.hasControls || !this.active) return false; - - var ex = pointer.x, - ey = pointer.y, - xPoints, - lines; - - for (var i in this.oCoords) { - - if (!this.isControlVisible(i)) { - continue; - } - - if (i === 'mtr' && !this.hasRotatingPoint) { - continue; - } - - if (this.get('lockUniScaling') && - (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { - continue; - } - - lines = this._getImageLines(this.oCoords[i].corner); - - // debugging - - // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); - - xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); - if (xPoints !== 0 && xPoints % 2 === 1) { - this.__corner = i; - return i; - } - } - return false; - }, - - /** - * Sets the coordinates of the draggable boxes in the corners of - * the image used to scale/rotate it. - * @private - */ - _setCornerCoords: function() { - var coords = this.oCoords, - theta = degreesToRadians(this.angle), - newTheta = degreesToRadians(45 - this.angle), - cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, - cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), - sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), - sinTh = Math.sin(theta), - cosTh = Math.cos(theta); - - coords.tl.corner = { - tl: { - x: coords.tl.x - sinHalfOffset, - y: coords.tl.y - cosHalfOffset - }, - tr: { - x: coords.tl.x + cosHalfOffset, - y: coords.tl.y - sinHalfOffset - }, - bl: { - x: coords.tl.x - cosHalfOffset, - y: coords.tl.y + sinHalfOffset - }, - br: { - x: coords.tl.x + sinHalfOffset, - y: coords.tl.y + cosHalfOffset - } - }; - - coords.tr.corner = { - tl: { - x: coords.tr.x - sinHalfOffset, - y: coords.tr.y - cosHalfOffset - }, - tr: { - x: coords.tr.x + cosHalfOffset, - y: coords.tr.y - sinHalfOffset - }, - br: { - x: coords.tr.x + sinHalfOffset, - y: coords.tr.y + cosHalfOffset - }, - bl: { - x: coords.tr.x - cosHalfOffset, - y: coords.tr.y + sinHalfOffset - } - }; - - coords.bl.corner = { - tl: { - x: coords.bl.x - sinHalfOffset, - y: coords.bl.y - cosHalfOffset - }, - bl: { - x: coords.bl.x - cosHalfOffset, - y: coords.bl.y + sinHalfOffset - }, - br: { - x: coords.bl.x + sinHalfOffset, - y: coords.bl.y + cosHalfOffset - }, - tr: { - x: coords.bl.x + cosHalfOffset, - y: coords.bl.y - sinHalfOffset - } - }; - - coords.br.corner = { - tr: { - x: coords.br.x + cosHalfOffset, - y: coords.br.y - sinHalfOffset - }, - bl: { - x: coords.br.x - cosHalfOffset, - y: coords.br.y + sinHalfOffset - }, - br: { - x: coords.br.x + sinHalfOffset, - y: coords.br.y + cosHalfOffset - }, - tl: { - x: coords.br.x - sinHalfOffset, - y: coords.br.y - cosHalfOffset - } - }; - - coords.ml.corner = { - tl: { - x: coords.ml.x - sinHalfOffset, - y: coords.ml.y - cosHalfOffset - }, - tr: { - x: coords.ml.x + cosHalfOffset, - y: coords.ml.y - sinHalfOffset - }, - bl: { - x: coords.ml.x - cosHalfOffset, - y: coords.ml.y + sinHalfOffset - }, - br: { - x: coords.ml.x + sinHalfOffset, - y: coords.ml.y + cosHalfOffset - } - }; - - coords.mt.corner = { - tl: { - x: coords.mt.x - sinHalfOffset, - y: coords.mt.y - cosHalfOffset - }, - tr: { - x: coords.mt.x + cosHalfOffset, - y: coords.mt.y - sinHalfOffset - }, - bl: { - x: coords.mt.x - cosHalfOffset, - y: coords.mt.y + sinHalfOffset - }, - br: { - x: coords.mt.x + sinHalfOffset, - y: coords.mt.y + cosHalfOffset - } - }; - - coords.mr.corner = { - tl: { - x: coords.mr.x - sinHalfOffset, - y: coords.mr.y - cosHalfOffset - }, - tr: { - x: coords.mr.x + cosHalfOffset, - y: coords.mr.y - sinHalfOffset - }, - bl: { - x: coords.mr.x - cosHalfOffset, - y: coords.mr.y + sinHalfOffset - }, - br: { - x: coords.mr.x + sinHalfOffset, - y: coords.mr.y + cosHalfOffset - } - }; - - coords.mb.corner = { - tl: { - x: coords.mb.x - sinHalfOffset, - y: coords.mb.y - cosHalfOffset - }, - tr: { - x: coords.mb.x + cosHalfOffset, - y: coords.mb.y - sinHalfOffset - }, - bl: { - x: coords.mb.x - cosHalfOffset, - y: coords.mb.y + sinHalfOffset - }, - br: { - x: coords.mb.x + sinHalfOffset, - y: coords.mb.y + cosHalfOffset - } - }; - - coords.mtr.corner = { - tl: { - x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset), - y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset) - }, - tr: { - x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset), - y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset) - }, - bl: { - x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset), - y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset) - }, - br: { - x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset), - y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset) - } - }; - }, - /** - * Draws borders of an object's bounding box. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawBorders: function(ctx) { - if (!this.hasBorders) return this; - - var padding = this.padding, - padding2 = padding * 2, - strokeWidth = ~~(this.strokeWidth / 2) * 2; // Round down to even number - - ctx.save(); - - ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; - ctx.strokeStyle = this.borderColor; - - var scaleX = 1 / this._constrainScale(this.scaleX), - scaleY = 1 / this._constrainScale(this.scaleY); - - ctx.lineWidth = 1 / this.borderScaleFactor; - - var vpt = this.getViewportTransform(), - wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), - sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), - w = wh.x, - h = wh.y, - sx= sxy.x, - sy= sxy.y; - if (this.group) { - w = w * this.group.scaleX; - h = h * this.group.scaleY; - } - - ctx.strokeRect( - ~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper - ~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5, - ~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper - ~~(h + padding2 + strokeWidth * sy) + 1 - ); - - if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { - - var rotateHeight = ( - this.flipY - ? h + (strokeWidth * sx) + (padding * 2) - : -h - (strokeWidth * sy) - (padding * 2) - ) / 2; - + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.fillStyle = point.fill; ctx.beginPath(); - ctx.moveTo(0, rotateHeight); - ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset)); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); - ctx.stroke(); - } - - ctx.restore(); - return this; + ctx.fill(); + ctx.restore(); }, - - /** - * Draws corners of an object's bounding box. - * Requires public properties: width, height - * Requires public options: cornerSize, padding - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawControls: function(ctx) { - if (!this.hasControls) return this; - - var size = this.cornerSize, - size2 = size / 2, - strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.getViewportTransform(), true), - width = wh.x, - height = wh.y, - left = -(width / 2), - top = -(height / 2), - padding = this.padding, - scaleOffset = size2, - scaleOffsetSize = size2 - size, - methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; - - ctx.save(); - - ctx.lineWidth = 1; - - ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; - ctx.strokeStyle = ctx.fillStyle = this.cornerColor; - - // top-left - this._drawControl('tl', ctx, methodName, - left - scaleOffset - strokeWidth2 - padding, - top - scaleOffset - strokeWidth2 - padding); - - // top-right - this._drawControl('tr', ctx, methodName, - left + width - scaleOffset + strokeWidth2 + padding, - top - scaleOffset - strokeWidth2 - padding); - - // bottom-left - this._drawControl('bl', ctx, methodName, - left - scaleOffset - strokeWidth2 - padding, - top + height + scaleOffsetSize + strokeWidth2 + padding); - - // bottom-right - this._drawControl('br', ctx, methodName, - left + width + scaleOffsetSize + strokeWidth2 + padding, - top + height + scaleOffsetSize + strokeWidth2 + padding); - - if (!this.get('lockUniScaling')) { - - // middle-top - this._drawControl('mt', ctx, methodName, - left + width/2 - scaleOffset, - top - scaleOffset - strokeWidth2 - padding); - - // middle-bottom - this._drawControl('mb', ctx, methodName, - left + width/2 - scaleOffset, - top + height + scaleOffsetSize + strokeWidth2 + padding); - - // middle-right - this._drawControl('mr', ctx, methodName, - left + width + scaleOffsetSize + strokeWidth2 + padding, - top + height/2 - scaleOffset); - - // middle-left - this._drawControl('ml', ctx, methodName, - left - scaleOffset - strokeWidth2 - padding, - top + height/2 - scaleOffset); - } - - // middle-top-rotate - if (this.hasRotatingPoint) { - this._drawControl('mtr', ctx, methodName, - left + width/2 - scaleOffset, - this.flipY - ? (top + height + this.rotatingPointOffset - this.cornerSize/2 + strokeWidth2 + padding) - : (top - this.rotatingPointOffset - this.cornerSize/2 - strokeWidth2 - padding)); - } - - ctx.restore(); - - return this; + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); }, - - /** - * @private - */ - _drawControl: function(control, ctx, methodName, left, top) { - var size = this.cornerSize; - - if (this.isControlVisible(control)) { - isVML || this.transparentCorners || ctx.clearRect(left, top, size, size); - ctx[methodName](left, top, size, size); - } + onMouseMove: function(pointer) { + this.drawDot(pointer); }, - - /** - * Returns true if the specified control is visible, false otherwise. - * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. - * @returns {Boolean} true if the specified control is visible, false otherwise - */ - isControlVisible: function(controlName) { - return this._getControlsVisibility()[controlName]; + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + var circles = []; + for (var i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: "center", + originY: "center", + fill: point.fill + }); + this.shadow && circle.setShadow(this.shadow); + circles.push(circle); + } + var group = new fabric.Group(circles, { + originX: "center", + originY: "center" + }); + group.canvas = this.canvas; + this.canvas.add(group); + this.canvas.fire("path:created", { + path: group + }); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); }, - - /** - * Sets the visibility of the specified control. - * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. - * @param {Boolean} visible true to set the specified control visible, false otherwise - * @return {fabric.Object} thisArg - * @chainable - */ - setControlVisible: function(controlName, visible) { - this._getControlsVisibility()[controlName] = visible; - return this; - }, - - /** - * Sets the visibility state of object controls. - * @param {Object} [options] Options object - * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it - * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it - * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it - * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it - * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it - * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it - * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it - * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it - * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it - * @return {fabric.Object} thisArg - * @chainable - */ - setControlsVisibility: function(options) { - options || (options = { }); - - for (var p in options) { - this.setControlVisible(p, options[p]); - } - return this; - }, - - /** - * Returns the instance of the control visibility set for this object. - * @private - * @returns {Object} - */ - _getControlsVisibility: function() { - if (!this._controlsVisibility) { - this._controlsVisibility = { - tl: true, - tr: true, - br: true, - bl: true, - ml: true, - mt: true, - mr: true, - mb: true, - mtr: true - }; - } - return this._controlsVisibility; + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt(Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0, 100) / 100).toRgba(); + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + this.points.push(pointerPoint); + return pointerPoint; } - }); +}); + +fabric.SprayBrush = fabric.util.createClass(fabric.BaseBrush, { + width: 10, + density: 20, + dotWidth: 1, + dotWidthVariance: 1, + randomOpacity: false, + optimizeOverlapping: true, + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = []; + }, + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.addSprayChunk(pointer); + this.render(); + }, + onMouseMove: function(pointer) { + this.addSprayChunk(pointer); + this.render(); + }, + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + var rects = []; + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: "center", + originY: "center", + fill: this.color + }); + this.shadow && rect.setShadow(this.shadow); + rects.push(rect); + } + } + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + var group = new fabric.Group(rects, { + originX: "center", + originY: "center" + }); + group.canvas = this.canvas; + this.canvas.add(group); + this.canvas.fire("path:created", { + path: group + }); + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + _getOptimizedRects: function(rects) { + var uniqueRects = {}, key; + for (var i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + "" + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = []; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + return uniqueRectsArray; + }, + render: function() { + var ctx = this.canvas.contextTop; + ctx.fillStyle = this.color; + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { + var point = this.sprayChunkPoints[i]; + if (typeof point.opacity !== "undefined") { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + addSprayChunk: function(pointer) { + this.sprayChunkPoints = []; + var x, y, width, radius = this.width / 2; + for (var i = 0; i < this.density; i++) { + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt(Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance); + } else { + width = this.dotWidth; + } + var point = new fabric.Point(x, y); + point.width = width; + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + this.sprayChunkPoints.push(point); + } + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, { + getPatternSrc: function() { + var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.document.createElement("canvas"), patternCtx = patternCanvas.getContext("2d"); + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + return patternCanvas; + }, + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace("this.color", '"' + this.color + '"'); + }, + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), "repeat"); + }, + _setBrushStyles: function() { + this.callSuper("_setBrushStyles"); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + createPath: function(pathData) { + var path = this.callSuper("createPath", pathData); + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction() + }); + return path; + } +}); + +(function() { + var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, STROKE_OFFSET = .5; + fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, { + initialize: function(el, options) { + options || (options = {}); + this._initStatic(el, options); + this._initInteractive(); + this._createCacheCanvas(); + fabric.Canvas.activeInstance = this; + }, + uniScaleTransform: false, + centeredScaling: false, + centeredRotation: false, + interactive: true, + selection: true, + selectionColor: "rgba(100, 100, 255, 0.3)", + selectionDashArray: [], + selectionBorderColor: "rgba(255, 255, 255, 0.3)", + selectionLineWidth: 1, + hoverCursor: "move", + moveCursor: "move", + defaultCursor: "default", + freeDrawingCursor: "crosshair", + rotationCursor: "crosshair", + containerClass: "canvas-container", + perPixelTargetFind: false, + targetFindTolerance: 0, + skipTargetFind: false, + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEventListeners(); + this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); + this.calcOffset(); + }, + _resetCurrentTransform: function(e) { + var t = this._currentTransform; + t.target.set({ + scaleX: t.original.scaleX, + scaleY: t.original.scaleY, + left: t.original.left, + top: t.original.top + }); + if (this._shouldCenterTransform(e, t.target)) { + if (t.action === "rotate") { + this._setOriginToCenter(t.target); + } else { + if (t.originX !== "center") { + if (t.originX === "right") { + t.mouseXSign = -1; + } else { + t.mouseXSign = 1; + } + } + if (t.originY !== "center") { + if (t.originY === "bottom") { + t.mouseYSign = -1; + } else { + t.mouseYSign = 1; + } + } + t.originX = "center"; + t.originY = "center"; + } + } else { + t.originX = t.original.originX; + t.originY = t.original.originY; + } + }, + containsPoint: function(e, target) { + var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); + return target.containsPoint(xy) || target._findTargetCorner(pointer); + }, + _normalizePointer: function(object, pointer) { + var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y, isObjectInGroup = activeGroup && object.type !== "group" && activeGroup.contains(object), lt; + if (isObjectInGroup) { + lt = new fabric.Point(activeGroup.left, activeGroup.top); + lt = fabric.util.transformPoint(lt, this.viewportTransform, true); + x -= lt.x; + y -= lt.y; + } + return { + x: x, + y: y + }; + }, + isTargetTransparent: function(target, x, y) { + var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners; + target.hasBorders = target.transparentCorners = false; + this._draw(this.contextCache, target); + target.hasBorders = hasBorders; + target.transparentCorners = transparentCorners; + var isTransparent = fabric.util.isTransparent(this.contextCache, x, y, this.targetFindTolerance); + this.clearContext(this.contextCache); + return isTransparent; + }, + _shouldClearSelection: function(e, target) { + var activeGroup = this.getActiveGroup(), activeObject = this.getActiveObject(); + return !target || target && activeGroup && !activeGroup.contains(target) && activeGroup !== target && !e.shiftKey || target && !target.evented || target && !target.selectable && activeObject && activeObject !== target; + }, + _shouldCenterTransform: function(e, target) { + if (!target) return; + var t = this._currentTransform, centerTransform; + if (t.action === "scale" || t.action === "scaleX" || t.action === "scaleY") { + centerTransform = this.centeredScaling || target.centeredScaling; + } else if (t.action === "rotate") { + centerTransform = this.centeredRotation || target.centeredRotation; + } + return centerTransform ? !e.altKey : e.altKey; + }, + _getOriginFromCorner: function(target, corner) { + var origin = { + x: target.originX, + y: target.originY + }; + if (corner === "ml" || corner === "tl" || corner === "bl") { + origin.x = "right"; + } else if (corner === "mr" || corner === "tr" || corner === "br") { + origin.x = "left"; + } + if (corner === "tl" || corner === "mt" || corner === "tr") { + origin.y = "bottom"; + } else if (corner === "bl" || corner === "mb" || corner === "br") { + origin.y = "top"; + } + return origin; + }, + _getActionFromCorner: function(target, corner) { + var action = "drag"; + if (corner) { + action = corner === "ml" || corner === "mr" ? "scaleX" : corner === "mt" || corner === "mb" ? "scaleY" : corner === "mtr" ? "rotate" : "scale"; + } + return action; + }, + _setupCurrentTransform: function(e, target) { + if (!target) return; + var pointer = this.getPointer(e), corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); + this._currentTransform = { + target: target, + action: action, + scaleX: target.scaleX, + scaleY: target.scaleY, + offsetX: pointer.x - target.left, + offsetY: pointer.y - target.top, + originX: origin.x, + originY: origin.y, + ex: pointer.x, + ey: pointer.y, + left: target.left, + top: target.top, + theta: degreesToRadians(target.angle), + width: target.width * target.scaleX, + mouseXSign: 1, + mouseYSign: 1 + }; + this._currentTransform.original = { + left: target.left, + top: target.top, + scaleX: target.scaleX, + scaleY: target.scaleY, + originX: origin.x, + originY: origin.y + }; + this._resetCurrentTransform(e); + }, + _translateObject: function(x, y) { + var target = this._currentTransform.target; + if (!target.get("lockMovementX")) { + target.set("left", x - this._currentTransform.offsetX); + } + if (!target.get("lockMovementY")) { + target.set("top", y - this._currentTransform.offsetY); + } + }, + _scaleObject: function(x, y, by) { + var t = this._currentTransform, target = t.target, lockScalingX = target.get("lockScalingX"), lockScalingY = target.get("lockScalingY"); + if (lockScalingX && lockScalingY) return; + var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); + this._setLocalMouse(localMouse, t); + this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by); + target.setPositionByOrigin(constraintPosition, t.originX, t.originY); + }, + _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by) { + var target = transform.target; + transform.newScaleX = target.scaleX; + transform.newScaleY = target.scaleY; + if (by === "equally" && !lockScalingX && !lockScalingY) { + this._scaleObjectEqually(localMouse, target, transform); + } else if (!by) { + transform.newScaleX = localMouse.x / (target.width + target.strokeWidth); + transform.newScaleY = localMouse.y / (target.height + target.strokeWidth); + lockScalingX || target.set("scaleX", transform.newScaleX); + lockScalingY || target.set("scaleY", transform.newScaleY); + } else if (by === "x" && !target.get("lockUniScaling")) { + transform.newScaleX = localMouse.x / (target.width + target.strokeWidth); + lockScalingX || target.set("scaleX", transform.newScaleX); + } else if (by === "y" && !target.get("lockUniScaling")) { + transform.newScaleY = localMouse.y / (target.height + target.strokeWidth); + lockScalingY || target.set("scaleY", transform.newScaleY); + } + this._flipObject(transform); + }, + _scaleObjectEqually: function(localMouse, target, transform) { + var dist = localMouse.y + localMouse.x, lastDist = (target.height + target.strokeWidth) * transform.original.scaleY + (target.width + target.strokeWidth) * transform.original.scaleX; + transform.newScaleX = transform.original.scaleX * dist / lastDist; + transform.newScaleY = transform.original.scaleY * dist / lastDist; + target.set("scaleX", transform.newScaleX); + target.set("scaleY", transform.newScaleY); + }, + _flipObject: function(transform) { + if (transform.newScaleX < 0) { + if (transform.originX === "left") { + transform.originX = "right"; + } else if (transform.originX === "right") { + transform.originX = "left"; + } + } + if (transform.newScaleY < 0) { + if (transform.originY === "top") { + transform.originY = "bottom"; + } else if (transform.originY === "bottom") { + transform.originY = "top"; + } + } + }, + _setLocalMouse: function(localMouse, t) { + var target = t.target; + if (t.originX === "right") { + localMouse.x *= -1; + } else if (t.originX === "center") { + localMouse.x *= t.mouseXSign * 2; + if (localMouse.x < 0) { + t.mouseXSign = -t.mouseXSign; + } + } + if (t.originY === "bottom") { + localMouse.y *= -1; + } else if (t.originY === "center") { + localMouse.y *= t.mouseYSign * 2; + if (localMouse.y < 0) { + t.mouseYSign = -t.mouseYSign; + } + } + if (abs(localMouse.x) > target.padding) { + if (localMouse.x < 0) { + localMouse.x += target.padding; + } else { + localMouse.x -= target.padding; + } + } else { + localMouse.x = 0; + } + if (abs(localMouse.y) > target.padding) { + if (localMouse.y < 0) { + localMouse.y += target.padding; + } else { + localMouse.y -= target.padding; + } + } else { + localMouse.y = 0; + } + }, + _rotateObject: function(x, y) { + var t = this._currentTransform; + if (t.target.get("lockRotation")) return; + var lastAngle = atan2(t.ey - t.top, t.ex - t.left), curAngle = atan2(y - t.top, x - t.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta); + if (angle < 0) { + angle = 360 + angle; + } + t.target.angle = angle; + }, + _setCursor: function(value) { + this.upperCanvasEl.style.cursor = value; + }, + _resetObjectTransform: function(target) { + target.scaleX = 1; + target.scaleY = 1; + target.setAngle(0); + }, + _drawSelection: function() { + var ctx = this.contextTop, groupSelector = this._groupSelector, left = groupSelector.left, top = groupSelector.top, aleft = abs(left), atop = abs(top); + ctx.fillStyle = this.selectionColor; + ctx.fillRect(groupSelector.ex - (left > 0 ? 0 : -left), groupSelector.ey - (top > 0 ? 0 : -top), aleft, atop); + ctx.lineWidth = this.selectionLineWidth; + ctx.strokeStyle = this.selectionBorderColor; + if (this.selectionDashArray.length > 1) { + var px = groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft), py = groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop); + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); + ctx.closePath(); + ctx.stroke(); + } else { + ctx.strokeRect(groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft), groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop), aleft, atop); + } + }, + _isLastRenderedObject: function(e) { + return this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)); + }, + findTarget: function(e, skipGroup) { + if (this.skipTargetFind) return; + if (this._isLastRenderedObject(e)) { + return this.lastRenderedObjectWithControlsAboveOverlay; + } + var activeGroup = this.getActiveGroup(); + if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + return activeGroup; + } + var target = this._searchPossibleTargets(e); + this._fireOverOutEvents(target); + return target; + }, + _fireOverOutEvents: function(target) { + if (target) { + if (this._hoveredTarget !== target) { + this.fire("mouse:over", { + target: target + }); + target.fire("mouseover"); + if (this._hoveredTarget) { + this.fire("mouse:out", { + target: this._hoveredTarget + }); + this._hoveredTarget.fire("mouseout"); + } + this._hoveredTarget = target; + } + } else if (this._hoveredTarget) { + this.fire("mouse:out", { + target: this._hoveredTarget + }); + this._hoveredTarget.fire("mouseout"); + this._hoveredTarget = null; + } + }, + _checkTarget: function(e, obj, pointer) { + if (obj && obj.visible && obj.evented && this.containsPoint(e, obj)) { + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); + if (!isTransparent) { + return true; + } + } else { + return true; + } + } + }, + _searchPossibleTargets: function(e) { + var target, pointer = this.getPointer(e, true); + var i = this._objects.length; + while (i--) { + if (this._checkTarget(e, this._objects[i], pointer)) { + this.relatedTarget = this._objects[i]; + target = this._objects[i]; + break; + } + } + return target; + }, + getPointer: function(e, ignoreZoom, upperCanvasEl) { + if (!upperCanvasEl) { + upperCanvasEl = this.upperCanvasEl; + } + var pointer = getPointer(e, upperCanvasEl), bounds = upperCanvasEl.getBoundingClientRect(), cssScale; + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; + if (!ignoreZoom) { + pointer = fabric.util.transformPoint(pointer, fabric.util.invertTransform(this.viewportTransform)); + } + if (bounds.width === 0 || bounds.height === 0) { + cssScale = { + width: 1, + height: 1 + }; + } else { + cssScale = { + width: upperCanvasEl.width / bounds.width, + height: upperCanvasEl.height / bounds.height + }; + } + return { + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height + }; + }, + _createUpperCanvas: function() { + var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ""); + this.upperCanvasEl = this._createCanvasElement(); + fabric.util.addClass(this.upperCanvasEl, "upper-canvas " + lowerCanvasClass); + this.wrapperEl.appendChild(this.upperCanvasEl); + this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); + this._applyCanvasStyle(this.upperCanvasEl); + this.contextTop = this.upperCanvasEl.getContext("2d"); + }, + _createCacheCanvas: function() { + this.cacheCanvasEl = this._createCanvasElement(); + this.cacheCanvasEl.setAttribute("width", this.width); + this.cacheCanvasEl.setAttribute("height", this.height); + this.contextCache = this.cacheCanvasEl.getContext("2d"); + }, + _initWrapperElement: function() { + this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", { + "class": this.containerClass + }); + fabric.util.setStyle(this.wrapperEl, { + width: this.getWidth() + "px", + height: this.getHeight() + "px", + position: "relative" + }); + fabric.util.makeElementUnselectable(this.wrapperEl); + }, + _applyCanvasStyle: function(element) { + var width = this.getWidth() || element.width, height = this.getHeight() || element.height; + fabric.util.setStyle(element, { + position: "absolute", + width: width + "px", + height: height + "px", + left: 0, + top: 0 + }); + element.width = width; + element.height = height; + fabric.util.makeElementUnselectable(element); + }, + _copyCanvasStyle: function(fromEl, toEl) { + toEl.style.cssText = fromEl.style.cssText; + }, + getSelectionContext: function() { + return this.contextTop; + }, + getSelectionElement: function() { + return this.upperCanvasEl; + }, + _setActiveObject: function(object) { + if (this._activeObject) { + this._activeObject.set("active", false); + } + this._activeObject = object; + object.set("active", true); + }, + setActiveObject: function(object, e) { + this._setActiveObject(object); + this.renderAll(); + this.fire("object:selected", { + target: object, + e: e + }); + object.fire("selected", { + e: e + }); + return this; + }, + getActiveObject: function() { + return this._activeObject; + }, + _discardActiveObject: function() { + if (this._activeObject) { + this._activeObject.set("active", false); + } + this._activeObject = null; + }, + discardActiveObject: function(e) { + this._discardActiveObject(); + this.renderAll(); + this.fire("selection:cleared", { + e: e + }); + return this; + }, + _setActiveGroup: function(group) { + this._activeGroup = group; + if (group) { + group.canvas = this; + group._calcBounds(); + group._updateObjectsCoords(); + group.setCoords(); + group.set("active", true); + } + }, + setActiveGroup: function(group, e) { + this._setActiveGroup(group); + if (group) { + this.fire("object:selected", { + target: group, + e: e + }); + group.fire("selected", { + e: e + }); + } + return this; + }, + getActiveGroup: function() { + return this._activeGroup; + }, + _discardActiveGroup: function() { + var g = this.getActiveGroup(); + if (g) { + g.destroy(); + } + this.setActiveGroup(null); + }, + discardActiveGroup: function(e) { + this._discardActiveGroup(); + this.fire("selection:cleared", { + e: e + }); + return this; + }, + deactivateAll: function() { + var allObjects = this.getObjects(), i = 0, len = allObjects.length; + for (;i < len; i++) { + allObjects[i].set("active", false); + } + this._discardActiveGroup(); + this._discardActiveObject(); + return this; + }, + deactivateAllWithDispatch: function(e) { + var activeObject = this.getActiveGroup() || this.getActiveObject(); + if (activeObject) { + this.fire("before:selection:cleared", { + target: activeObject, + e: e + }); + } + this.deactivateAll(); + if (activeObject) { + this.fire("selection:cleared", { + e: e + }); + } + return this; + }, + drawControls: function(ctx) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this._drawGroupControls(ctx, activeGroup); + } else { + this._drawObjectsControls(ctx); + } + }, + _drawGroupControls: function(ctx, activeGroup) { + activeGroup._renderControls(ctx); + }, + _drawObjectsControls: function(ctx) { + for (var i = 0, len = this._objects.length; i < len; ++i) { + if (!this._objects[i] || !this._objects[i].active) continue; + this._objects[i]._renderControls(ctx); + this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; + } + } + }); + for (var prop in fabric.StaticCanvas) { + if (prop !== "prototype") { + fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + } + } + if (fabric.isTouchSupported) { + fabric.Canvas.prototype._setCursorFromEvent = function() {}; + } + fabric.Element = fabric.Canvas; })(); - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Animation duration (in ms) for fx* methods - * @type Number - * @default - */ - FX_DURATION: 500, - - /** - * Centers object horizontally with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxCenterObjectH: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('left'), - endValue: this.getCenter().left, - duration: this.FX_DURATION, - onChange: function(value) { - object.set('left', value); - _this.renderAll(); - onChange(); - }, - onComplete: function() { - object.setCoords(); - onComplete(); - } +(function() { + var cursorOffset = { + mt: 0, + tr: 1, + mr: 2, + br: 3, + mb: 4, + bl: 5, + ml: 6, + tl: 7 + }, addListener = fabric.util.addListener, removeListener = fabric.util.removeListener; + fabric.util.object.extend(fabric.Canvas.prototype, { + cursorMap: [ "n-resize", "ne-resize", "e-resize", "se-resize", "s-resize", "sw-resize", "w-resize", "nw-resize" ], + _initEventListeners: function() { + this._bindEvents(); + addListener(fabric.window, "resize", this._onResize); + addListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + addListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + addListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel); + addListener(this.upperCanvasEl, "touchstart", this._onMouseDown); + addListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (typeof Event !== "undefined" && "add" in Event) { + Event.add(this.upperCanvasEl, "gesture", this._onGesture); + Event.add(this.upperCanvasEl, "drag", this._onDrag); + Event.add(this.upperCanvasEl, "orientation", this._onOrientationChange); + Event.add(this.upperCanvasEl, "shake", this._onShake); + } + }, + _bindEvents: function() { + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onResize = this._onResize.bind(this); + this._onGesture = this._onGesture.bind(this); + this._onDrag = this._onDrag.bind(this); + this._onShake = this._onShake.bind(this); + this._onOrientationChange = this._onOrientationChange.bind(this); + this._onMouseWheel = this._onMouseWheel.bind(this); + }, + removeListeners: function() { + removeListener(fabric.window, "resize", this._onResize); + removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + removeListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel); + removeListener(this.upperCanvasEl, "touchstart", this._onMouseDown); + removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (typeof Event !== "undefined" && "remove" in Event) { + Event.remove(this.upperCanvasEl, "gesture", this._onGesture); + Event.remove(this.upperCanvasEl, "drag", this._onDrag); + Event.remove(this.upperCanvasEl, "orientation", this._onOrientationChange); + Event.remove(this.upperCanvasEl, "shake", this._onShake); + } + }, + _onGesture: function(e, s) { + this.__onTransformGesture && this.__onTransformGesture(e, s); + }, + _onDrag: function(e, s) { + this.__onDrag && this.__onDrag(e, s); + }, + _onMouseWheel: function(e, s) { + this.__onMouseWheel && this.__onMouseWheel(e, s); + }, + _onOrientationChange: function(e, s) { + this.__onOrientationChange && this.__onOrientationChange(e, s); + }, + _onShake: function(e, s) { + this.__onShake && this.__onShake(e, s); + }, + _onMouseDown: function(e) { + this.__onMouseDown(e); + addListener(fabric.document, "touchend", this._onMouseUp); + addListener(fabric.document, "touchmove", this._onMouseMove); + removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (e.type === "touchstart") { + removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown); + } else { + addListener(fabric.document, "mouseup", this._onMouseUp); + addListener(fabric.document, "mousemove", this._onMouseMove); + } + }, + _onMouseUp: function(e) { + this.__onMouseUp(e); + removeListener(fabric.document, "mouseup", this._onMouseUp); + removeListener(fabric.document, "touchend", this._onMouseUp); + removeListener(fabric.document, "mousemove", this._onMouseMove); + removeListener(fabric.document, "touchmove", this._onMouseMove); + addListener(this.upperCanvasEl, "mousemove", this._onMouseMove); + addListener(this.upperCanvasEl, "touchmove", this._onMouseMove); + if (e.type === "touchend") { + var _this = this; + setTimeout(function() { + addListener(_this.upperCanvasEl, "mousedown", _this._onMouseDown); + }, 400); + } + }, + _onMouseMove: function(e) { + !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + _onResize: function() { + this.calcOffset(); + }, + _shouldRender: function(target, pointer) { + var activeObject = this.getActiveGroup() || this.getActiveObject(); + return !!(target && (target.isMoving || target !== activeObject) || !target && !!activeObject || !target && !activeObject && !this._groupSelector || pointer && this._previousPointer && this.selection && (pointer.x !== this._previousPointer.x || pointer.y !== this._previousPointer.y)); + }, + __onMouseUp: function(e) { + var target; + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._onMouseUpInDrawingMode(e); + return; + } + if (this._currentTransform) { + this._finalizeCurrentTransform(); + target = this._currentTransform.target; + } else { + target = this.findTarget(e, true); + } + var shouldRender = this._shouldRender(target, this.getPointer(e)); + this._maybeGroupObjects(e); + if (target) { + target.isMoving = false; + } + shouldRender && this.renderAll(); + this._handleCursorAndEvent(e, target); + }, + _handleCursorAndEvent: function(e, target) { + this._setCursorFromEvent(e, target); + var _this = this; + setTimeout(function() { + _this._setCursorFromEvent(e, target); + }, 50); + this.fire("mouse:up", { + target: target, + e: e + }); + target && target.fire("mouseup", { + e: e + }); + }, + _finalizeCurrentTransform: function() { + var transform = this._currentTransform, target = transform.target; + if (target._scaling) { + target._scaling = false; + } + target.setCoords(); + if (this.stateful && target.hasStateChanged()) { + this.fire("object:modified", { + target: target + }); + target.fire("modified"); + } + this._restoreOriginXY(target); + }, + _restoreOriginXY: function(target) { + if (this._previousOriginX && this._previousOriginY) { + var originPoint = target.translateToOriginPoint(target.getCenterPoint(), this._previousOriginX, this._previousOriginY); + target.originX = this._previousOriginX; + target.originY = this._previousOriginY; + target.left = originPoint.x; + target.top = originPoint.y; + this._previousOriginX = null; + this._previousOriginY = null; + } + }, + _onMouseDownInDrawingMode: function(e) { + this._isCurrentlyDrawing = true; + this.discardActiveObject(e).renderAll(); + if (this.clipTo) { + fabric.util.clipContext(this, this.contextTop); + } + var ivt = fabric.util.invertTransform(this.viewportTransform); + var pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseDown(pointer); + this.fire("mouse:down", { + e: e + }); + }, + _onMouseMoveInDrawingMode: function(e) { + if (this._isCurrentlyDrawing) { + var ivt = fabric.util.invertTransform(this.viewportTransform), pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); + this.freeDrawingBrush.onMouseMove(pointer); + } + this.upperCanvasEl.style.cursor = this.freeDrawingCursor; + this.fire("mouse:move", { + e: e + }); + }, + _onMouseUpInDrawingMode: function(e) { + this._isCurrentlyDrawing = false; + if (this.clipTo) { + this.contextTop.restore(); + } + this.freeDrawingBrush.onMouseUp(); + this.fire("mouse:up", { + e: e + }); + }, + __onMouseDown: function(e) { + var isLeftClick = "which" in e ? e.which === 1 : e.button === 1; + if (!isLeftClick && !fabric.isTouchSupported) return; + if (this.isDrawingMode) { + this._onMouseDownInDrawingMode(e); + return; + } + if (this._currentTransform) return; + var target = this.findTarget(e), pointer = this.getPointer(e, true); + this._previousPointer = pointer; + var shouldRender = this._shouldRender(target, pointer), shouldGroup = this._shouldGroup(e, target); + if (this._shouldClearSelection(e, target)) { + this._clearSelection(e, target, pointer); + } else if (shouldGroup) { + this._handleGrouping(e, target); + target = this.getActiveGroup(); + } + if (target && target.selectable && !shouldGroup) { + this._beforeTransform(e, target); + this._setupCurrentTransform(e, target); + } + shouldRender && this.renderAll(); + this.fire("mouse:down", { + target: target, + e: e + }); + target && target.fire("mousedown", { + e: e + }); + }, + _beforeTransform: function(e, target) { + var corner; + this.stateful && target.saveState(); + if (corner = target._findTargetCorner(this.getPointer(e))) { + this.onBeforeScaleRotate(target); + } + if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { + this.deactivateAll(); + this.setActiveObject(target, e); + } + }, + _clearSelection: function(e, target, pointer) { + this.deactivateAllWithDispatch(e); + if (target && target.selectable) { + this.setActiveObject(target, e); + } else if (this.selection) { + this._groupSelector = { + ex: pointer.x, + ey: pointer.y, + top: 0, + left: 0 + }; + } + }, + _setOriginToCenter: function(target) { + this._previousOriginX = this._currentTransform.target.originX; + this._previousOriginY = this._currentTransform.target.originY; + var center = target.getCenterPoint(); + target.originX = "center"; + target.originY = "center"; + target.left = center.x; + target.top = center.y; + this._currentTransform.left = target.left; + this._currentTransform.top = target.top; + }, + _setCenterToOrigin: function(target) { + var originPoint = target.translateToOriginPoint(target.getCenterPoint(), this._previousOriginX, this._previousOriginY); + target.originX = this._previousOriginX; + target.originY = this._previousOriginY; + target.left = originPoint.x; + target.top = originPoint.y; + this._previousOriginX = null; + this._previousOriginY = null; + }, + __onMouseMove: function(e) { + var target, pointer; + if (this.isDrawingMode) { + this._onMouseMoveInDrawingMode(e); + return; + } + var groupSelector = this._groupSelector; + if (groupSelector) { + pointer = this.getPointer(e, true); + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; + this.renderTop(); + } else if (!this._currentTransform) { + target = this.findTarget(e); + if (!target || target && !target.selectable) { + this.upperCanvasEl.style.cursor = this.defaultCursor; + } else { + this._setCursorFromEvent(e, target); + } + } else { + this._transformObject(e); + } + this.fire("mouse:move", { + target: target, + e: e + }); + target && target.fire("mousemove", { + e: e + }); + }, + _transformObject: function(e) { + var pointer = this.getPointer(e), transform = this._currentTransform; + transform.reset = false, transform.target.isMoving = true; + this._beforeScaleTransform(e, transform); + this._performTransformAction(e, transform, pointer); + this.renderAll(); + }, + _performTransformAction: function(e, transform, pointer) { + var x = pointer.x, y = pointer.y, target = transform.target, action = transform.action; + if (action === "rotate") { + this._rotateObject(x, y); + this._fire("rotating", target, e); + } else if (action === "scale") { + this._onScale(e, transform, x, y); + this._fire("scaling", target, e); + } else if (action === "scaleX") { + this._scaleObject(x, y, "x"); + this._fire("scaling", target, e); + } else if (action === "scaleY") { + this._scaleObject(x, y, "y"); + this._fire("scaling", target, e); + } else { + this._translateObject(x, y); + this._fire("moving", target, e); + this._setCursor(this.moveCursor); + } + }, + _fire: function(eventName, target, e) { + this.fire("object:" + eventName, { + target: target, + e: e + }); + target.fire(eventName, { + e: e + }); + }, + _beforeScaleTransform: function(e, transform) { + if (transform.action === "scale" || transform.action === "scaleX" || transform.action === "scaleY") { + var centerTransform = this._shouldCenterTransform(e, transform.target); + if (centerTransform && (transform.originX !== "center" || transform.originY !== "center") || !centerTransform && transform.originX === "center" && transform.originY === "center") { + this._resetCurrentTransform(e); + transform.reset = true; + } + } + }, + _onScale: function(e, transform, x, y) { + if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get("lockUniScaling")) { + transform.currentAction = "scale"; + this._scaleObject(x, y); + } else { + if (!transform.reset && transform.currentAction === "scale") { + this._resetCurrentTransform(e, transform.target); + } + transform.currentAction = "scaleEqually"; + this._scaleObject(x, y, "equally"); + } + }, + _setCursorFromEvent: function(e, target) { + var style = this.upperCanvasEl.style; + if (!target || !target.selectable) { + style.cursor = this.defaultCursor; + return false; + } else { + var activeGroup = this.getActiveGroup(), corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) && target._findTargetCorner(this.getPointer(e, true)); + if (!corner) { + style.cursor = target.hoverCursor || this.hoverCursor; + } else { + this._setCornerCursor(corner, target); + } + } + return true; + }, + _setCornerCursor: function(corner, target) { + var style = this.upperCanvasEl.style; + if (corner in cursorOffset) { + style.cursor = this._getRotatedCornerCursor(corner, target); + } else if (corner === "mtr" && target.hasRotatingPoint) { + style.cursor = this.rotationCursor; + } else { + style.cursor = this.defaultCursor; + return false; + } + }, + _getRotatedCornerCursor: function(corner, target) { + var n = Math.round(target.getAngle() % 360 / 45); + if (n < 0) { + n += 8; + } + n += cursorOffset[corner]; + n %= 8; + return this.cursorMap[n]; + } }); +})(); - return this; - }, - - /** - * Centers object vertically with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxCenterObjectV: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('top'), - endValue: this.getCenter().top, - duration: this.FX_DURATION, - onChange: function(value) { - object.set('top', value); - _this.renderAll(); - onChange(); - }, - onComplete: function() { - object.setCoords(); - onComplete(); - } +(function() { + var min = Math.min, max = Math.max; + fabric.util.object.extend(fabric.Canvas.prototype, { + _shouldGroup: function(e, target) { + var activeObject = this.getActiveObject(); + return e.shiftKey && (this.getActiveGroup() || activeObject && activeObject !== target) && this.selection; + }, + _handleGrouping: function(e, target) { + if (target === this.getActiveGroup()) { + target = this.findTarget(e, true); + if (!target || target.isType("group")) { + return; + } + } + if (this.getActiveGroup()) { + this._updateActiveGroup(target, e); + } else { + this._createActiveGroup(target, e); + } + if (this._activeGroup) { + this._activeGroup.saveCoords(); + } + }, + _updateActiveGroup: function(target, e) { + var activeGroup = this.getActiveGroup(); + if (activeGroup.contains(target)) { + activeGroup.removeWithUpdate(target); + this._resetObjectTransform(activeGroup); + target.set("active", false); + if (activeGroup.size() === 1) { + this.discardActiveGroup(e); + this.setActiveObject(activeGroup.item(0)); + return; + } + } else { + activeGroup.addWithUpdate(target); + this._resetObjectTransform(activeGroup); + } + this.fire("selection:created", { + target: activeGroup, + e: e + }); + activeGroup.set("active", true); + }, + _createActiveGroup: function(target, e) { + if (this._activeObject && target !== this._activeObject) { + var group = this._createGroup(target); + this.setActiveGroup(group); + this._activeObject = null; + this.fire("selection:created", { + target: group, + e: e + }); + } + target.set("active", true); + }, + _createGroup: function(target) { + var objects = this.getObjects(), isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), groupObjects = isActiveLower ? [ this._activeObject, target ] : [ target, this._activeObject ]; + return new fabric.Group(groupObjects, { + originX: "center", + originY: "center" + }); + }, + _groupSelectedObjects: function(e) { + var group = this._collectObjects(); + if (group.length === 1) { + this.setActiveObject(group[0], e); + } else if (group.length > 1) { + group = new fabric.Group(group.reverse(), { + originX: "center", + originY: "center" + }); + this.setActiveGroup(group, e); + group.saveCoords(); + this.fire("selection:created", { + target: group + }); + this.renderAll(); + } + }, + _collectObjects: function() { + var group = [], currentObject, x1 = this._groupSelector.ex, y1 = this._groupSelector.ey, x2 = x1 + this._groupSelector.left, y2 = y1 + this._groupSelector.top, selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), isClick = x1 === x2 && y1 === y2; + for (var i = this._objects.length; i--; ) { + currentObject = this._objects[i]; + if (!currentObject || !currentObject.selectable || !currentObject.visible) continue; + if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || currentObject.containsPoint(selectionX1Y1) || currentObject.containsPoint(selectionX2Y2)) { + currentObject.set("active", true); + group.push(currentObject); + if (isClick) break; + } + } + return group; + }, + _maybeGroupObjects: function(e) { + if (this.selection && this._groupSelector) { + this._groupSelectedObjects(e); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.setObjectsCoords().setCoords(); + activeGroup.isMoving = false; + this._setCursor(this.defaultCursor); + } + this._groupSelector = null; + this._currentTransform = null; + } }); +})(); - return this; - }, - - /** - * Same as `fabric.Canvas#remove` but animated - * @param {fabric.Object} object Object to remove - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxRemove: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('opacity'), - endValue: 0, - duration: this.FX_DURATION, - onStart: function() { - object.set('active', false); - }, - onChange: function(value) { - object.set('opacity', value); - _this.renderAll(); - onChange(); - }, - onComplete: function () { - _this.remove(object); - onComplete(); - } - }); - - return this; - } +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + toDataURL: function(options) { + options || (options = {}); + var format = options.format || "png", quality = options.quality || 1, multiplier = options.multiplier || 1, cropping = { + left: options.left, + top: options.top, + width: options.width, + height: options.height + }; + if (multiplier !== 1) { + return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); + } else { + return this.__toDataURL(format, quality, cropping); + } + }, + __toDataURL: function(format, quality, cropping) { + this.renderAll(true); + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + if (format === "jpg") { + format = "jpeg"; + } + var data = fabric.StaticCanvas.supports("toDataURLWithQuality") ? (croppedCanvasEl || canvasEl).toDataURL("image/" + format, quality) : (croppedCanvasEl || canvasEl).toDataURL("image/" + format); + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + if (croppedCanvasEl) { + croppedCanvasEl = null; + } + return data; + }, + __getCroppedCanvas: function(canvasEl, cropping) { + var croppedCanvasEl, croppedCtx, shouldCrop = "left" in cropping || "top" in cropping || "width" in cropping || "height" in cropping; + if (shouldCrop) { + croppedCanvasEl = fabric.util.createCanvasElement(); + croppedCtx = croppedCanvasEl.getContext("2d"); + croppedCanvasEl.width = cropping.width || this.width; + croppedCanvasEl.height = cropping.height || this.height; + croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); + } + return croppedCanvasEl; + }, + __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { + var origWidth = this.getWidth(), origHeight = this.getHeight(), scaledWidth = origWidth * multiplier, scaledHeight = origHeight * multiplier, activeObject = this.getActiveObject(), activeGroup = this.getActiveGroup(), ctx = this.contextTop || this.contextContainer; + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } + ctx.scale(multiplier, multiplier); + if (cropping.left) { + cropping.left *= multiplier; + } + if (cropping.top) { + cropping.top *= multiplier; + } + if (cropping.width) { + cropping.width *= multiplier; + } else if (multiplier < 1) { + cropping.width = scaledWidth; + } + if (cropping.height) { + cropping.height *= multiplier; + } else if (multiplier < 1) { + cropping.height = scaledHeight; + } + if (activeGroup) { + this._tempRemoveBordersControlsFromGroup(activeGroup); + } else if (activeObject && this.deactivateAll) { + this.deactivateAll(); + } + this.renderAll(true); + var data = this.__toDataURL(format, quality, cropping); + this.width = origWidth; + this.height = origHeight; + ctx.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + if (activeGroup) { + this._restoreBordersControlsOnGroup(activeGroup); + } else if (activeObject && this.setActiveObject) { + this.setActiveObject(activeObject); + } + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + return data; + }, + toDataURLWithMultiplier: function(format, multiplier, quality) { + return this.toDataURL({ + format: format, + multiplier: multiplier, + quality: quality + }); + }, + _tempRemoveBordersControlsFromGroup: function(group) { + group.origHasControls = group.hasControls; + group.origBorderColor = group.borderColor; + group.hasControls = true; + group.borderColor = "rgba(0,0,0,0)"; + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = "rgba(0,0,0,0)"; + }); + }, + _restoreBordersControlsOnGroup: function(group) { + group.hideControls = group.origHideControls; + group.borderColor = group.origBorderColor; + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + } }); -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - /** - * Animates object's properties - * @param {String|Object} property to animate (if string) or properties to animate (if object) - * @param {Number|Object} value to animate property to (if string was given first) or options object - * @return {fabric.Object} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation} - * @chainable - * - * As object — multiple properties - * - * object.animate({ left: ..., top: ... }); - * object.animate({ left: ..., top: ... }, { duration: ... }); - * - * As string — one property - * - * object.animate('left', ...); - * object.animate('left', { duration: ... }); - * - */ - animate: function() { - if (arguments[0] && typeof arguments[0] === 'object') { - var propsToAnimate = [ ], prop, skipCallbacks; - for (prop in arguments[0]) { - propsToAnimate.push(prop); - } - for (var i = 0, len = propsToAnimate.length; i < len; i++) { - prop = propsToAnimate[i]; - skipCallbacks = i !== len - 1; - this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); - } - } - else { - this._animate.apply(this, arguments); - } - return this; - }, - - /** - * @private - * @param {String} property Property to animate - * @param {String} to Value to animate to - * @param {Object} [options] Options object - * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked - */ - _animate: function(property, to, options, skipCallbacks) { - var _this = this, propPair; - - to = to.toString(); - - if (!options) { - options = { }; - } - else { - options = fabric.util.object.clone(options); - } - - if (~property.indexOf('.')) { - propPair = property.split('.'); - } - - var currentValue = propPair - ? this.get(propPair[0])[propPair[1]] - : this.get(property); - - if (!('from' in options)) { - options.from = currentValue; - } - - if (~to.indexOf('=')) { - to = currentValue + parseFloat(to.replace('=', '')); - } - else { - to = parseFloat(to); - } - - fabric.util.animate({ - startValue: options.from, - endValue: to, - byValue: options.by, - easing: options.easing, - duration: options.duration, - abort: options.abort && function() { - return options.abort.call(_this); - }, - onChange: function(value) { - if (propPair) { - _this[propPair[0]][propPair[1]] = value; +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + loadFromDatalessJSON: function(json, callback, reviver) { + return this.loadFromJSON(json, callback, reviver); + }, + loadFromJSON: function(json, callback, reviver) { + if (!json) return; + var serialized = typeof json === "string" ? JSON.parse(json) : json; + this.clear(); + var _this = this; + this._enlivenObjects(serialized.objects, function() { + _this._setBgOverlay(serialized, callback); + }, reviver); + return this; + }, + _setBgOverlay: function(serialized, callback) { + var _this = this, loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; } - else { - _this.set(property, value); + var cbIfLoaded = function() { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + _this.renderAll(); + callback && callback(); + } + }; + this.__setBgOverlay("backgroundImage", serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay("overlayImage", serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay("backgroundColor", serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay("overlayColor", serialized.overlay, loaded, cbIfLoaded); + cbIfLoaded(); + }, + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + if (!value) { + loaded[property] = true; + return; } - if (skipCallbacks) return; - options.onChange && options.onChange(); - }, - onComplete: function() { - if (skipCallbacks) return; - - _this.setCoords(); - options.onComplete && options.onComplete(); - } - }); - } + if (property === "backgroundImage" || property === "overlayImage") { + fabric.Image.fromObject(value, function(img) { + _this[property] = img; + loaded[property] = true; + callback && callback(); + }); + } else { + this["set" + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); + } + }, + _enlivenObjects: function(objects, callback, reviver) { + var _this = this; + if (!objects || objects.length === 0) { + callback && callback(); + return; + } + var renderOnAddRemove = this.renderOnAddRemove; + this.renderOnAddRemove = false; + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + enlivenedObjects.forEach(function(obj, index) { + _this.insertAt(obj, index, true); + }); + _this.renderOnAddRemove = renderOnAddRemove; + callback && callback(); + }, null, reviver); + }, + _toDataURL: function(format, callback) { + this.clone(function(clone) { + callback(clone.toDataURL(format)); + }); + }, + _toDataURLWithMultiplier: function(format, multiplier, callback) { + this.clone(function(clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + clone: function(callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + cloneWithoutData: function(callback) { + var el = fabric.document.createElement("canvas"); + el.width = this.getWidth(); + el.height = this.getHeight(); + var clone = new fabric.Canvas(el); + clone.clipTo = this.clipTo; + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } else { + callback && callback(clone); + } + } }); +(function() { + var degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees; + fabric.util.object.extend(fabric.Canvas.prototype, { + __onTransformGesture: function(e, self) { + if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || "gesture" !== self.gesture) { + return; + } + var target = this.findTarget(e); + if ("undefined" !== typeof target) { + this.onBeforeScaleRotate(target); + this._rotateObjectByAngle(self.rotation); + this._scaleObjectBy(self.scale); + } + this.fire("touch:gesture", { + target: target, + e: e, + self: self + }); + }, + __onDrag: function(e, self) { + this.fire("touch:drag", { + e: e, + self: self + }); + }, + __onOrientationChange: function(e, self) { + this.fire("touch:orientation", { + e: e, + self: self + }); + }, + __onShake: function(e, self) { + this.fire("touch:shake", { + e: e, + self: self + }); + }, + _scaleObjectBy: function(s, by) { + var t = this._currentTransform, target = t.target, lockScalingX = target.get("lockScalingX"), lockScalingY = target.get("lockScalingY"); + if (lockScalingX && lockScalingY) return; + target._scaling = true; + if (!by) { + if (!lockScalingX) { + target.set("scaleX", t.scaleX * s); + } + if (!lockScalingY) { + target.set("scaleY", t.scaleY * s); + } + } else if (by === "x" && !target.get("lockUniScaling")) { + lockScalingX || target.set("scaleX", t.scaleX * s); + } else if (by === "y" && !target.get("lockUniScaling")) { + lockScalingY || target.set("scaleY", t.scaleY * s); + } + }, + _rotateObjectByAngle: function(curAngle) { + var t = this._currentTransform; + if (t.target.get("lockRotation")) return; + t.target.angle = radiansToDegrees(degreesToRadians(curAngle) + t.theta); + } + }); +})(); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Line) { - fabric.warn('fabric.Line is already defined'); - return; - } - - /** - * Line class - * @class fabric.Line - * @extends fabric.Object - * @see {@link fabric.Line#initialize} for constructor definition - */ - fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'line', - - /** - * x value or first line edge - * @type Number - * @default - */ - x1: 0, - - /** - * y value or first line edge - * @type Number - * @default - */ - y1: 0, - - /** - * x value or second line edge - * @type Number - * @default - */ - x2: 0, - - /** - * y value or second line edge - * @type Number - * @default - */ - y2: 0, - - /** - * Constructor - * @param {Array} [points] Array of points - * @param {Object} [options] Options object - * @return {fabric.Line} thisArg - */ - initialize: function(points, options) { - options = options || { }; - - if (!points) { - points = [0, 0, 0, 0]; - } - - this.callSuper('initialize', options); - - this.set('x1', points[0]); - this.set('y1', points[1]); - this.set('x2', points[2]); - this.set('y2', points[3]); - - this._setWidthHeight(options); - }, - - /** - * @private - * @param {Object} [options] Options - */ - _setWidthHeight: function(options) { - options || (options = { }); - - this.width = Math.abs(this.x2 - this.x1) || 1; - this.height = Math.abs(this.y2 - this.y1) || 1; - - this.left = 'left' in options - ? options.left - : this._getLeftToOriginX(); - - this.top = 'top' in options - ? options.top - : this._getTopToOriginY(); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - */ - _set: function(key, value) { - this[key] = value; - if (typeof coordProps[key] !== 'undefined') { - this._setWidthHeight(); - } - return this; - }, - - /** - * @private - * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. - */ - _getLeftToOriginX: makeEdgeToOriginGetter( - { // property names - origin: 'originX', - axis1: 'x1', - axis2: 'x2', - dimension: 'width' - }, - { // possible values of origin - nearest: 'left', - center: 'center', - farthest: 'right' - } - ), - - /** - * @private - * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. - */ - _getTopToOriginY: makeEdgeToOriginGetter( - { // property names - origin: 'originY', - axis1: 'y1', - axis2: 'y2', - dimension: 'height' - }, - { // possible values of origin - nearest: 'top', - center: 'center', - farthest: 'bottom' - } - ), - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - ctx.beginPath(); - - var isInPathGroup = this.group && this.group.type === 'path-group'; - if (isInPathGroup && !this.transformMatrix) { - // Line coords are distances from left-top of canvas to origin of line. - // - // To render line in a path-group, we need to translate them to - // distances from center of path-group to center of line. - var cp = this.getCenterPoint(); - ctx.translate( - -this.group.width/2 + cp.x, - -this.group.height / 2 + cp.y - ); - } - - if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { - - // move from center (of virtual box) to its left/top corner - // we can't assume x1, y1 is top left and x2, y2 is bottom right - var xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1; - - ctx.moveTo( - this.width === 1 ? 0 : (xMult * this.width / 2), - this.height === 1 ? 0 : (yMult * this.height / 2)); - - ctx.lineTo( - this.width === 1 ? 0 : (xMult * -1 * this.width / 2), - this.height === 1 ? 0 : (yMult * -1 * this.height / 2)); - } - - ctx.lineWidth = this.strokeWidth; - - // TODO: test this - // make sure setting "fill" changes color of a line - // (by copying fillStyle to strokeStyle, since line is stroked, not filled) - var origStrokeStyle = ctx.strokeStyle; - ctx.strokeStyle = this.stroke || ctx.fillStyle; - this.stroke && this._renderStroke(ctx); - ctx.strokeStyle = origStrokeStyle; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var - xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1, - x = this.width === 1 ? 0 : xMult * this.width / 2, - y = this.height === 1 ? 0 : yMult * this.height / 2; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @methd toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - x1: this.get('x1'), - y1: this.get('y1'), - x2: this.get('x2'), - y2: this.get('y2') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) - * @static - * @memberOf fabric.Line - * @see http://www.w3.org/TR/SVG/shapes.html#LineElement - */ - fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); - - /** - * Returns fabric.Line instance from an SVG element - * @static - * @memberOf fabric.Line - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromElement = function(element, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), - points = [ - parsedAttributes.x1 || 0, - parsedAttributes.y1 || 0, - parsedAttributes.x2 || 0, - parsedAttributes.y2 || 0 - ]; - return new fabric.Line(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Line instance from an object representation - * @static - * @memberOf fabric.Line - * @param {Object} object Object to create an instance from - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromObject = function(object) { - var points = [object.x1, object.y1, object.x2, object.y2]; - return new fabric.Line(points, object); - }; - - /** - * Produces a function that calculates distance from canvas edge to Line origin. - */ - function makeEdgeToOriginGetter(propertyNames, originValues) { - var origin = propertyNames.origin, - axis1 = propertyNames.axis1, - axis2 = propertyNames.axis2, - dimension = propertyNames.dimension, - nearest = originValues.nearest, - center = originValues.center, - farthest = originValues.farthest; - - return function() { - switch (this.get(origin)) { - case nearest: - return Math.min(this.get(axis1), this.get(axis2)); - case center: - return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); - case farthest: - return Math.max(this.get(axis1), this.get(axis2)); - } - }; - - } - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Circle) { - fabric.warn('fabric.Circle is already defined.'); - return; - } - - /** - * Circle class - * @class fabric.Circle - * @extends fabric.Object - * @see {@link fabric.Circle#initialize} for constructor definition - */ - fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'circle', - - /** - * Radius of this circle - * @type Number - * @default - */ - radius: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Circle} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.set('radius', options.radius || 0); - this.callSuper('initialize', options); - }, - - /** - * @private - * @param {String} key - * @param {Any} value - * @return {fabric.Circle} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key === 'radius') { - this.setRadius(value); - } - - return this; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - radius: this.get('radius') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param ctx {CanvasRenderingContext2D} context to render on - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - // multiply by currently set alpha (the one that was set by path group where this object is contained, for example) - 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.stroke && this._renderStroke(ctx); - }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusX: function() { - return this.get('radius') * this.get('scaleX'); - }, - - /** - * Returns vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusY: function() { - return this.get('radius') * this.get('scaleY'); - }, - - /** - * Sets radius of an object (and updates width accordingly) - * @return {Number} - */ - setRadius: function(value) { - this.radius = value; - this.set('width', value * 2).set('height', value * 2); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) - * @static - * @memberOf fabric.Circle - * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement - */ - fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); - - /** - * Returns {@link fabric.Circle} instance from an SVG element - * @static - * @memberOf fabric.Circle - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @throws {Error} If value of `r` attribute is missing or invalid - * @return {fabric.Circle} Instance of fabric.Circle - */ - fabric.Circle.fromElement = function(element, options) { - options || (options = { }); - var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); - if (!isValidRadius(parsedAttributes)) { - throw new Error('value of `r` attribute is required and can not be negative'); - } - if ('left' in parsedAttributes) { - parsedAttributes.left -= (options.width / 2) || 0; - } - if ('top' in parsedAttributes) { - parsedAttributes.top -= (options.height / 2) || 0; - } - var obj = new fabric.Circle(extend(parsedAttributes, options)); - - obj.cx = parseFloat(element.getAttribute('cx')) || 0; - obj.cy = parseFloat(element.getAttribute('cy')) || 0; - - return obj; - }; - - /** - * @private - */ - function isValidRadius(attributes) { - return (('radius' in attributes) && (attributes.radius > 0)); - } - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Circle} instance from an object representation - * @static - * @memberOf fabric.Circle - * @param {Object} object Object to create an instance from - * @return {Object} Instance of fabric.Circle - */ - fabric.Circle.fromObject = function(object) { - return new fabric.Circle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Triangle) { - fabric.warn('fabric.Triangle is already defined'); - return; - } - - /** - * Triangle class - * @class fabric.Triangle - * @extends fabric.Object - * @return {fabric.Triangle} thisArg - * @see {@link fabric.Triangle#initialize} for constructor definition - */ - fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'triangle', - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('width', options.width || 100) - .set('height', options.height || 100); - }, - - /** - * @private - * @param ctx {CanvasRenderingContext2D} Context to render on - */ - _render: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - ctx.moveTo(-widthBy2, heightBy2); - ctx.lineTo(0, -heightBy2); - ctx.lineTo(widthBy2, heightBy2); - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @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(); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - widthBy2 = this.width / 2, - heightBy2 = this.height / 2, - points = [ - -widthBy2 + ' ' + heightBy2, - '0 ' + -heightBy2, - widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Returns fabric.Triangle instance from an object representation - * @static - * @memberOf fabric.Triangle - * @param object {Object} object to create an instance from - * @return {Object} instance of Canvas.Triangle - */ - fabric.Triangle.fromObject = function(object) { - return new fabric.Triangle(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global){ - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Ellipse) { - fabric.warn('fabric.Ellipse is already defined.'); - return; - } - - /** - * Ellipse class - * @class fabric.Ellipse - * @extends fabric.Object - * @return {fabric.Ellipse} thisArg - * @see {@link fabric.Ellipse#initialize} for constructor definition - */ - fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'ellipse', - - /** - * Horizontal radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical radius - * @type Number - * @default - */ - ry: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Ellipse} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('rx', options.rx || 0); - this.set('ry', options.ry || 0); - - this.set('width', this.get('rx') * 2); - this.set('height', this.get('ry') * 2); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx'), - ry: this.get('ry') - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Renders this instance on a given context - * @param ctx {CanvasRenderingContext2D} context to render on - * @param noTransform {Boolean} context is not transformed when set to true - */ - render: function(ctx, noTransform) { - // do not use `get` for perf. reasons - if (this.rx === 0 || this.ry === 0) return; - return this.callSuper('render', ctx, noTransform); - }, - - /** - * @private - * @param ctx {CanvasRenderingContext2D} context to render on - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.save(); - ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; - if (this.transformMatrix && this.group) { - ctx.translate(this.cx, this.cy); - } - 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); - ctx.restore(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) - * @static - * @memberOf fabric.Ellipse - * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement - */ - fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); - - /** - * Returns {@link fabric.Ellipse} instance from an SVG element - * @static - * @memberOf fabric.Ellipse - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES), - cx = parsedAttributes.left, - cy = parsedAttributes.top; - - if ('left' in parsedAttributes) { - parsedAttributes.left -= (options.width / 2) || 0; - } - if ('top' in parsedAttributes) { - parsedAttributes.top -= (options.height / 2) || 0; - } - - var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); - - ellipse.cx = cx || 0; - ellipse.cy = cy || 0; - - return ellipse; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Ellipse} instance from an object representation - * @static - * @memberOf fabric.Ellipse - * @param {Object} object Object to create an instance from - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromObject = function(object) { - return new fabric.Ellipse(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - if (fabric.Rect) { - console.warn('fabric.Rect is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push('rx', 'ry', 'x', 'y'); - - /** - * Rectangle class - * @class fabric.Rect - * @extends fabric.Object - * @return {fabric.Rect} thisArg - * @see {@link fabric.Rect#initialize} for constructor definition - */ - fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { - - /** - * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * Type of an object - * @type String - * @default - */ - type: 'rect', - - /** - * Horizontal border radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical border radius - * @type Number - * @default - */ - ry: 0, - - /** - * @type Number - * @default - */ - x: 0, - - /** - * @type Number - * @default - */ - y: 0, - - /** - * Used to specify dash pattern for stroke on this object - * @type Array - */ - strokeDashArray: null, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this._initRxRy(); - - this.x = options.x || 0; - this.y = options.y || 0; - }, - - /** - * Initializes rx/ry attributes - * @private - */ - _initRxRy: function() { - if (this.rx && !this.ry) { - this.ry = this.rx; - } - else if (this.ry && !this.rx) { - this.rx = this.ry; - } - }, - - /** - * @private - * @param ctx {CanvasRenderingContext2D} context to render on - */ - _render: function(ctx) { - - // optimize 1x1 case (used in spray brush) - if (this.width === 1 && this.height === 1) { - ctx.fillRect(0, 0, 1, 1); + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, degreesToRadians = fabric.util.degreesToRadians, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Object) { return; - } - - var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, - ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, - w = this.width, - h = this.height, - x = -w / 2, - y = -h / 2, - isInPathGroup = this.group && this.group.type === 'path-group', - isRounded = rx !== 0 || ry !== 0, - k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; - - ctx.beginPath(); - ctx.globalAlpha = isInPathGroup ? (ctx.globalAlpha * this.opacity) : this.opacity; - - if (this.transformMatrix && isInPathGroup) { - ctx.translate( - this.width / 2 + this.x, - this.height / 2 + this.y); - } - if (!this.transformMatrix && isInPathGroup) { - ctx.translate( - -this.group.width / 2 + this.width / 2 + this.x, - -this.group.height / 2 + this.height / 2 + this.y); - } - - ctx.moveTo(x + rx, y); - - ctx.lineTo(x + w - rx, y); - isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); - - ctx.lineTo(x + w, y + h - ry); - isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); - - ctx.lineTo(x + rx, y + h); - isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); - - ctx.lineTo(x, y + ry); - isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); - - ctx.closePath(); - - this._renderFill(ctx); - 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; - - 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(); - }, - - /** - * Since coordinate system differs from that of SVG - * @private - */ - _normalizeLeftTopProperties: function(parsedAttributes) { - if ('left' in parsedAttributes) { - this.set('left', parsedAttributes.left + this.getWidth() / 2); - } - this.set('x', parsedAttributes.left || 0); - if ('top' in parsedAttributes) { - this.set('top', parsedAttributes.top + this.getHeight() / 2); - } - this.set('y', parsedAttributes.top || 0); - return this; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - rx: this.get('rx') || 0, - ry: this.get('ry') || 0, - x: this.get('x'), - y: this.get('y') - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - - markup.push( - ''); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; } - }); + fabric.Object = fabric.util.createClass({ + type: "object", + originX: "left", + originY: "top", + top: 0, + left: 0, + width: 0, + height: 0, + scaleX: 1, + scaleY: 1, + flipX: false, + flipY: false, + opacity: 1, + angle: 0, + cornerSize: 12, + transparentCorners: true, + hoverCursor: null, + padding: 0, + borderColor: "rgba(102,153,255,0.75)", + cornerColor: "rgba(102,153,255,0.5)", + centeredScaling: false, + centeredRotation: true, + fill: "rgb(0,0,0)", + fillRule: "source-over", + backgroundColor: "", + stroke: null, + strokeWidth: 1, + strokeDashArray: null, + strokeLineCap: "butt", + strokeLineJoin: "miter", + strokeMiterLimit: 10, + shadow: null, + borderOpacityWhenMoving: .4, + borderScaleFactor: 1, + transformMatrix: null, + minScaleLimit: .01, + selectable: true, + evented: true, + visible: true, + hasControls: true, + hasBorders: true, + hasRotatingPoint: true, + rotatingPointOffset: 40, + perPixelTargetFind: false, + includeDefaultValues: true, + clipTo: null, + lockMovementX: false, + lockMovementY: false, + lockRotation: false, + lockScalingX: false, + lockScalingY: false, + lockUniScaling: false, + stateProperties: ("top left width height scaleX scaleY flipX flipY originX originY transformMatrix " + "stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit " + "angle opacity fill fillRule shadow clipTo visible backgroundColor").split(" "), + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + _initGradient: function(options) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { + this.set("fill", new fabric.Gradient(options.fill)); + } + }, + _initPattern: function(options) { + if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { + this.set("fill", new fabric.Pattern(options.fill)); + } + if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { + this.set("stroke", new fabric.Pattern(options.stroke)); + } + }, + _initClipping: function(options) { + if (!options.clipTo || typeof options.clipTo !== "string") return; + var functionBody = fabric.util.getFunctionBody(options.clipTo); + if (typeof functionBody !== "undefined") { + this.clipTo = new Function("ctx", functionBody); + } + }, + setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + this._initGradient(options); + this._initPattern(options); + this._initClipping(options); + }, + transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } + ctx.globalAlpha = this.opacity; + var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + ctx.scale(this.scaleX * (this.flipX ? -1 : 1), this.scaleY * (this.flipY ? -1 : 1)); + }, + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: this.fill && this.fill.toObject ? this.fill.toObject() : this.fill, + stroke: this.stroke && this.stroke.toObject ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: this.shadow && this.shadow.toObject ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + fabric.util.populateWithProperties(this, object, propertiesToInclude); + return object; + }, + toDatalessObject: function(propertiesToInclude) { + return this.toObject(propertiesToInclude); + }, + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, stateProperties = prototype.stateProperties; + stateProperties.forEach(function(prop) { + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + }); + return object; + }, + toString: function() { + return "#"; + }, + get: function(property) { + return this[property]; + }, + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + set: function(key, value) { + if (typeof key === "object") { + this._setObject(key); + } else { + if (typeof value === "function" && key !== "clipTo") { + this._set(key, value(this.get(key))); + } else { + this._set(key, value); + } + } + return this; + }, + _set: function(key, value) { + var shouldConstrainValue = key === "scaleX" || key === "scaleY"; + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === "scaleX" && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } else if (key === "scaleY" && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } else if (key === "width" || key === "height") { + this.minScaleLimit = toFixed(Math.min(.1, 1 / Math.max(this.width, this.height)), 2); + } else if (key === "shadow" && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + this[key] = value; + return this; + }, + toggle: function(property) { + var value = this.get(property); + if (typeof value === "boolean") { + this.set(property, !value); + } + return this; + }, + setSourcePath: function(value) { + this.sourcePath = value; + return this; + }, + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) return this.canvas.viewportTransform; + return [ 1, 0, 0, 1, 0, 0 ]; + }, + render: function(ctx, noTransform) { + if (this.width === 0 || this.height === 0 || !this.visible) return; + ctx.save(); + this._setupFillRule(ctx); + this._transform(ctx, noTransform); + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + var m = this.transformMatrix; + if (m && this.group) { + ctx.translate(-this.group.width / 2, -this.group.height / 2); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + this._render(ctx, noTransform); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + this._restoreFillRule(ctx); + ctx.restore(); + }, + _transform: function(ctx, noTransform) { + var m = this.transformMatrix; + if (m && !this.group) { + ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + }, + _setStrokeStyles: function(ctx) { + if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke; + } + }, + _setFillStyles: function(ctx) { + if (this.fill) { + ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx) : this.fill; + } + }, + _renderControls: function(ctx, noTransform) { + var v = this.getViewportTransform(); + ctx.save(); + if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), v); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), v, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + this.drawBorders(ctx); + this.drawControls(ctx); + } + ctx.restore(); + }, + _setShadow: function(ctx) { + if (!this.shadow) return; + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; + }, + _removeShadow: function(ctx) { + if (!this.shadow) return; + ctx.shadowColor = ""; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + _renderFill: function(ctx) { + if (!this.fill) return; + if (this.fill.toLive) { + ctx.save(); + ctx.translate(-this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); + } + if (this.fillRule === "destination-over") { + ctx.fill("evenodd"); + } else { + ctx.fill(); + } + if (this.fill.toLive) { + ctx.restore(); + } + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + }, + _renderStroke: function(ctx) { + if (!this.stroke) return; + ctx.save(); + if (this.strokeDashArray) { + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + if (supportsLineDash) { + 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); + ctx.restore(); + }, + clone: function(callback, propertiesToInclude) { + if (this.constructor.fromObject) { + return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + } + return new fabric.Object(this.toObject(propertiesToInclude)); + }, + cloneAsImage: function(callback) { + var dataUrl = this.toDataURL(); + fabric.util.loadImage(dataUrl, function(img) { + if (callback) { + callback(new fabric.Image(img)); + } + }); + return this; + }, + toDataURL: function(options) { + options || (options = {}); + var el = fabric.util.createCanvasElement(), boundingRect = this.getBoundingRect(); + el.width = boundingRect.width; + el.height = boundingRect.height; + fabric.util.wrapElement(el, "div"); + var canvas = new fabric.Canvas(el); + if (options.format === "jpg") { + options.format = "jpeg"; + } + if (options.format === "jpeg") { + canvas.backgroundColor = "#fff"; + } + var origParams = { + active: this.get("active"), + left: this.getLeft(), + top: this.getTop() + }; + this.set("active", false); + this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), "center", "center"); + var originalCanvas = this.canvas; + canvas.add(this); + var data = canvas.toDataURL(options); + this.set(origParams).setCoords(); + this.canvas = originalCanvas; + canvas.dispose(); + canvas = null; + return data; + }, + isType: function(type) { + return this.type === type; + }, + complexity: function() { + return 0; + }, + toJSON: function(propertiesToInclude) { + return this.toObject(propertiesToInclude); + }, + setGradient: function(property, options) { + options || (options = {}); + var gradient = { + colorStops: [] + }; + gradient.type = options.type || (options.r1 || options.r2 ? "radial" : "linear"); + gradient.coords = { + x1: options.x1, + y1: options.y1, + x2: options.x2, + y2: options.y2 + }; + if (options.r1 || options.r2) { + gradient.coords.r1 = options.r1; + gradient.coords.r2 = options.r2; + } + for (var position in options.colorStops) { + var color = new fabric.Color(options.colorStops[position]); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + return this.set(property, fabric.Gradient.forObject(this, gradient)); + }, + setPatternFill: function(options) { + return this.set("fill", new fabric.Pattern(options)); + }, + setShadow: function(options) { + return this.set("shadow", new fabric.Shadow(options)); + }, + setColor: function(color) { + this.set("fill", color); + return this; + }, + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== "center" || this.originY !== "center") && this.centeredRotation; + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + this.set("angle", angle); + if (shouldCenterOrigin) { + this._resetOrigin(); + } + return this; + }, + centerH: function() { + this.canvas.centerObjectH(this); + return this; + }, + centerV: function() { + this.canvas.centerObjectV(this); + return this; + }, + center: function() { + this.canvas.centerObject(this); + return this; + }, + remove: function() { + this.canvas.remove(this); + return this; + }, + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), "left", "top"); + return { + x: pointer.x - objectLeftTop.x, + y: pointer.y - objectLeftTop.y + }; + }, + _setupFillRule: function(ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + _restoreFillRule: function(ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } + } + }); + fabric.util.createAccessors(fabric.Object); + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + extend(fabric.Object.prototype, fabric.Observable); + fabric.Object.NUM_FRACTION_DIGITS = 2; + fabric.Object.__uid = 0; +})(typeof exports !== "undefined" ? exports : this); - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) - * @static - * @memberOf fabric.Rect - * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement - */ - fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); +(function() { + var degreesToRadians = fabric.util.degreesToRadians; + fabric.util.object.extend(fabric.Object.prototype, { + translateToCenterPoint: function(point, originX, originY) { + var cx = point.x, cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; + if (originX === "left") { + cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === "top") { + cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); + }, + translateToOriginPoint: function(center, originX, originY) { + var x = center.x, y = center.y, strokeWidth = this.stroke ? this.strokeWidth : 0; + if (originX === "left") { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === "top") { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); + }, + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), strokeWidth = this.stroke ? this.strokeWidth : 0, x, y; + if (originX && originY) { + if (originX === "left") { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else if (originX === "right") { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } else { + x = center.x; + } + if (originY === "top") { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else if (originY === "bottom") { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } else { + y = center.y; + } + } else { + x = this.left; + y = this.top; + } + return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x, y)); + }, + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), position = this.translateToOriginPoint(center, this.originX, this.originY); + this.set("left", position.x); + this.set("top", position.y); + }, + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), hypotHalf = this.getWidth() / 2, xHalf = Math.cos(angle) * hypotHalf, yHalf = Math.sin(angle) * hypotHalf, hypotFull = this.getWidth(), xFull = Math.cos(angle) * hypotFull, yFull = Math.sin(angle) * hypotFull; + if (this.originX === "center" && to === "left" || this.originX === "right" && to === "center") { + this.left -= xHalf; + this.top -= yHalf; + } else if (this.originX === "left" && to === "center" || this.originX === "center" && to === "right") { + this.left += xHalf; + this.top += yHalf; + } else if (this.originX === "left" && to === "right") { + this.left += xFull; + this.top += yFull; + } else if (this.originX === "right" && to === "left") { + this.left -= xFull; + this.top -= yFull; + } + this.setCoords(); + this.originX = to; + }, + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + var center = this.getCenterPoint(); + this.originX = "center"; + this.originY = "center"; + this.left = center.x; + this.top = center.y; + }, + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint(this.getCenterPoint(), this._originalOriginX, this._originalOriginY); + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + this.left = originPoint.x; + this.top = originPoint.y; + this._originalOriginX = null; + this._originalOriginY = null; + }, + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), "left", "center"); + } + }); +})(); - /** - * @private - */ - function _setDefaultLeftTopValues(attributes) { - attributes.left = attributes.left || 0; - attributes.top = attributes.top || 0; - return attributes; - } +(function() { + var degreesToRadians = fabric.util.degreesToRadians; + fabric.util.object.extend(fabric.Object.prototype, { + oCoords: null, + intersectsWithRect: function(pointTL, pointBR) { + var oCoords = this.oCoords, tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), br = new fabric.Point(oCoords.br.x, oCoords.br.y), intersection = fabric.Intersection.intersectPolygonRectangle([ tl, tr, br, bl ], pointTL, pointBR); + return intersection.status === "Intersection"; + }, + intersectsWithObject: function(other) { + function getCoords(oCoords) { + return { + tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br: new fabric.Point(oCoords.br.x, oCoords.br.y) + }; + } + var thisCoords = getCoords(this.oCoords), otherCoords = getCoords(other.oCoords), intersection = fabric.Intersection.intersectPolygonPolygon([ thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl ], [ otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl ]); + return intersection.status === "Intersection"; + }, + isContainedWithinObject: function(other) { + var boundingRect = other.getBoundingRect(), point1 = new fabric.Point(boundingRect.left, boundingRect.top), point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); + return this.isContainedWithinRect(point1, point2); + }, + isContainedWithinRect: function(pointTL, pointBR) { + var boundingRect = this.getBoundingRect(); + return boundingRect.left >= pointTL.x && boundingRect.left + boundingRect.width <= pointBR.x && boundingRect.top >= pointTL.y && boundingRect.top + boundingRect.height <= pointBR.y; + }, + containsPoint: function(point) { + var lines = this._getImageLines(this.oCoords), xPoints = this._findCrossPoints(point, lines); + return xPoints !== 0 && xPoints % 2 === 1; + }, + _getImageLines: function(oCoords) { + return { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + }; + }, + _findCrossPoints: function(point, oCoords) { + var b1, b2, a1, a2, xi, yi, xcount = 0, iLine; + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + if (iLine.o.y < point.y && iLine.d.y < point.y) { + continue; + } + if (iLine.o.y >= point.y && iLine.d.y >= point.y) { + continue; + } + if (iLine.o.x === iLine.d.x && iLine.o.x >= point.x) { + xi = iLine.o.x; + yi = point.y; + } else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + xi = -(a1 - a2) / (b1 - b2); + yi = a1 + b1 * xi; + } + if (xi >= point.x) { + xcount += 1; + } + if (xcount === 2) { + break; + } + } + return xcount; + }, + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + getBoundingRect: function() { + this.oCoords || this.setCoords(); + var xCoords = [ this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x ], minX = fabric.util.array.min(xCoords), maxX = fabric.util.array.max(xCoords), width = Math.abs(minX - maxX), yCoords = [ this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y ], minY = fabric.util.array.min(yCoords), maxY = fabric.util.array.max(yCoords), height = Math.abs(minY - maxY); + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + getWidth: function() { + return this.width * this.scaleX; + }, + getHeight: function() { + return this.height * this.scaleY; + }, + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } else { + return this.minScaleLimit; + } + } + return value; + }, + scale: function(value) { + value = this._constrainScale(value); + if (value < 0) { + this.flipX = !this.flipX; + this.flipY = !this.flipY; + value *= -1; + } + this.scaleX = value; + this.scaleY = value; + this.setCoords(); + return this; + }, + scaleToWidth: function(value) { + var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + scaleToHeight: function(value) { + var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + setCoords: function() { + var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(); + var f = function(p) { + return fabric.util.transformPoint(p, vpt); + }; + this.currentWidth = (this.width + strokeWidth) * this.scaleX; + this.currentHeight = (this.height + strokeWidth) * this.scaleY; + if (this.currentWidth < 0) { + this.currentWidth = Math.abs(this.currentWidth); + } + var _hypotenuse = Math.sqrt(Math.pow(this.currentWidth / 2, 2) + Math.pow(this.currentHeight / 2, 2)), _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(this.currentWidth, this.currentHeight), _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), _tr = new fabric.Point(_tl.x + wh.x * cosTh, _tl.y + wh.x * sinTh), _bl = new fabric.Point(_tl.x - wh.y * sinTh, _tl.y + wh.y * cosTh), _mt = new fabric.Point(_tl.x + wh.x / 2 * cosTh, _tl.y + wh.x / 2 * sinTh), tl = f(_tl), tr = f(_tr), br = f(new fabric.Point(_tr.x - wh.y * sinTh, _tr.y + wh.y * cosTh)), bl = f(_bl), ml = f(new fabric.Point(_tl.x - wh.y / 2 * sinTh, _tl.y + wh.y / 2 * cosTh)), mt = f(_mt), mr = f(new fabric.Point(_tr.x - wh.y / 2 * sinTh, _tr.y + wh.y / 2 * cosTh)), mb = f(new fabric.Point(_bl.x + wh.x / 2 * cosTh, _bl.y + wh.x / 2 * sinTh)), mtr = f(new fabric.Point(_mt.x, _mt.y)); + var padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + this.oCoords = { + tl: tl, + tr: tr, + br: br, + bl: bl, + ml: ml, + mt: mt, + mr: mr, + mb: mb, + mtr: mtr + }; + this._setCornerCoords && this._setCornerCoords(); + return this; + } + }); +})(); - /** - * Returns {@link fabric.Rect} instance from an SVG element - * @static - * @memberOf fabric.Rect - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Rect} Instance of fabric.Rect - */ - fabric.Rect.fromElement = function(element, options) { - if (!element) { - return null; +fabric.util.object.extend(fabric.Object.prototype, { + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } else { + this.canvas.sendToBack(this); + } + return this; + }, + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } else { + this.canvas.bringToFront(this); + } + return this; + }, + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } else { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } else { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + moveTo: function(index) { + if (this.group) { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } else { + this.canvas.moveTo(this, index); + } + return this; } +}); - var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); - parsedAttributes = _setDefaultLeftTopValues(parsedAttributes); +fabric.util.object.extend(fabric.Object.prototype, { + getSvgStyles: function() { + var fill = this.fill ? this.fill.toLive ? "url(#SVGID_" + this.fill.id + ")" : this.fill : "none", stroke = this.stroke ? this.stroke.toLive ? "url(#SVGID_" + this.stroke.id + ")" : this.stroke : "none", strokeWidth = this.strokeWidth ? this.strokeWidth : "0", strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(" ") : "", strokeLineCap = this.strokeLineCap ? this.strokeLineCap : "butt", strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : "miter", strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : "4", opacity = typeof this.opacity !== "undefined" ? this.opacity : "1", visibility = this.visible ? "" : " visibility: hidden;", filter = this.shadow && this.type !== "text" ? "filter: url(#SVGID_" + this.shadow.id + ");" : ""; + return [ "stroke: ", stroke, "; ", "stroke-width: ", strokeWidth, "; ", "stroke-dasharray: ", strokeDashArray, "; ", "stroke-linecap: ", strokeLineCap, "; ", "stroke-linejoin: ", strokeLineJoin, "; ", "stroke-miterlimit: ", strokeMiterLimit, "; ", "fill: ", fill, "; ", "opacity: ", opacity, ";", filter, visibility ].join(""); + }, + getSvgTransform: function() { + var toFixed = fabric.util.toFixed, angle = this.getAngle(), center = this.getCenterPoint(), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, translatePart = "translate(" + toFixed(center.x, NUM_FRACTION_DIGITS) + " " + toFixed(center.y, NUM_FRACTION_DIGITS) + ")", anglePart = angle !== 0 ? " rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")" : "", scalePart = this.scaleX === 1 && this.scaleY === 1 ? "" : " scale(" + toFixed(this.scaleX, NUM_FRACTION_DIGITS) + " " + toFixed(this.scaleY, NUM_FRACTION_DIGITS) + ")", flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : "", flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : ""; + return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(""); + }, + _createBaseSVGMarkup: function() { + 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)); + } + if (this.shadow) { + markup.push(this.shadow.toSVG(this)); + } + return markup; + } +}); - var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - rect._normalizeLeftTopProperties(parsedAttributes); +fabric.util.object.extend(fabric.Object.prototype, { + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this.get(prop) !== this.originalState[prop]; + }, this); + }, + saveState: function(options) { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + if (options && options.stateProperties) { + options.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + } + return this; + }, + setupState: function() { + this.originalState = {}; + this.saveState(); + return this; + } +}); - return rect; - }; - /* _FROM_SVG_END_ */ +(function() { + var degreesToRadians = fabric.util.degreesToRadians, isVML = typeof G_vmlCanvasManager !== "undefined"; + fabric.util.object.extend(fabric.Object.prototype, { + _controlsVisibility: null, + _findTargetCorner: function(pointer) { + if (!this.hasControls || !this.active) return false; + var ex = pointer.x, ey = pointer.y, xPoints, lines; + for (var i in this.oCoords) { + if (!this.isControlVisible(i)) { + continue; + } + if (i === "mtr" && !this.hasRotatingPoint) { + continue; + } + if (this.get("lockUniScaling") && (i === "mt" || i === "mr" || i === "mb" || i === "ml")) { + continue; + } + lines = this._getImageLines(this.oCoords[i].corner); + xPoints = this._findCrossPoints({ + x: ex, + y: ey + }, lines); + if (xPoints !== 0 && xPoints % 2 === 1) { + this.__corner = i; + return i; + } + } + return false; + }, + _setCornerCoords: function() { + var coords = this.oCoords, theta = degreesToRadians(this.angle), newTheta = degreesToRadians(45 - this.angle), cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), sinTh = Math.sin(theta), cosTh = Math.cos(theta); + coords.tl.corner = { + tl: { + x: coords.tl.x - sinHalfOffset, + y: coords.tl.y - cosHalfOffset + }, + tr: { + x: coords.tl.x + cosHalfOffset, + y: coords.tl.y - sinHalfOffset + }, + bl: { + x: coords.tl.x - cosHalfOffset, + y: coords.tl.y + sinHalfOffset + }, + br: { + x: coords.tl.x + sinHalfOffset, + y: coords.tl.y + cosHalfOffset + } + }; + coords.tr.corner = { + tl: { + x: coords.tr.x - sinHalfOffset, + y: coords.tr.y - cosHalfOffset + }, + tr: { + x: coords.tr.x + cosHalfOffset, + y: coords.tr.y - sinHalfOffset + }, + br: { + x: coords.tr.x + sinHalfOffset, + y: coords.tr.y + cosHalfOffset + }, + bl: { + x: coords.tr.x - cosHalfOffset, + y: coords.tr.y + sinHalfOffset + } + }; + coords.bl.corner = { + tl: { + x: coords.bl.x - sinHalfOffset, + y: coords.bl.y - cosHalfOffset + }, + bl: { + x: coords.bl.x - cosHalfOffset, + y: coords.bl.y + sinHalfOffset + }, + br: { + x: coords.bl.x + sinHalfOffset, + y: coords.bl.y + cosHalfOffset + }, + tr: { + x: coords.bl.x + cosHalfOffset, + y: coords.bl.y - sinHalfOffset + } + }; + coords.br.corner = { + tr: { + x: coords.br.x + cosHalfOffset, + y: coords.br.y - sinHalfOffset + }, + bl: { + x: coords.br.x - cosHalfOffset, + y: coords.br.y + sinHalfOffset + }, + br: { + x: coords.br.x + sinHalfOffset, + y: coords.br.y + cosHalfOffset + }, + tl: { + x: coords.br.x - sinHalfOffset, + y: coords.br.y - cosHalfOffset + } + }; + coords.ml.corner = { + tl: { + x: coords.ml.x - sinHalfOffset, + y: coords.ml.y - cosHalfOffset + }, + tr: { + x: coords.ml.x + cosHalfOffset, + y: coords.ml.y - sinHalfOffset + }, + bl: { + x: coords.ml.x - cosHalfOffset, + y: coords.ml.y + sinHalfOffset + }, + br: { + x: coords.ml.x + sinHalfOffset, + y: coords.ml.y + cosHalfOffset + } + }; + coords.mt.corner = { + tl: { + x: coords.mt.x - sinHalfOffset, + y: coords.mt.y - cosHalfOffset + }, + tr: { + x: coords.mt.x + cosHalfOffset, + y: coords.mt.y - sinHalfOffset + }, + bl: { + x: coords.mt.x - cosHalfOffset, + y: coords.mt.y + sinHalfOffset + }, + br: { + x: coords.mt.x + sinHalfOffset, + y: coords.mt.y + cosHalfOffset + } + }; + coords.mr.corner = { + tl: { + x: coords.mr.x - sinHalfOffset, + y: coords.mr.y - cosHalfOffset + }, + tr: { + x: coords.mr.x + cosHalfOffset, + y: coords.mr.y - sinHalfOffset + }, + bl: { + x: coords.mr.x - cosHalfOffset, + y: coords.mr.y + sinHalfOffset + }, + br: { + x: coords.mr.x + sinHalfOffset, + y: coords.mr.y + cosHalfOffset + } + }; + coords.mb.corner = { + tl: { + x: coords.mb.x - sinHalfOffset, + y: coords.mb.y - cosHalfOffset + }, + tr: { + x: coords.mb.x + cosHalfOffset, + y: coords.mb.y - sinHalfOffset + }, + bl: { + x: coords.mb.x - cosHalfOffset, + y: coords.mb.y + sinHalfOffset + }, + br: { + x: coords.mb.x + sinHalfOffset, + y: coords.mb.y + cosHalfOffset + } + }; + coords.mtr.corner = { + tl: { + x: coords.mtr.x - sinHalfOffset + sinTh * this.rotatingPointOffset, + y: coords.mtr.y - cosHalfOffset - cosTh * this.rotatingPointOffset + }, + tr: { + x: coords.mtr.x + cosHalfOffset + sinTh * this.rotatingPointOffset, + y: coords.mtr.y - sinHalfOffset - cosTh * this.rotatingPointOffset + }, + bl: { + x: coords.mtr.x - cosHalfOffset + sinTh * this.rotatingPointOffset, + y: coords.mtr.y + sinHalfOffset - cosTh * this.rotatingPointOffset + }, + br: { + x: coords.mtr.x + sinHalfOffset + sinTh * this.rotatingPointOffset, + y: coords.mtr.y + cosHalfOffset - cosTh * this.rotatingPointOffset + } + }; + }, + drawBorders: function(ctx) { + if (!this.hasBorders) return this; + var padding = this.padding, padding2 = padding * 2, strokeWidth = ~~(this.strokeWidth / 2) * 2; + ctx.save(); + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = this.borderColor; + var scaleX = 1 / this._constrainScale(this.scaleX), scaleY = 1 / this._constrainScale(this.scaleY); + ctx.lineWidth = 1 / this.borderScaleFactor; + var vpt = this.getViewportTransform(), wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, h = wh.y, sx = sxy.x, sy = sxy.y; + if (this.group) { + w = w * this.group.scaleX; + h = h * this.group.scaleY; + } + ctx.strokeRect(~~(-(w / 2) - padding - strokeWidth / 2 * sx) - .5, ~~(-(h / 2) - padding - strokeWidth / 2 * sy) - .5, ~~(w + padding2 + strokeWidth * sx) + 1, ~~(h + padding2 + strokeWidth * sy) + 1); + if (this.hasRotatingPoint && this.isControlVisible("mtr") && !this.get("lockRotation") && this.hasControls) { + var rotateHeight = (this.flipY ? h + strokeWidth * sx + padding * 2 : -h - strokeWidth * sy - padding * 2) / 2; + ctx.beginPath(); + ctx.moveTo(0, rotateHeight); + ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset)); + ctx.closePath(); + ctx.stroke(); + } + ctx.restore(); + return this; + }, + drawControls: function(ctx) { + if (!this.hasControls) return this; + var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.getViewportTransform(), true), width = wh.x, height = wh.y, left = -(width / 2), top = -(height / 2), padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, methodName = this.transparentCorners ? "strokeRect" : "fillRect"; + ctx.save(); + ctx.lineWidth = 1; + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = ctx.fillStyle = this.cornerColor; + this._drawControl("tl", ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top - scaleOffset - strokeWidth2 - padding); + this._drawControl("tr", ctx, methodName, left + width - scaleOffset + strokeWidth2 + padding, top - scaleOffset - strokeWidth2 - padding); + this._drawControl("bl", ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top + height + scaleOffsetSize + strokeWidth2 + padding); + this._drawControl("br", ctx, methodName, left + width + scaleOffsetSize + strokeWidth2 + padding, top + height + scaleOffsetSize + strokeWidth2 + padding); + if (!this.get("lockUniScaling")) { + this._drawControl("mt", ctx, methodName, left + width / 2 - scaleOffset, top - scaleOffset - strokeWidth2 - padding); + this._drawControl("mb", ctx, methodName, left + width / 2 - scaleOffset, top + height + scaleOffsetSize + strokeWidth2 + padding); + this._drawControl("mr", ctx, methodName, left + width + scaleOffsetSize + strokeWidth2 + padding, top + height / 2 - scaleOffset); + this._drawControl("ml", ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top + height / 2 - scaleOffset); + } + if (this.hasRotatingPoint) { + this._drawControl("mtr", ctx, methodName, left + width / 2 - scaleOffset, this.flipY ? top + height + this.rotatingPointOffset - this.cornerSize / 2 + strokeWidth2 + padding : top - this.rotatingPointOffset - this.cornerSize / 2 - strokeWidth2 - padding); + } + ctx.restore(); + return this; + }, + _drawControl: function(control, ctx, methodName, left, top) { + var size = this.cornerSize; + if (this.isControlVisible(control)) { + isVML || this.transparentCorners || ctx.clearRect(left, top, size, size); + ctx[methodName](left, top, size, size); + } + }, + isControlVisible: function(controlName) { + return this._getControlsVisibility()[controlName]; + }, + setControlVisible: function(controlName, visible) { + this._getControlsVisibility()[controlName] = visible; + return this; + }, + setControlsVisibility: function(options) { + options || (options = {}); + for (var p in options) { + this.setControlVisible(p, options[p]); + } + return this; + }, + _getControlsVisibility: function() { + if (!this._controlsVisibility) { + this._controlsVisibility = { + tl: true, + tr: true, + br: true, + bl: true, + ml: true, + mt: true, + mr: true, + mb: true, + mtr: true + }; + } + return this._controlsVisibility; + } + }); +})(); - /** - * Returns {@link fabric.Rect} instance from an object representation - * @static - * @memberOf fabric.Rect - * @param object {Object} object to create an instance from - * @return {Object} instance of fabric.Rect - */ - fabric.Rect.fromObject = function(object) { - return new fabric.Rect(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + FX_DURATION: 500, + fxCenterObjectH: function(object, callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: object.get("left"), + endValue: this.getCenter().left, + duration: this.FX_DURATION, + onChange: function(value) { + object.set("left", value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + return this; + }, + fxCenterObjectV: function(object, callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: object.get("top"), + endValue: this.getCenter().top, + duration: this.FX_DURATION, + onChange: function(value) { + object.set("top", value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + return this; + }, + fxRemove: function(object, callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: object.get("opacity"), + endValue: 0, + duration: this.FX_DURATION, + onStart: function() { + object.set("active", false); + }, + onChange: function(value) { + object.set("opacity", value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + _this.remove(object); + onComplete(); + } + }); + return this; + } +}); +fabric.util.object.extend(fabric.Object.prototype, { + animate: function() { + if (arguments[0] && typeof arguments[0] === "object") { + var propsToAnimate = [], prop, skipCallbacks; + for (prop in arguments[0]) { + propsToAnimate.push(prop); + } + for (var i = 0, len = propsToAnimate.length; i < len; i++) { + prop = propsToAnimate[i]; + skipCallbacks = i !== len - 1; + this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); + } + } else { + this._animate.apply(this, arguments); + } + return this; + }, + _animate: function(property, to, options, skipCallbacks) { + var _this = this, propPair; + to = to.toString(); + if (!options) { + options = {}; + } else { + options = fabric.util.object.clone(options); + } + if (~property.indexOf(".")) { + propPair = property.split("."); + } + var currentValue = propPair ? this.get(propPair[0])[propPair[1]] : this.get(property); + if (!("from" in options)) { + options.from = currentValue; + } + if (~to.indexOf("=")) { + to = currentValue + parseFloat(to.replace("=", "")); + } else { + to = parseFloat(to); + } + fabric.util.animate({ + startValue: options.from, + endValue: to, + byValue: options.by, + easing: options.easing, + duration: options.duration, + abort: options.abort && function() { + return options.abort.call(_this); + }, + onChange: function(value) { + if (propPair) { + _this[propPair[0]][propPair[1]] = value; + } else { + _this.set(property, value); + } + if (skipCallbacks) return; + options.onChange && options.onChange(); + }, + onComplete: function() { + if (skipCallbacks) return; + _this.setCoords(); + options.onComplete && options.onComplete(); + } + }); + } +}); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - toFixed = fabric.util.toFixed; - - if (fabric.Polyline) { - fabric.warn('fabric.Polyline is already defined'); - return; - } - - /** - * Polyline class - * @class fabric.Polyline - * @extends fabric.Object - * @see {@link fabric.Polyline#initialize} for constructor definition - */ - fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polyline', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Constructor - * @param {Array} points Array of points (where each point is an object with x and y) - * @param {Object} [options] Options object - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - * @return {fabric.Polyline} thisArg - * @example - * var poly = new fabric.Polyline([ - * { x: 10, y: 10 }, - * { x: 50, y: 30 }, - * { x: 40, y: 70 }, - * { x: 60, y: 50 }, - * { x: 100, y: 150 }, - * { x: 40, y: 100 } - * ], { - * stroke: 'red', - * left: 100, - * top: 100 - * }); - */ - initialize: function(points, options, skipOffset) { - options = options || { }; - this.set('points', points); - this.callSuper('initialize', options); - this._calcDimensions(skipOffset); - }, - - /** - * @private - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - */ - _calcDimensions: function(skipOffset) { - return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], - markup = this._createBaseSVGMarkup(); - - 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), ' '); - } - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var point; - ctx.beginPath(); - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0, len = this.points.length; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - - this._renderFill(ctx); - 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); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.get('points').length; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, coordProps = { + x1: 1, + x2: 1, + y1: 1, + y2: 1 + }, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Line) { + fabric.warn("fabric.Line is already defined"); + return; } - }); + fabric.Line = fabric.util.createClass(fabric.Object, { + type: "line", + x1: 0, + y1: 0, + x2: 0, + y2: 0, + initialize: function(points, options) { + options = options || {}; + if (!points) { + points = [ 0, 0, 0, 0 ]; + } + this.callSuper("initialize", options); + this.set("x1", points[0]); + this.set("y1", points[1]); + this.set("x2", points[2]); + this.set("y2", points[3]); + this._setWidthHeight(options); + }, + _setWidthHeight: function(options) { + options || (options = {}); + this.width = Math.abs(this.x2 - this.x1) || 1; + this.height = Math.abs(this.y2 - this.y1) || 1; + this.left = "left" in options ? options.left : this._getLeftToOriginX(); + this.top = "top" in options ? options.top : this._getTopToOriginY(); + }, + _set: function(key, value) { + this[key] = value; + if (typeof coordProps[key] !== "undefined") { + this._setWidthHeight(); + } + return this; + }, + _getLeftToOriginX: makeEdgeToOriginGetter({ + origin: "originX", + axis1: "x1", + axis2: "x2", + dimension: "width" + }, { + nearest: "left", + center: "center", + farthest: "right" + }), + _getTopToOriginY: makeEdgeToOriginGetter({ + origin: "originY", + axis1: "y1", + axis2: "y2", + dimension: "height" + }, { + nearest: "top", + center: "center", + farthest: "bottom" + }), + _render: function(ctx) { + ctx.beginPath(); + var isInPathGroup = this.group && this.group.type === "path-group"; + if (isInPathGroup && !this.transformMatrix) { + var cp = this.getCenterPoint(); + ctx.translate(-this.group.width / 2 + cp.x, -this.group.height / 2 + cp.y); + } + if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { + var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1; + ctx.moveTo(this.width === 1 ? 0 : xMult * this.width / 2, this.height === 1 ? 0 : yMult * this.height / 2); + ctx.lineTo(this.width === 1 ? 0 : xMult * -1 * this.width / 2, this.height === 1 ? 0 : yMult * -1 * this.height / 2); + } + ctx.lineWidth = this.strokeWidth; + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + _renderDashedStroke: function(ctx) { + var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x = this.width === 1 ? 0 : xMult * this.width / 2, y = this.height === 1 ? 0 : yMult * this.height / 2; + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); + ctx.closePath(); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + x1: this.get("x1"), + y1: this.get("y1"), + x2: this.get("x2"), + y2: this.get("y2") + }); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")); + fabric.Line.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ]; + return new fabric.Line(points, extend(parsedAttributes, options)); + }; + fabric.Line.fromObject = function(object) { + var points = [ object.x1, object.y1, object.x2, object.y2 ]; + return new fabric.Line(points, object); + }; + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, axis1 = propertyNames.axis1, axis2 = propertyNames.axis2, dimension = propertyNames.dimension, nearest = originValues.nearest, center = originValues.center, farthest = originValues.farthest; + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) - * @static - * @memberOf fabric.Polyline - * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement - */ - fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + .5 * this.get(dimension); - /** - * Returns fabric.Polyline instance from an SVG element - * @static - * @memberOf fabric.Polyline - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromElement = function(element, options) { - if (!element) { - return null; + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; } - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); - - fabric.util.normalizePoints(points, options); - - return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polyline instance from an object representation - * @static - * @memberOf fabric.Polyline - * @param object {Object} object Object to create an instance from - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromObject = function(object) { - var points = object.points; - return new fabric.Polyline(points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - toFixed = fabric.util.toFixed; - - if (fabric.Polygon) { - fabric.warn('fabric.Polygon is already defined'); - return; - } - - /** - * Polygon class - * @class fabric.Polygon - * @extends fabric.Object - * @see {@link fabric.Polygon#initialize} for constructor definition - */ - fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polygon', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Constructor - * @param {Array} points Array of points - * @param {Object} [options] Options object - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - * @return {fabric.Polygon} thisArg - */ - initialize: function(points, options, skipOffset) { - options = options || { }; - this.points = points; - this.callSuper('initialize', options); - this._calcDimensions(skipOffset); - }, - - /** - * @private - * @param {Boolean} [skipOffset] Whether points offsetting should be skipped - */ - _calcDimensions: function(skipOffset) { - - var points = this.points, - minX = min(points, 'x'), - minY = min(points, 'y'), - maxX = max(points, 'x'), - maxY = max(points, 'y'); - - this.width = (maxX - minX) || 1; - this.height = (maxY - minY) || 1; - - this.minX = minX; - this.minY = minY; - - if (skipOffset) return; - - var halfWidth = this.width / 2 + this.minX, - halfHeight = this.height / 2 + this.minY; - - // change points to offset polygon into a bounding box - this.points.forEach(function(p) { - p.x -= halfWidth; - p.y -= halfHeight; - }, this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - points: this.points.concat() - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], - markup = this._createBaseSVGMarkup(); - - 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), ' '); - } - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var point; - ctx.beginPath(); - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0, len = this.points.length; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - this._renderFill(ctx); - if (this.stroke || this.strokeDashArray) { - ctx.closePath(); - 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] || 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 - */ - complexity: function() { - return this.points.length; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), piBy2 = Math.PI * 2, extend = fabric.util.object.extend; + if (fabric.Circle) { + fabric.warn("fabric.Circle is already defined."); + return; } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) - * @static - * @memberOf fabric.Polygon - * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement - */ - fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns {@link fabric.Polygon} instance from an SVG element - * @static - * @memberOf fabric.Polygon - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromElement = function(element, options) { - if (!element) { - return null; + fabric.Circle = fabric.util.createClass(fabric.Object, { + type: "circle", + radius: 0, + initialize: function(options) { + options = options || {}; + this.set("radius", options.radius || 0); + this.callSuper("initialize", options); + }, + _set: function(key, value) { + this.callSuper("_set", key, value); + if (key === "radius") { + this.setRadius(value); + } + return this; + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + radius: this.get("radius") + }); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx, noTransform) { + ctx.beginPath(); + 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.stroke && this._renderStroke(ctx); + }, + getRadiusX: function() { + return this.get("radius") * this.get("scaleX"); + }, + getRadiusY: function() { + return this.get("radius") * this.get("scaleY"); + }, + setRadius: function(value) { + this.radius = value; + this.set("width", value * 2).set("height", value * 2); + }, + complexity: function() { + return 1; + } + }); + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")); + fabric.Circle.fromElement = function(element, options) { + options || (options = {}); + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + if (!isValidRadius(parsedAttributes)) { + throw new Error("value of `r` attribute is required and can not be negative"); + } + if ("left" in parsedAttributes) { + parsedAttributes.left -= options.width / 2 || 0; + } + if ("top" in parsedAttributes) { + parsedAttributes.top -= options.height / 2 || 0; + } + var obj = new fabric.Circle(extend(parsedAttributes, options)); + obj.cx = parseFloat(element.getAttribute("cx")) || 0; + obj.cy = parseFloat(element.getAttribute("cy")) || 0; + return obj; + }; + function isValidRadius(attributes) { + return "radius" in attributes && attributes.radius > 0; } - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); - - fabric.util.normalizePoints(points, options); - - return new fabric.Polygon(points, extend(parsedAttributes, options), true); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polygon instance from an object representation - * @static - * @memberOf fabric.Polygon - * @param object {Object} object Object to create an instance from - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromObject = function(object) { - return new fabric.Polygon(object.points, object, true); - }; - -})(typeof exports !== 'undefined' ? exports : this); - + fabric.Circle.fromObject = function(object) { + return new fabric.Circle(object); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + if (fabric.Triangle) { + fabric.warn("fabric.Triangle is already defined"); + return; + } + fabric.Triangle = fabric.util.createClass(fabric.Object, { + type: "triangle", + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this.set("width", options.width || 100).set("height", options.height || 100); + }, + _render: function(ctx) { + var widthBy2 = this.width / 2, heightBy2 = this.height / 2; + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _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(); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + " " + heightBy2, "0 " + -heightBy2, widthBy2 + " " + heightBy2 ].join(","); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Triangle.fromObject = function(object) { + return new fabric.Triangle(object); + }; +})(typeof exports !== "undefined" ? exports : this); - 'use strict'; +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), piBy2 = Math.PI * 2, extend = fabric.util.object.extend; + if (fabric.Ellipse) { + fabric.warn("fabric.Ellipse is already defined."); + return; + } + fabric.Ellipse = fabric.util.createClass(fabric.Object, { + type: "ellipse", + rx: 0, + ry: 0, + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this.set("rx", options.rx || 0); + this.set("ry", options.ry || 0); + this.set("width", this.get("rx") * 2); + this.set("height", this.get("ry") * 2); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + rx: this.get("rx"), + ry: this.get("ry") + }); + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + render: function(ctx, noTransform) { + if (this.rx === 0 || this.ry === 0) return; + return this.callSuper("render", ctx, noTransform); + }, + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.save(); + ctx.globalAlpha = this.group ? ctx.globalAlpha * this.opacity : this.opacity; + if (this.transformMatrix && this.group) { + ctx.translate(this.cx, this.cy); + } + 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); + ctx.restore(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + complexity: function() { + return 1; + } + }); + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")); + fabric.Ellipse.fromElement = function(element, options) { + options || (options = {}); + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES), cx = parsedAttributes.left, cy = parsedAttributes.top; + if ("left" in parsedAttributes) { + parsedAttributes.left -= options.width / 2 || 0; + } + if ("top" in parsedAttributes) { + parsedAttributes.top -= options.height / 2 || 0; + } + var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); + ellipse.cx = cx || 0; + ellipse.cy = cy || 0; + return ellipse; + }; + fabric.Ellipse.fromObject = function(object) { + return new fabric.Ellipse(object); + }; +})(typeof exports !== "undefined" ? exports : this); - var fabric = global.fabric || (global.fabric = { }), - min = fabric.util.array.min, - max = fabric.util.array.max, - extend = fabric.util.object.extend, - _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc, - commandLengths = { +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + if (fabric.Rect) { + console.warn("fabric.Rect is already defined"); + return; + } + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push("rx", "ry", "x", "y"); + fabric.Rect = fabric.util.createClass(fabric.Object, { + stateProperties: stateProperties, + type: "rect", + rx: 0, + ry: 0, + x: 0, + y: 0, + strokeDashArray: null, + initialize: function(options) { + options = options || {}; + this.callSuper("initialize", options); + this._initRxRy(); + this.x = options.x || 0; + this.y = options.y || 0; + }, + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + _render: function(ctx) { + if (this.width === 1 && this.height === 1) { + ctx.fillRect(0, 0, 1, 1); + return; + } + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, w = this.width, h = this.height, x = -w / 2, y = -h / 2, isInPathGroup = this.group && this.group.type === "path-group", isRounded = rx !== 0 || ry !== 0, k = 1 - .5522847498; + ctx.beginPath(); + ctx.globalAlpha = isInPathGroup ? ctx.globalAlpha * this.opacity : this.opacity; + if (this.transformMatrix && isInPathGroup) { + ctx.translate(this.width / 2 + this.x, this.height / 2 + this.y); + } + if (!this.transformMatrix && isInPathGroup) { + ctx.translate(-this.group.width / 2 + this.width / 2 + this.x, -this.group.height / 2 + this.height / 2 + this.y); + } + ctx.moveTo(x + rx, y); + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + ctx.closePath(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; + 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(); + }, + _normalizeLeftTopProperties: function(parsedAttributes) { + if ("left" in parsedAttributes) { + this.set("left", parsedAttributes.left + this.getWidth() / 2); + } + this.set("x", parsedAttributes.left || 0); + if ("top" in parsedAttributes) { + this.set("top", parsedAttributes.top + this.getHeight() / 2); + } + this.set("y", parsedAttributes.top || 0); + return this; + }, + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper("toObject", propertiesToInclude), { + rx: this.get("rx") || 0, + ry: this.get("ry") || 0, + x: this.get("x"), + y: this.get("y") + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(); + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return 1; + } + }); + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")); + function _setDefaultLeftTopValues(attributes) { + attributes.left = attributes.left || 0; + attributes.top = attributes.top || 0; + return attributes; + } + fabric.Rect.fromElement = function(element, options) { + if (!element) { + return null; + } + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + parsedAttributes = _setDefaultLeftTopValues(parsedAttributes); + var rect = new fabric.Rect(extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)); + rect._normalizeLeftTopProperties(parsedAttributes); + return rect; + }; + fabric.Rect.fromObject = function(object) { + return new fabric.Rect(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), toFixed = fabric.util.toFixed; + if (fabric.Polyline) { + fabric.warn("fabric.Polyline is already defined"); + return; + } + fabric.Polyline = fabric.util.createClass(fabric.Object, { + type: "polyline", + points: null, + initialize: function(points, options, skipOffset) { + options = options || {}; + this.set("points", points); + this.callSuper("initialize", options); + this._calcDimensions(skipOffset); + }, + _calcDimensions: function(skipOffset) { + return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset); + }, + toObject: function(propertiesToInclude) { + return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); + }, + toSVG: function(reviver) { + var points = [], markup = this._createBaseSVGMarkup(); + 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), " "); + } + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx) { + var point; + ctx.beginPath(); + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + this._renderFill(ctx); + this._renderStroke(ctx); + }, + _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); + } + }, + complexity: function() { + return this.get("points").length; + } + }); + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + fabric.Polyline.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = {}); + var points = fabric.parsePointsAttribute(element.getAttribute("points")), parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); + fabric.util.normalizePoints(points, options); + return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options), true); + }; + fabric.Polyline.fromObject = function(object) { + var points = object.points; + return new fabric.Polyline(points, object, true); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, toFixed = fabric.util.toFixed; + if (fabric.Polygon) { + fabric.warn("fabric.Polygon is already defined"); + return; + } + fabric.Polygon = fabric.util.createClass(fabric.Object, { + type: "polygon", + points: null, + initialize: function(points, options, skipOffset) { + options = options || {}; + this.points = points; + this.callSuper("initialize", options); + this._calcDimensions(skipOffset); + }, + _calcDimensions: function(skipOffset) { + var points = this.points, minX = min(points, "x"), minY = min(points, "y"), maxX = max(points, "x"), maxY = max(points, "y"); + this.width = maxX - minX || 1; + this.height = maxY - minY || 1; + this.minX = minX; + this.minY = minY; + if (skipOffset) return; + var halfWidth = this.width / 2 + this.minX, halfHeight = this.height / 2 + this.minY; + this.points.forEach(function(p) { + p.x -= halfWidth; + p.y -= halfHeight; + }, this); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + points: this.points.concat() + }); + }, + toSVG: function(reviver) { + var points = [], markup = this._createBaseSVGMarkup(); + 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), " "); + } + markup.push("'); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _render: function(ctx) { + var point; + ctx.beginPath(); + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + this._renderFill(ctx); + if (this.stroke || this.strokeDashArray) { + ctx.closePath(); + this._renderStroke(ctx); + } + }, + _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(); + }, + complexity: function() { + return this.points.length; + } + }); + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + fabric.Polygon.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = {}); + var points = fabric.parsePointsAttribute(element.getAttribute("points")), parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); + fabric.util.normalizePoints(points, options); + return new fabric.Polygon(points, extend(parsedAttributes, options), true); + }; + fabric.Polygon.fromObject = function(object) { + return new fabric.Polygon(object.points, object, true); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, commandLengths = { m: 2, l: 2, h: 1, @@ -18279,7596 +9375,3464 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot q: 4, t: 2, a: 7 - }, - repeatedCommands = { - m: 'l', - M: 'L' - }; - - if (fabric.Path) { - fabric.warn('fabric.Path is already defined'); - return; - } - - /** - * @private - */ - function getX(item) { - if (item[0] === 'H') { - return item[1]; + }, repeatedCommands = { + m: "l", + M: "L" + }; + if (fabric.Path) { + fabric.warn("fabric.Path is already defined"); + return; } - return item[item.length - 2]; - } - - /** - * @private - */ - function getY(item) { - if (item[0] === 'V') { - return item[1]; + function getX(item) { + if (item[0] === "H") { + return item[1]; + } + return item[item.length - 2]; } - return item[item.length - 1]; - } - - /** - * Path class - * @class fabric.Path - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.Path#initialize} for constructor definition - */ - fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path', - - /** - * Array of path points - * @type Array - * @default - */ - path: null, - - /** - * Constructor - * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) - * @param {Object} [options] Options object - * @return {fabric.Path} thisArg - */ - initialize: function(path, options) { - options = options || { }; - - this.setOptions(options); - - if (!path) { - throw new Error('`path` argument is required'); - } - - var fromArray = _toString.call(path) === '[object Array]'; - - this.path = fromArray - ? path - // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) - : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); - - if (!this.path) return; - - if (!fromArray) { - this.path = this._parsePath(); - } - this._initializePath(options); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initializePath: function (options) { - var isWidthSet = 'width' in options && options.width != null, - isHeightSet = 'height' in options && options.width != null, - isLeftSet = 'left' in options, - isTopSet = 'top' in options, - origLeft = isLeftSet ? this.left : 0, - origTop = isTopSet ? this.top : 0; - - if (!isWidthSet || !isHeightSet) { - extend(this, this._parseDimensions()); - if (isWidthSet) { - this.width = options.width; + function getY(item) { + if (item[0] === "V") { + return item[1]; } - if (isHeightSet) { - this.height = options.height; - } - } - else { //Set center location relative to given height/width if not specified - if (!isTopSet) { - this.top = this.height / 2; - } - if (!isLeftSet) { - this.left = this.width / 2; - } - } - this.pathOffset = this.pathOffset || - // Save top-left coords as offset - this._calculatePathOffset(origLeft, origTop); - }, - - /** - * @private - * @param {Boolean} positionSet When false, path offset is returned otherwise 0 - */ - _calculatePathOffset: function (origLeft, origTop) { - return { - x: this.left - origLeft - (this.width / 2), - y: this.top - origTop - (this.height / 2) - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render path on - */ - _render: function(ctx) { - var current, // current instruction - previous = null, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - tempControlX, - tempControlY, - l = -((this.width / 2) + this.pathOffset.x), - t = -((this.height / 2) + this.pathOffset.y), - methodName; - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'v': // vertical lineto, relative - y += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'V': // verical lineto, absolute - y = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - // draw a line if previous command was moveTo as well (otherwise, it will have no effect) - methodName = (previous && (previous[0] === 'm' || previous[0] === 'M')) - ? 'lineTo' - : 'moveTo'; - ctx[methodName](x + l, y + t); - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - // draw a line if previous command was moveTo as well (otherwise, it will have no effect) - methodName = (previous && (previous[0] === 'm' || previous[0] === 'M')) - ? 'lineTo' - : 'moveTo'; - ctx[methodName](x + l, y + t); - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - ctx.bezierCurveTo( - x + current[1] + l, // x1 - y + current[2] + t, // y1 - controlX + l, // x2 - controlY + t, // y2 - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - ctx.bezierCurveTo( - current[1] + l, - current[2] + t, - controlX + l, - controlY + t, - x + l, - y + t - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - // calculate reflection of previous control points - controlX = controlX ? (2 * x - controlX) : x; - controlY = controlY ? (2 * y - controlY) : y; - - ctx.bezierCurveTo( - controlX + l, - controlY + t, - x + current[1] + l, - y + current[2] + t, - tempX + l, - tempY + t - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - ctx.bezierCurveTo( - controlX + l, - controlY + t, - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - controlX = x + current[1]; - controlY = y + current[2]; - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - - ctx.quadraticCurveTo( - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = current[1]; - controlY = current[2]; - break; - - case 't': // shorthand quadraticCurveTo, relative - - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; + return item[item.length - 1]; + } + fabric.Path = fabric.util.createClass(fabric.Object, { + type: "path", + path: null, + initialize: function(path, options) { + options = options || {}; + this.setOptions(options); + if (!path) { + throw new Error("`path` argument is required"); } - else if (previous[0] === 't') { - // calculate reflection of previous control points for t - controlX = 2 * x - tempControlX; - controlY = 2 * y - tempControlY; + var fromArray = _toString.call(path) === "[object Array]"; + this.path = fromArray ? path : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + if (!this.path) return; + if (!fromArray) { + this.path = this._parsePath(); } - else if (previous[0] === 'q') { - // calculate reflection of previous control points for q - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; + this._initializePath(options); + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); } + }, + _initializePath: function(options) { + var isWidthSet = "width" in options && options.width != null, isHeightSet = "height" in options && options.width != null, isLeftSet = "left" in options, isTopSet = "top" in options, origLeft = isLeftSet ? this.left : 0, origTop = isTopSet ? this.top : 0; + if (!isWidthSet || !isHeightSet) { + extend(this, this._parseDimensions()); + if (isWidthSet) { + this.width = options.width; + } + if (isHeightSet) { + this.height = options.height; + } + } else { + if (!isTopSet) { + this.top = this.height / 2; + } + if (!isLeftSet) { + this.left = this.width / 2; + } + } + this.pathOffset = this.pathOffset || this._calculatePathOffset(origLeft, origTop); + }, + _calculatePathOffset: function(origLeft, origTop) { + return { + x: this.left - origLeft - this.width / 2, + y: this.top - origTop - this.height / 2 + }; + }, + _render: function(ctx) { + var current, previous = null, x = 0, y = 0, controlX = 0, controlY = 0, tempX, tempY, tempControlX, tempControlY, l = -(this.width / 2 + this.pathOffset.x), t = -(this.height / 2 + this.pathOffset.y), methodName; + for (var i = 0, len = this.path.length; i < len; ++i) { + current = this.path[i]; + switch (current[0]) { + case "l": + x += current[1]; + y += current[2]; + ctx.lineTo(x + l, y + t); + break; - tempControlX = controlX; - tempControlY = controlY; + case "L": + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = x + current[1]; - controlY = y + current[2]; - break; + case "h": + x += current[1]; + ctx.lineTo(x + l, y + t); + break; - case 'T': - tempX = current[1]; - tempY = current[2]; + case "H": + x = current[1]; + ctx.lineTo(x + l, y + t); + break; - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; + case "v": + y += current[1]; + ctx.lineTo(x + l, y + t); + break; - case 'a': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x + l, - current[7] + y + t - ]); - x += current[6]; - y += current[7]; - break; + case "V": + y = current[1]; + ctx.lineTo(x + l, y + t); + break; - case 'A': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + l, - current[7] + t - ]); - x = current[6]; - y = current[7]; - break; + case "m": + x += current[1]; + y += current[2]; + methodName = previous && (previous[0] === "m" || previous[0] === "M") ? "lineTo" : "moveTo"; + ctx[methodName](x + l, y + t); + break; - case 'z': - case 'Z': + case "M": + x = current[1]; + y = current[2]; + methodName = previous && (previous[0] === "m" || previous[0] === "M") ? "lineTo" : "moveTo"; + ctx[methodName](x + l, y + t); + break; + + case "c": + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + ctx.bezierCurveTo(x + current[1] + l, y + current[2] + t, controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "C": + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo(current[1] + l, current[2] + t, controlX + l, controlY + t, x + l, y + t); + break; + + case "s": + tempX = x + current[3]; + tempY = y + current[4]; + controlX = controlX ? 2 * x - controlX : x; + controlY = controlY ? 2 * y - controlY : y; + ctx.bezierCurveTo(controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t); + controlX = x + current[1]; + controlY = y + current[2]; + x = tempX; + y = tempY; + break; + + case "S": + tempX = current[3]; + tempY = current[4]; + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.bezierCurveTo(controlX + l, controlY + t, current[1] + l, current[2] + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case "q": + tempX = x + current[3]; + tempY = y + current[4]; + controlX = x + current[1]; + controlY = y + current[2]; + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "Q": + tempX = current[3]; + tempY = current[4]; + ctx.quadraticCurveTo(current[1] + l, current[2] + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case "t": + tempX = x + current[1]; + tempY = y + current[2]; + if (previous[0].match(/[QqTt]/) === null) { + controlX = x; + controlY = y; + } else if (previous[0] === "t") { + controlX = 2 * x - tempControlX; + controlY = 2 * y - tempControlY; + } else if (previous[0] === "q") { + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + tempControlX = controlX; + tempControlY = controlY; + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + controlX = x + current[1]; + controlY = y + current[2]; + break; + + case "T": + tempX = current[1]; + tempY = current[2]; + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); + x = tempX; + y = tempY; + break; + + case "a": + drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + x + l, current[7] + y + t ]); + x += current[6]; + y += current[7]; + break; + + case "A": + drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + l, current[7] + t ]); + x = current[6]; + y = current[7]; + break; + + case "z": + case "Z": + ctx.closePath(); + break; + } + previous = current; + } + }, + render: function(ctx, noTransform) { + if (!this.visible) return; + ctx.save(); + var m = this.transformMatrix; + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + ctx.beginPath(); + this._render(ctx); + this._renderFill(ctx); + this._renderStroke(ctx); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + toString: function() { + return "#"; + }, + toObject: function(propertiesToInclude) { + var o = extend(this.callSuper("toObject", propertiesToInclude), { + path: this.path.map(function(item) { + return item.slice(); + }), + pathOffset: this.pathOffset + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + if (this.transformMatrix) { + o.transformMatrix = this.transformMatrix; + } + return o; + }, + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.path = this.sourcePath; + } + delete o.sourcePath; + return o; + }, + toSVG: function(reviver) { + var chunks = [], markup = this._createBaseSVGMarkup(); + for (var i = 0, len = this.path.length; i < len; i++) { + chunks.push(this.path[i].join(" ")); + } + var path = chunks.join(" "); + markup.push('', "", ""); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + complexity: function() { + return this.path.length; + }, + _parsePath: function() { + var result = [], coords = [], currentPath, parsed, re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi, match, coordsStr; + for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { + currentPath = this.path[i]; + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + while (match = re.exec(coordsStr)) { + coords.push(match[0]); + } + coordsParsed = [ currentPath.charAt(0) ]; + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + var command = coordsParsed[0], commandLength = commandLengths[command.toLowerCase()], repeatedCommand = repeatedCommands[command] || command; + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } else { + result.push(coordsParsed); + } + } + return result; + }, + _parseDimensions: function() { + var aX = [], aY = [], previous = {}; + this.path.forEach(function(item, i) { + this._getCoordsFromCommand(item, i, aX, aY, previous); + }, this); + var minX = min(aX), minY = min(aY), maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, deltaY = maxY - minY, o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; + return o; + }, + _getCoordsFromCommand: function(item, i, aX, aY, previous) { + var isLowerCase = false; + if (item[0] !== "H") { + previous.x = i === 0 ? getX(item) : getX(this.path[i - 1]); + } + if (item[0] !== "V") { + previous.y = i === 0 ? getY(item) : getY(this.path[i - 1]); + } + if (item[0] === item[0].toLowerCase()) { + isLowerCase = true; + } + var xy = this._getXY(item, isLowerCase, previous), val; + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } + val = parseInt(xy.y, 10); + if (!isNaN(val)) { + aY.push(val); + } + }, + _getXY: function(item, isLowerCase, previous) { + var x = isLowerCase ? previous.x + getX(item) : item[0] === "V" ? previous.x : getX(item), y = isLowerCase ? previous.y + getY(item) : item[0] === "H" ? previous.y : getY(item); + return { + x: x, + y: y + }; + } + }); + fabric.Path.fromObject = function(object, callback) { + if (typeof object.path === "string") { + fabric.loadSVGFromURL(object.path, function(elements) { + var path = elements[0], pathUrl = object.path; + delete object.path; + fabric.util.object.extend(path, object); + path.setSourcePath(pathUrl); + callback(path); + }); + } else { + callback(new fabric.Path(object.path, object)); + } + }; + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat([ "d" ]); + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + fabric.Path.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, invoke = fabric.util.array.invoke, parentToObject = fabric.Object.prototype.toObject; + if (fabric.PathGroup) { + fabric.warn("fabric.PathGroup is already defined"); + return; + } + fabric.PathGroup = fabric.util.createClass(fabric.Path, { + type: "path-group", + fill: "", + initialize: function(paths, options) { + options = options || {}; + this.paths = paths || []; + for (var i = this.paths.length; i--; ) { + this.paths[i].group = this; + } + this.setOptions(options); + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + this.setCoords(); + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + render: function(ctx) { + if (!this.visible) return; + ctx.save(); + var m = this.transformMatrix; + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + this.transform(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + for (var i = 0, l = this.paths.length; i < l; ++i) { + this.paths[i].render(ctx, true); + } + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + _set: function(prop, value) { + if (prop === "fill" && value && this.isSameColor()) { + var i = this.paths.length; + while (i--) { + this.paths[i]._set(prop, value); + } + } + return this.callSuper("_set", prop, value); + }, + toObject: function(propertiesToInclude) { + var o = extend(parentToObject.call(this, propertiesToInclude), { + paths: invoke(this.getObjects(), "toObject", propertiesToInclude) + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + return o; + }, + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.paths = this.sourcePath; + } + return o; + }, + toSVG: function(reviver) { + var objects = this.getObjects(), markup = [ "" ]; + for (var i = 0, len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + markup.push(""); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + toString: function() { + return "#"; + }, + isSameColor: function() { + var firstPathFill = (this.getObjects()[0].get("fill") || "").toLowerCase(); + return this.getObjects().every(function(path) { + return (path.get("fill") || "").toLowerCase() === firstPathFill; + }); + }, + complexity: function() { + return this.paths.reduce(function(total, path) { + return total + (path && path.complexity ? path.complexity() : 0); + }, 0); + }, + getObjects: function() { + return this.paths; + } + }); + fabric.PathGroup.fromObject = function(object, callback) { + if (typeof object.paths === "string") { + fabric.loadSVGFromURL(object.paths, function(elements) { + var pathUrl = object.paths; + delete object.paths; + var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); + callback(pathGroup); + }); + } else { + fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { + delete object.paths; + callback(new fabric.PathGroup(enlivenedObjects, object)); + }); + } + }; + fabric.PathGroup.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, invoke = fabric.util.array.invoke, degreesToRadians = fabric.util.degreesToRadians; + if (fabric.Group) { + return; + } + var _lockProperties = { + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockUniScaling: true + }; + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, { + type: "group", + initialize: function(objects, options) { + options = options || {}; + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + this.originalState = {}; + this.callSuper("initialize"); + if (options) { + extend(this, options); + } + this._setOpacityIfSame(); + this.saveCoords(); + }, + _updateObjectsCoords: function() { + this.forEachObject(this._updateObjectCoords, this); + }, + _updateObjectCoords: function(object) { + var objectLeft = object.getLeft(), objectTop = object.getTop(); + object.set({ + originalLeft: objectLeft, + originalTop: objectTop, + left: objectLeft - this.left, + top: objectTop - this.top + }); + object.setCoords(); + object.__origHasControls = object.hasControls; + object.hasControls = false; + }, + toString: function() { + return "#"; + }, + addWithUpdate: function(object) { + this._restoreObjectsState(); + this._objects.push(object); + object.group = this; + this.forEachObject(this._setObjectActive, this); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + _setObjectActive: function(object) { + object.set("active", true); + object.group = this; + }, + removeWithUpdate: function(object) { + this._moveFlippedObject(object); + this._restoreObjectsState(); + this.forEachObject(this._setObjectActive, this); + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + _onObjectAdded: function(object) { + object.group = this; + }, + _onObjectRemoved: function(object) { + delete object.group; + object.set("active", false); + }, + delegatedProperties: { + fill: true, + opacity: true, + fontFamily: true, + fontWeight: true, + fontSize: true, + fontStyle: true, + lineHeight: true, + textDecoration: true, + textAlign: true, + backgroundColor: true + }, + _set: function(key, value) { + if (key in this.delegatedProperties) { + var i = this._objects.length; + this[key] = value; + while (i--) { + this._objects[i].set(key, value); + } + } else { + this[key] = value; + } + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + objects: invoke(this._objects, "toObject", propertiesToInclude) + }); + }, + render: function(ctx, noTransform) { + if (!this.visible) return; + ctx.save(); + this.clipTo && fabric.util.clipContext(this, ctx); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._renderObject(this._objects[i], ctx); + } + this.clipTo && ctx.restore(); + ctx.restore(); + }, + _renderControls: function(ctx, noTransform) { + this.callSuper("_renderControls", ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + _renderObject: function(object, ctx) { + var originalHasRotatingPoint = object.hasRotatingPoint; + if (!object.visible) return; + object.hasRotatingPoint = false; + object.render(ctx); + object.hasRotatingPoint = originalHasRotatingPoint; + }, + _restoreObjectsState: function() { + this._objects.forEach(this._restoreObjectState, this); + return this; + }, + _moveFlippedObject: function(object) { + var oldOriginX = object.get("originX"), oldOriginY = object.get("originY"), center = object.getCenterPoint(); + object.set({ + originX: "center", + originY: "center", + left: center.x, + top: center.y + }); + this._toggleFlipping(object); + var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); + object.set({ + originX: oldOriginX, + originY: oldOriginY, + left: newOrigin.x, + top: newOrigin.y + }); + return this; + }, + _toggleFlipping: function(object) { + if (this.flipX) { + object.toggle("flipX"); + object.set("left", -object.get("left")); + object.setAngle(-object.getAngle()); + } + if (this.flipY) { + object.toggle("flipY"); + object.set("top", -object.get("top")); + object.setAngle(-object.getAngle()); + } + }, + _restoreObjectState: function(object) { + this._setObjectPosition(object); + object.setCoords(); + object.hasControls = object.__origHasControls; + delete object.__origHasControls; + object.set("active", false); + object.setCoords(); + delete object.group; + return this; + }, + _setObjectPosition: function(object) { + var groupLeft = this.getLeft(), groupTop = this.getTop(), rotated = this._getRotatedLeftTop(object); + object.set({ + angle: object.getAngle() + this.getAngle(), + left: groupLeft + rotated.left, + top: groupTop + rotated.top, + scaleX: object.get("scaleX") * this.get("scaleX"), + scaleY: object.get("scaleY") * this.get("scaleY") + }); + }, + _getRotatedLeftTop: function(object) { + var groupAngle = this.getAngle() * (Math.PI / 180); + return { + left: -Math.sin(groupAngle) * object.getTop() * this.get("scaleY") + Math.cos(groupAngle) * object.getLeft() * this.get("scaleX"), + top: Math.cos(groupAngle) * object.getTop() * this.get("scaleY") + Math.sin(groupAngle) * object.getLeft() * this.get("scaleX") + }; + }, + destroy: function() { + this._objects.forEach(this._moveFlippedObject, this); + return this._restoreObjectsState(); + }, + saveCoords: function() { + this._originalLeft = this.get("left"); + this._originalTop = this.get("top"); + return this; + }, + hasMoved: function() { + return this._originalLeft !== this.get("left") || this._originalTop !== this.get("top"); + }, + setObjectsCoords: function() { + this.forEachObject(function(object) { + object.setCoords(); + }); + return this; + }, + _setOpacityIfSame: function() { + var objects = this.getObjects(), firstValue = objects[0] ? objects[0].get("opacity") : 1, isSameOpacity = objects.every(function(o) { + return o.get("opacity") === firstValue; + }); + if (isSameOpacity) { + this.opacity = firstValue; + } + }, + _calcBounds: function(onlyWidthHeight) { + var aX = [], aY = [], o; + for (var i = 0, len = this._objects.length; i < len; ++i) { + o = this._objects[i]; + o.setCoords(); + for (var prop in o.oCoords) { + aX.push(o.oCoords[prop].x); + aY.push(o.oCoords[prop].y); + } + } + this.set(this._getBounds(aX, aY, onlyWidthHeight)); + }, + _getBounds: function(aX, aY, onlyWidthHeight) { + var ivt; + if (this.canvas) { + ivt = fabric.util.invertTransform(this.getViewportTransform()); + } + var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { + width: maxXY.x - minXY.x || 0, + height: maxXY.y - minXY.y || 0 + }; + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; + }, + toSVG: function(reviver) { + var markup = [ "' ]; + for (var i = 0, len = this._objects.length; i < len; i++) { + markup.push(this._objects[i].toSVG(reviver)); + } + markup.push(""); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + get: function(prop) { + if (prop in _lockProperties) { + if (this[prop]) { + return this[prop]; + } else { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i][prop]) { + return true; + } + } + return false; + } + } else { + if (prop in this.delegatedProperties) { + return this._objects[0] && this._objects[0].get(prop); + } + return this[prop]; + } + } + }); + fabric.Group.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.Group(enlivenedObjects, object)); + }); + }; + fabric.Group.async = true; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var extend = fabric.util.object.extend; + if (!global.fabric) { + global.fabric = {}; + } + if (global.fabric.Image) { + fabric.warn("fabric.Image is already defined."); + return; + } + fabric.Image = fabric.util.createClass(fabric.Object, { + type: "image", + crossOrigin: "", + initialize: function(element, options) { + options || (options = {}); + this.filters = []; + this.callSuper("initialize", options); + this._initElement(element, options); + this._initConfig(options); + if (options.filters) { + this.filters = options.filters; + this.applyFilters(); + } + }, + getElement: function() { + return this._element; + }, + setElement: function(element, callback) { + this._element = element; + this._originalElement = element; + this._initConfig(); + if (this.filters.length !== 0) { + this.applyFilters(callback); + } + return this; + }, + setCrossOrigin: function(value) { + this.crossOrigin = value; + this._element.crossOrigin = value; + return this; + }, + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.width, + height: element.height + }; + }, + render: function(ctx, noTransform) { + if (!this.visible) return; + ctx.save(); + var m = this.transformMatrix, isInPathGroup = this.group && this.group.type === "path-group"; + if (isInPathGroup) { + ctx.translate(-this.group.width / 2, -this.group.height / 2); + } + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + if (isInPathGroup) { + ctx.translate(this.width / 2, this.height / 2); + } + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + this._render(ctx); + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + this._renderStroke(ctx); + this.clipTo && ctx.restore(); + ctx.restore(); + }, + _stroke: function(ctx) { + ctx.save(); + this._setStrokeStyles(ctx); + ctx.beginPath(); + ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); ctx.closePath(); - break; - } - previous = current; - } - }, - - /** - * Renders path on a specified context - * @param {CanvasRenderingContext2D} ctx context to render path on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) return; - - ctx.save(); - var m = this.transformMatrix; - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - ctx.beginPath(); - - this._render(ctx); - this._renderFill(ctx); - this._renderStroke(ctx); - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Returns string representation of an instance - * @return {String} string representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path.map(function(item) { return item.slice() }), - pathOffset: this.pathOffset - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - if (this.transformMatrix) { - o.transformMatrix = this.transformMatrix; - } - return o; - }, - - /** - * Returns dataless object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.path = this.sourcePath; - } - delete o.sourcePath; - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var chunks = [], - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - - markup.push( - '', - '', - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns number representation of an instance complexity - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.path.length; - }, - - /** - * @private - */ - _parsePath: function() { - var result = [ ], - coords = [ ], - currentPath, - parsed, - re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, - match, - coordsStr; - - for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { - currentPath = this.path[i]; - - coordsStr = currentPath.slice(1).trim(); - coords.length = 0; - - while ((match = re.exec(coordsStr))) { - coords.push(match[0]); - } - - coordsParsed = [ currentPath.charAt(0) ]; - - for (var j = 0, jlen = coords.length; j < jlen; j++) { - parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - coordsParsed.push(parsed); - } - } - - var command = coordsParsed[0], - commandLength = commandLengths[command.toLowerCase()], - repeatedCommand = repeatedCommands[command] || command; - - if (coordsParsed.length - 1 > commandLength) { - for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); - command = repeatedCommand; - } - } - else { - result.push(coordsParsed); - } - } - - return result; - }, - - /** - * @private - */ - _parseDimensions: function() { - var aX = [], - aY = [], - previous = { }; - - this.path.forEach(function(item, i) { - this._getCoordsFromCommand(item, i, aX, aY, previous); - }, this); - - var minX = min(aX), - minY = min(aY), - maxX = max(aX), - maxY = max(aY), - deltaX = maxX - minX, - deltaY = maxY - minY, - - o = { - left: this.left + (minX + deltaX / 2), - top: this.top + (minY + deltaY / 2), - width: deltaX, - height: deltaY - }; - - return o; - }, - - _getCoordsFromCommand: function(item, i, aX, aY, previous) { - var isLowerCase = false; - - if (item[0] !== 'H') { - previous.x = (i === 0) ? getX(item) : getX(this.path[i - 1]); - } - if (item[0] !== 'V') { - previous.y = (i === 0) ? getY(item) : getY(this.path[i - 1]); - } - - // lowercased letter denotes relative position; - // transform to absolute - if (item[0] === item[0].toLowerCase()) { - isLowerCase = true; - } - - var xy = this._getXY(item, isLowerCase, previous), - val; - - val = parseInt(xy.x, 10); - if (!isNaN(val)) { - aX.push(val); - } - - val = parseInt(xy.y, 10); - if (!isNaN(val)) { - aY.push(val); - } - }, - - _getXY: function(item, isLowerCase, previous) { - - // last 2 items in an array of coordinates are the actualy x/y (except H/V), collect them - // TODO (kangax): support relative h/v commands - - var x = isLowerCase - ? previous.x + getX(item) - : item[0] === 'V' - ? previous.x - : getX(item), - - y = isLowerCase - ? previous.y + getY(item) - : item[0] === 'H' - ? previous.y - : getY(item); - - return { x: x, y: y }; - } - }); - - /** - * Creates an instance of fabric.Path from an object - * @static - * @memberOf fabric.Path - * @param {Object} object - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - */ - fabric.Path.fromObject = function(object, callback) { - if (typeof object.path === 'string') { - fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0], - pathUrl = object.path; - - delete object.path; - - fabric.util.object.extend(path, object); - path.setSourcePath(pathUrl); - - callback(path); - }); - } - else { - callback(new fabric.Path(object.path, object)); - } - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) - * @static - * @memberOf fabric.Path - * @see http://www.w3.org/TR/SVG/paths.html#PathElement - */ - fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); - - /** - * Creates an instance of fabric.Path from an SVG element - * @static - * @memberOf fabric.Path - * @param {SVGElement} element to parse - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - * @param {Object} [options] Options object - */ - fabric.Path.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); - callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Path - * @type Boolean - * @default - */ - fabric.Path.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - invoke = fabric.util.array.invoke, - parentToObject = fabric.Object.prototype.toObject; - - if (fabric.PathGroup) { - fabric.warn('fabric.PathGroup is already defined'); - return; - } - - /** - * Path group class - * @class fabric.PathGroup - * @extends fabric.Path - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} - * @see {@link fabric.PathGroup#initialize} for constructor definition - */ - fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path-group', - - /** - * Fill value - * @type String - * @default - */ - fill: '', - - /** - * Constructor - * @param {Array} paths - * @param {Object} [options] Options object - * @return {fabric.PathGroup} thisArg - */ - initialize: function(paths, options) { - - options = options || { }; - this.paths = paths || [ ]; - - for (var i = this.paths.length; i--; ) { - this.paths[i].group = this; - } - - this.setOptions(options); - - if (options.widthAttr) { - this.scaleX = options.widthAttr / options.width; - } - if (options.heightAttr) { - this.scaleY = options.heightAttr / options.height; - } - - this.setCoords(); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * Renders this group on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render this instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) return; - - ctx.save(); - - var m = this.transformMatrix; - - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - this.transform(ctx); - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - for (var i = 0, l = this.paths.length; i < l; ++i) { - this.paths[i].render(ctx, true); - } - this.clipTo && ctx.restore(); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * Sets certain property to a certain value - * @param {String} prop - * @param {Any} value - * @return {fabric.PathGroup} thisArg - */ - _set: function(prop, value) { - - if (prop === 'fill' && value && this.isSameColor()) { - var i = this.paths.length; - while (i--) { - this.paths[i]._set(prop, value); - } - } - - return this.callSuper('_set', prop, value); - }, - - /** - * Returns object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(parentToObject.call(this, propertiesToInclude), { - paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) - }); - if (this.sourcePath) { - o.sourcePath = this.sourcePath; - } - return o; - }, - - /** - * Returns dataless object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} dataless object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.paths = this.sourcePath; - } - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var objects = this.getObjects(), - markup = [ - '' - ]; - - for (var i = 0, len = objects.length; i < len; i++) { - markup.push(objects[i].toSVG(reviver)); - } - markup.push(''); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns a string representation of this path group - * @return {String} string representation of an object - */ - toString: function() { - return '#'; - }, - - /** - * Returns true if all paths in this group are of same color - * @return {Boolean} true if all paths are of the same color (`fill`) - */ - isSameColor: function() { - var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); - return this.getObjects().every(function(path) { - return (path.get('fill') || '').toLowerCase() === firstPathFill; - }); - }, - - /** - * Returns number representation of object's complexity - * @return {Number} complexity - */ - complexity: function() { - return this.paths.reduce(function(total, path) { - return total + ((path && path.complexity) ? path.complexity() : 0); - }, 0); - }, - - /** - * Returns all paths in this path group - * @return {Array} array of path objects included in this path group - */ - getObjects: function() { - return this.paths; - } - }); - - /** - * Creates fabric.PathGroup instance from an object representation - * @static - * @memberOf fabric.PathGroup - * @param {Object} object Object to create an instance from - * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created - */ - fabric.PathGroup.fromObject = function(object, callback) { - if (typeof object.paths === 'string') { - fabric.loadSVGFromURL(object.paths, function (elements) { - - var pathUrl = object.paths; - delete object.paths; - - var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); - - callback(pathGroup); - }); - } - else { - fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { - delete object.paths; - callback(new fabric.PathGroup(enlivenedObjects, object)); - }); - } - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.PathGroup - * @type Boolean - * @default - */ - fabric.PathGroup.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global){ - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - invoke = fabric.util.array.invoke, - degreesToRadians = fabric.util.degreesToRadians; - - if (fabric.Group) { - return; - } - - // lock-related properties, for use in fabric.Group#get - // to enable locking behavior on group - // when one of its objects has lock-related properties set - var _lockProperties = { - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockUniScaling: true - }; - - /** - * Group class - * @class fabric.Group - * @extends fabric.Object - * @mixes fabric.Collection - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} - * @see {@link fabric.Group#initialize} for constructor definition - */ - fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'group', - - /** - * Constructor - * @param {Object} objects Group objects - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(objects, options) { - options = options || { }; - - this._objects = objects || []; - for (var i = this._objects.length; i--; ) { - this._objects[i].group = this; - } - - this.originalState = { }; - this.callSuper('initialize'); - - if (options) { - extend(this, options); - } - this._setOpacityIfSame(); - this.saveCoords(); - }, - - /** - * @private - */ - _updateObjectsCoords: function() { - this.forEachObject(this._updateObjectCoords, this); - }, - - /** - * @private - */ - _updateObjectCoords: function(object) { - var objectLeft = object.getLeft(), - objectTop = object.getTop(); - - object.set({ - originalLeft: objectLeft, - originalTop: objectTop, - left: objectLeft - this.left, - top: objectTop - this.top - }); - - object.setCoords(); - - // do not display corners of objects enclosed in a group - object.__origHasControls = object.hasControls; - object.hasControls = false; - }, - - /** - * Returns string represenation of a group - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Adds an object to a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - addWithUpdate: function(object) { - this._restoreObjectsState(); - this._objects.push(object); - object.group = this; - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - this._calcBounds(); - this._updateObjectsCoords(); - return this; - }, - - /** - * @private - */ - _setObjectActive: function(object) { - object.set('active', true); - object.group = this; - }, - - /** - * Removes an object from a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - removeWithUpdate: function(object) { - this._moveFlippedObject(object); - this._restoreObjectsState(); - - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - - this.remove(object); - this._calcBounds(); - this._updateObjectsCoords(); - - return this; - }, - - /** - * @private - */ - _onObjectAdded: function(object) { - object.group = this; - }, - - /** - * @private - */ - _onObjectRemoved: function(object) { - delete object.group; - object.set('active', false); - }, - - /** - * Properties that are delegated to group objects when reading/writing - * @param {Object} delegatedProperties - */ - delegatedProperties: { - fill: true, - opacity: true, - fontFamily: true, - fontWeight: true, - fontSize: true, - fontStyle: true, - lineHeight: true, - textDecoration: true, - textAlign: true, - backgroundColor: true - }, - - /** - * @private - */ - _set: function(key, value) { - if (key in this.delegatedProperties) { - var i = this._objects.length; - this[key] = value; - while (i--) { - this._objects[i].set(key, value); - } - } - else { - this[key] = value; - } - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - objects: invoke(this._objects, 'toObject', propertiesToInclude) - }); - }, - - /** - * Renders instance on a given context - * @param {CanvasRenderingContext2D} ctx context to render instance on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) return; - - ctx.save(); - this.clipTo && fabric.util.clipContext(this, ctx); - - // the array is now sorted in order of highest first, so start from end - for (var i = 0, len = this._objects.length; i < len; i++) { - this._renderObject(this._objects[i], ctx); - } - - this.clipTo && ctx.restore(); - - ctx.restore(); - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - this.callSuper('_renderControls', ctx, noTransform); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i]._renderControls(ctx); - } - }, - - /** - * @private - */ - _renderObject: function(object, ctx) { - var originalHasRotatingPoint = object.hasRotatingPoint; - - // do not render if object is not visible - if (!object.visible) return; - - object.hasRotatingPoint = false; - - object.render(ctx); - - object.hasRotatingPoint = originalHasRotatingPoint; - }, - - /** - * Retores original state of each of group objects (original state is that which was before group was created). - * @private - * @return {fabric.Group} thisArg - * @chainable - */ - _restoreObjectsState: function() { - this._objects.forEach(this._restoreObjectState, this); - return this; - }, - - /** - * Moves a flipped object to the position where it's displayed - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _moveFlippedObject: function(object) { - var oldOriginX = object.get('originX'), - oldOriginY = object.get('originY'), - center = object.getCenterPoint(); - - object.set({ - originX: 'center', - originY: 'center', - left: center.x, - top: center.y - }); - - this._toggleFlipping(object); - - var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); - - object.set({ - originX: oldOriginX, - originY: oldOriginY, - left: newOrigin.x, - top: newOrigin.y - }); - - return this; - }, - - /** - * @private - */ - _toggleFlipping: function(object) { - if (this.flipX) { - object.toggle('flipX'); - object.set('left', -object.get('left')); - object.setAngle(-object.getAngle()); - } - if (this.flipY) { - object.toggle('flipY'); - object.set('top', -object.get('top')); - object.setAngle(-object.getAngle()); - } - }, - - /** - * Restores original state of a specified object in group - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _restoreObjectState: function(object) { - this._setObjectPosition(object); - - object.setCoords(); - object.hasControls = object.__origHasControls; - delete object.__origHasControls; - object.set('active', false); - object.setCoords(); - delete object.group; - - return this; - }, - - /** - * @private - */ - _setObjectPosition: function(object) { - var groupLeft = this.getLeft(), - groupTop = this.getTop(), - rotated = this._getRotatedLeftTop(object); - - object.set({ - angle: object.getAngle() + this.getAngle(), - left: groupLeft + rotated.left, - top: groupTop + rotated.top, - scaleX: object.get('scaleX') * this.get('scaleX'), - scaleY: object.get('scaleY') * this.get('scaleY') - }); - }, - - /** - * @private - */ - _getRotatedLeftTop: function(object) { - var groupAngle = this.getAngle() * (Math.PI / 180); - return { - left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + - Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), - - top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + - Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) - }; - }, - - /** - * Destroys a group (restoring state of its objects) - * @return {fabric.Group} thisArg - * @chainable - */ - destroy: function() { - this._objects.forEach(this._moveFlippedObject, this); - return this._restoreObjectsState(); - }, - - /** - * Saves coordinates of this instance (to be used together with `hasMoved`) - * @saveCoords - * @return {fabric.Group} thisArg - * @chainable - */ - saveCoords: function() { - this._originalLeft = this.get('left'); - this._originalTop = this.get('top'); - return this; - }, - - /** - * Checks whether this group was moved (since `saveCoords` was called last) - * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) - */ - hasMoved: function() { - return this._originalLeft !== this.get('left') || - this._originalTop !== this.get('top'); - }, - - /** - * Sets coordinates of all group objects - * @return {fabric.Group} thisArg - * @chainable - */ - setObjectsCoords: function() { - this.forEachObject(function(object) { - object.setCoords(); - }); - return this; - }, - - /** - * @private - */ - _setOpacityIfSame: function() { - var objects = this.getObjects(), - firstValue = objects[0] ? objects[0].get('opacity') : 1, - isSameOpacity = objects.every(function(o) { - return o.get('opacity') === firstValue; - }); - - if (isSameOpacity) { - this.opacity = firstValue; - } - }, - - /** - * @private - */ - _calcBounds: function(onlyWidthHeight) { - var aX = [], - aY = [], - o; - - for (var i = 0, len = this._objects.length; i < len; ++i) { - o = this._objects[i]; - o.setCoords(); - for (var prop in o.oCoords) { - aX.push(o.oCoords[prop].x); - aY.push(o.oCoords[prop].y); - } - } - - this.set(this._getBounds(aX, aY, onlyWidthHeight)); - }, - - /** - * @private - */ - _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt; - if (this.canvas) { - ivt = fabric.util.invertTransform(this.getViewportTransform()); - } - var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), - obj = { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0 - }; - - if (!onlyWidthHeight) { - obj.left = (minXY.x + maxXY.x) / 2 || 0; - obj.top = (minXY.y + maxXY.y) / 2 || 0; - } - return obj; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [ - '' - ]; - - for (var i = 0, len = this._objects.length; i < len; i++) { - markup.push(this._objects[i].toSVG(reviver)); - } - - markup.push(''); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns requested property - * @param {String} prop Property to get - * @return {Any} - */ - get: function(prop) { - if (prop in _lockProperties) { - if (this[prop]) { - return this[prop]; - } - else { - for (var i = 0, len = this._objects.length; i < len; i++) { - if (this._objects[i][prop]) { - return true; + ctx.restore(); + }, + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; + ctx.save(); + this._setStrokeStyles(ctx); + 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(); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + return extend(this.callSuper("toObject", propertiesToInclude), { + src: this._originalElement.src || this._originalElement._src, + filters: this.filters.map(function(filterObj) { + return filterObj && filterObj.toObject(); + }), + crossOrigin: this.crossOrigin + }); + }, + toSVG: function(reviver) { + var markup = []; + markup.push('', '"); + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + markup.push("'); + this.fill = origFill; } - } - return false; + markup.push(""); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + getSrc: function() { + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } + }, + toString: function() { + return '#'; + }, + clone: function(callback, propertiesToInclude) { + this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + }, + applyFilters: function(callback) { + if (!this._originalElement) { + return; + } + if (this.filters.length === 0) { + this._element = this._originalElement; + callback && callback(); + return; + } + var imgEl = this._originalElement, canvasEl = fabric.util.createCanvasElement(), replacement = fabric.util.createImage(), _this = this; + canvasEl.width = imgEl.width; + canvasEl.height = imgEl.height; + canvasEl.getContext("2d").drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); + this.filters.forEach(function(filter) { + filter && filter.applyTo(canvasEl); + }); + replacement.width = imgEl.width; + replacement.height = imgEl.height; + if (fabric.isLikelyNode) { + replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); + _this._element = replacement; + callback && callback(); + } else { + replacement.onload = function() { + _this._element = replacement; + callback && callback(); + replacement.onload = canvasEl = imgEl = null; + }; + replacement.src = canvasEl.toDataURL("image/png"); + } + return this; + }, + _render: function(ctx) { + this._element && ctx.drawImage(this._element, -this.width / 2, -this.height / 2, this.width, this.height); + }, + _resetWidthHeight: function() { + var element = this.getElement(); + this.set("width", element.width); + this.set("height", element.height); + }, + _initElement: function(element) { + this.setElement(fabric.util.getById(element)); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + _initConfig: function(options) { + options || (options = {}); + this.setOptions(options); + this._setWidthHeight(options); + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } + }, + _initFilters: function(object, callback) { + if (object.filters && object.filters.length) { + fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, "fabric.Image.filters"); + } else { + callback && callback(); + } + }, + _setWidthHeight: function(options) { + this.width = "width" in options ? options.width : this.getElement() ? this.getElement().width || 0 : 0; + this.height = "height" in options ? options.height : this.getElement() ? this.getElement().height || 0 : 0; + }, + complexity: function() { + return 1; } - } - else { - if (prop in this.delegatedProperties) { - return this._objects[0] && this._objects[0].get(prop); - } - return this[prop]; - } - } - }); - - /** - * Returns {@link fabric.Group} instance from an object representation - * @static - * @memberOf fabric.Group - * @param {Object} object Object to create a group from - * @param {Object} [options] Options object - * @return {fabric.Group} An instance of fabric.Group - */ - fabric.Group.fromObject = function(object, callback) { - fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { - delete object.objects; - callback && callback(new fabric.Group(enlivenedObjects, object)); }); - }; + fabric.Image.CSS_CANVAS = "canvas-img"; + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + fabric.Image.prototype._initFilters.call(object, object, function(filters) { + object.filters = filters || []; + var instance = new fabric.Image(img, object); + callback && callback(instance); + }); + }, null, object.crossOrigin); + }; + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img) { + callback(new fabric.Image(img, imgOptions)); + }, null, imgOptions && imgOptions.crossOrigin); + }; + fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")); + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + fabric.Image.fromURL(parsedAttributes["xlink:href"], callback, extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)); + }; + fabric.Image.async = true; + fabric.Image.pngCompression = 1; +})(typeof exports !== "undefined" ? exports : this); - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Group - * @type Boolean - * @default - */ - fabric.Group.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var extend = fabric.util.object.extend; - - if (!global.fabric) { - global.fabric = { }; - } - - if (global.fabric.Image) { - fabric.warn('fabric.Image is already defined.'); - return; - } - - /** - * Image class - * @class fabric.Image - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} - * @see {@link fabric.Image#initialize} for constructor definition - */ - fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'image', - - /** - * crossOrigin value (one of "", "anonymous", "allow-credentials") - * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes - * @type String - * @default - */ - crossOrigin: '', - - /** - * Constructor - * @param {HTMLImageElement | String} element Image element - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - */ - initialize: function(element, options) { - options || (options = { }); - - this.filters = [ ]; - - this.callSuper('initialize', options); - - this._initElement(element, options); - this._initConfig(options); - - if (options.filters) { - this.filters = options.filters; - this.applyFilters(); - } +fabric.util.object.extend(fabric.Object.prototype, { + _getAngleValueForStraighten: function() { + var angle = this.getAngle() % 360; + if (angle > 0) { + return Math.round((angle - 1) / 90) * 90; + } + return Math.round(angle / 90) * 90; }, - - /** - * Returns image element which this instance if based on - * @return {HTMLImageElement} Image element - */ - getElement: function() { - return this._element; + straighten: function() { + this.setAngle(this._getAngleValueForStraighten()); + return this; }, + fxStraighten: function(callbacks) { + callbacks = callbacks || {}; + var empty = function() {}, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; + fabric.util.animate({ + startValue: this.get("angle"), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.setAngle(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + onStart: function() { + _this.set("active", false); + } + }); + return this; + } +}); - /** - * Sets image element for this instance to a specified one. - * If filters defined they are applied to new image. - * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. - * @param {HTMLImageElement} element - * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated - * @return {fabric.Image} thisArg - * @chainable - */ - setElement: function(element, callback) { - this._element = element; - this._originalElement = element; - this._initConfig(); - - if (this.filters.length !== 0) { - this.applyFilters(callback); - } - - return this; +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + straightenObject: function(object) { + object.straighten(); + this.renderAll(); + return this; }, + fxStraightenObject: function(object) { + object.fxStraighten({ + onChange: this.renderAll.bind(this) + }); + return this; + } +}); - /** - * Sets crossOrigin value (on an instance and corresponding image element) - * @return {fabric.Image} thisArg - * @chainable - */ - setCrossOrigin: function(value) { - this.crossOrigin = value; - this._element.crossOrigin = value; +fabric.Image.filters = fabric.Image.filters || {}; - return this; - }, - - /** - * Returns original size of an image - * @return {Object} Object with "width" and "height" properties - */ - getOriginalSize: function() { - var element = this.getElement(); - return { - width: element.width, - height: element.height - }; - }, - - /** - * Renders image on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) return; - - ctx.save(); - var m = this.transformMatrix, - isInPathGroup = this.group && this.group.type === 'path-group'; - - // this._resetWidthHeight(); - if (isInPathGroup) { - ctx.translate(-this.group.width/2, -this.group.height/2); - } - if (m) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - if (!noTransform) { - this.transform(ctx); - } - if (isInPathGroup) { - ctx.translate(this.width/2, this.height/2); - } - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx); - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - this._renderStroke(ctx); - this.clipTo && ctx.restore(); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _stroke: function(ctx) { - ctx.save(); - this._setStrokeStyles(ctx); - ctx.beginPath(); - ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); - ctx.closePath(); - 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.save(); - this._setStrokeStyles(ctx); - - 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(); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - src: this._originalElement.src || this._originalElement._src, - filters: this.filters.map(function(filterObj) { - return filterObj && filterObj.toObject(); - }), - crossOrigin: this.crossOrigin - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = []; - - markup.push( - '', - '' - ); - - if (this.stroke || this.strokeDashArray) { - var origFill = this.fill; - this.fill = null; - markup.push( - '' - ); - this.fill = origFill; - } - - markup.push(''); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns source of an image - * @return {String} Source of an image - */ - getSrc: function() { - if (this.getElement()) { - return this.getElement().src || this.getElement()._src; - } - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns a clone of an instance - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - */ - clone: function(callback, propertiesToInclude) { - this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - }, - - /** - * Applies filters assigned to this image (from "filters" array) - * @mthod applyFilters - * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated - * @return {fabric.Image} thisArg - * @chainable - */ - applyFilters: function(callback) { - - if (!this._originalElement) { - return; - } - - if (this.filters.length === 0) { - this._element = this._originalElement; - callback && callback(); - return; - } - - var imgEl = this._originalElement, - canvasEl = fabric.util.createCanvasElement(), - replacement = fabric.util.createImage(), - _this = this; - - canvasEl.width = imgEl.width; - canvasEl.height = imgEl.height; - - canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); - - this.filters.forEach(function(filter) { - filter && filter.applyTo(canvasEl); - }); - - /** @ignore */ - - replacement.width = imgEl.width; - replacement.height = imgEl.height; - - if (fabric.isLikelyNode) { - replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); - - // onload doesn't fire in some node versions, so we invoke callback manually - _this._element = replacement; - callback && callback(); - } - else { - replacement.onload = function() { - _this._element = replacement; - callback && callback(); - replacement.onload = canvasEl = imgEl = null; +fabric.Image.filters.BaseFilter = fabric.util.createClass({ + type: "BaseFilter", + toObject: function() { + return { + type: this.type }; - replacement.src = canvasEl.toDataURL('image/png'); - } - - return this; }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - this._element && ctx.drawImage( - this._element, - -this.width / 2, - -this.height / 2, - this.width, - this.height - ); - }, - - /** - * @private - */ - _resetWidthHeight: function() { - var element = this.getElement(); - - this.set('width', element.width); - this.set('height', element.height); - }, - - /** - * The Image class's initialization method. This method is automatically - * called by the constructor. - * @private - * @param {HTMLImageElement|String} element The element representing the image - */ - _initElement: function(element) { - this.setElement(fabric.util.getById(element)); - fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initConfig: function(options) { - options || (options = { }); - this.setOptions(options); - this._setWidthHeight(options); - if (this._element && this.crossOrigin) { - this._element.crossOrigin = this.crossOrigin; - } - }, - - /** - * @private - * @param {Object} object Object with filters property - * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created - */ - _initFilters: function(object, callback) { - if (object.filters && object.filters.length) { - fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { - callback && callback(enlivenedObjects); - }, 'fabric.Image.filters'); - } - else { - callback && callback(); - } - }, - - /** - * @private - * @param {Object} [options] Object with width/height properties - */ - _setWidthHeight: function(options) { - this.width = 'width' in options - ? options.width - : (this.getElement() - ? this.getElement().width || 0 - : 0); - - this.height = 'height' in options - ? options.height - : (this.getElement() - ? this.getElement().height || 0 - : 0); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; + toJSON: function() { + return this.toObject(); } - }); - - /** - * Default CSS class name for canvas - * @static - * @type String - * @default - */ - fabric.Image.CSS_CANVAS = 'canvas-img'; - - /** - * Alias for getSrc - * @static - */ - fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; - - /** - * Creates an instance of fabric.Image from its object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an image instance is created - */ - fabric.Image.fromObject = function(object, callback) { - fabric.util.loadImage(object.src, function(img) { - fabric.Image.prototype._initFilters.call(object, object, function(filters) { - object.filters = filters || [ ]; - var instance = new fabric.Image(img, object); - callback && callback(instance); - }); - }, null, object.crossOrigin); - }; - - /** - * Creates an instance of fabric.Image from an URL string - * @static - * @param {String} url URL to create an image from - * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) - * @param {Object} [imgOptions] Options object - */ - fabric.Image.fromURL = function(url, callback, imgOptions) { - fabric.util.loadImage(url, function(img) { - callback(new fabric.Image(img, imgOptions)); - }, null, imgOptions && imgOptions.crossOrigin); - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) - * @static - * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} - */ - fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' ')); - - /** - * Returns {@link fabric.Image} instance from an SVG element - * @static - * @param {SVGElement} element Element to parse - * @param {Function} callback Callback to execute when fabric.Image object is created - * @param {Object} [options] Options object - * @return {fabric.Image} Instance of fabric.Image - */ - fabric.Image.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); - - fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, - extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.async = true; - - /** - * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 - * @static - * @type Number - * @default - */ - fabric.Image.pngCompression = 1; - -})(typeof exports !== 'undefined' ? exports : this); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * @private - * @return {Number} angle value - */ - _getAngleValueForStraighten: function() { - var angle = this.getAngle() % 360; - if (angle > 0) { - return Math.round((angle - 1) / 90) * 90; - } - return Math.round(angle / 90) * 90; - }, - - /** - * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) - * @return {fabric.Object} thisArg - * @chainable - */ - straighten: function() { - this.setAngle(this._getAngleValueForStraighten()); - return this; - }, - - /** - * Same as {@link fabric.Object.prototype.straighten} but with animation - * @param {Object} callbacks Object with callback functions - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Object} thisArg - * @chainable - */ - fxStraighten: function(callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: this.get('angle'), - endValue: this._getAngleValueForStraighten(), - duration: this.FX_DURATION, - onChange: function(value) { - _this.setAngle(value); - onChange(); - }, - onComplete: function() { - _this.setCoords(); - onComplete(); - }, - onStart: function() { - _this.set('active', false); - } - }); - - return this; - } }); -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Straightens object, then rerenders canvas - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - straightenObject: function (object) { - object.straighten(); - this.renderAll(); - return this; - }, - - /** - * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxStraightenObject: function (object) { - object.fxStraighten({ - onChange: this.renderAll.bind(this) - }); - return this; - } -}); - - -/** - * @namespace fabric.Image.filters - * @memberOf fabric.Image - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - */ -fabric.Image.filters = fabric.Image.filters || { }; - -/** - * Root filter class from which all filter classes inherit from - * @class fabric.Image.filters.BaseFilter - * @memberOf fabric.Image.filters - */ -fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'BaseFilter', - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { type: this.type }; - }, - - /** - * Returns a JSON representation of an instance - * @return {Object} JSON - */ - toJSON: function() { - // delegate, not alias - return this.toObject(); - } -}); - - (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Brightness filter class - * @class fabric.Image.filters.Brightness - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Brightness({ - * brightness: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Brightness', - - /** - * Constructor - * @memberOf fabric.Image.filters.Brightness.prototype - * @param {Object} [options] Options object - * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) - */ - initialize: function(options) { - options = options || { }; - this.brightness = options.brightness || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - brightness = this.brightness; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i] += brightness; - data[i + 1] += brightness; - data[i + 2] += brightness; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - brightness: this.brightness - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness - */ - fabric.Image.filters.Brightness.fromObject = function(object) { - return new fabric.Image.filters.Brightness(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Adapted from html5rocks article - * @class fabric.Image.filters.Convolute - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Sharpen filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 0, -1, 0, - * -1, 5, -1, - * 0, -1, 0 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Blur filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter with opaqueness - * var filter = new fabric.Image.filters.Convolute({ - * opaque: true, - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Convolute', - - /** - * Constructor - * @memberOf fabric.Image.filters.Convolute.prototype - * @param {Object} [options] Options object - * @param {Boolean} [options.opaque=false] Opaque value (true/false) - * @param {Array} [options.matrix] Filter matrix - */ - initialize: function(options) { - options = options || { }; - - this.opaque = options.opaque; - this.matrix = options.matrix || [ - 0, 0, 0, - 0, 1, 0, - 0, 0, 0 - ]; - - var canvasEl = fabric.util.createCanvasElement(); - this.tmpCtx = canvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createImageData: function(w, h) { - return this.tmpCtx.createImageData(w, h); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - - var weights = this.matrix, - context = canvasEl.getContext('2d'), - pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - - side = Math.round(Math.sqrt(weights.length)), - halfSide = Math.floor(side/2), - src = pixels.data, - sw = pixels.width, - sh = pixels.height, - - // pad output by the convolution matrix - w = sw, - h = sh, - output = this._createImageData(w, h), - - dst = output.data, - - // go through the destination image pixels - alphaFac = this.opaque ? 1 : 0; - - for (var y = 0; y < h; y++) { - for (var x = 0; x < w; x++) { - var sy = y, - sx = x, - dstOff = (y * w + x) * 4, - // calculate the weighed sum of the source image pixels that - // fall under the convolution matrix - r = 0, g = 0, b = 0, a = 0; - - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - - var scy = sy + cy - halfSide, - scx = sx + cx - halfSide; - - /* jshint maxdepth:5 */ - if (scy < 0 || scy > sh || scx < 0 || scx > sw) continue; - - var srcOff = (scy * sw + scx) * 4, - wt = weights[cy * side + cx]; - - r += src[srcOff] * wt; - g += src[srcOff + 1] * wt; - b += src[srcOff + 2] * wt; - a += src[srcOff + 3] * wt; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Brightness", + initialize: function(options) { + options = options || {}; + this.brightness = options.brightness || 0; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, brightness = this.brightness; + for (var i = 0, len = data.length; i < len; i += 4) { + data[i] += brightness; + data[i + 1] += brightness; + data[i + 2] += brightness; } - } - dst[dstOff] = r; - dst[dstOff + 1] = g; - dst[dstOff + 2] = b; - dst[dstOff + 3] = a + alphaFac * (255 - a); + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + brightness: this.brightness + }); } - } - - context.putImageData(output, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - opaque: this.opaque, - matrix: this.matrix - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute - */ - fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * GradientTransparency filter class - * @class fabric.Image.filters.GradientTransparency - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.GradientTransparency({ - * threshold: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'GradientTransparency', - - /** - * Constructor - * @memberOf fabric.Image.filters.GradientTransparency.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=100] Threshold value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 100; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - total = data.length; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i + 3] = threshold + 255 * (total - i) / total; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency - */ - fabric.Image.filters.GradientTransparency.fromObject = function(object) { - return new fabric.Image.filters.GradientTransparency(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Grayscale image filter class - * @class fabric.Image.filters.Grayscale - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Grayscale(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Grayscale', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Grayscale.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - len = imageData.width * imageData.height * 4, - index = 0, - average; - - while (index < len) { - average = (data[index] + data[index + 1] + data[index + 2]) / 3; - data[index] = average; - data[index + 1] = average; - data[index + 2] = average; - index += 4; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale - */ - fabric.Image.filters.Grayscale.fromObject = function() { - return new fabric.Image.filters.Grayscale(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Invert filter class - * @class fabric.Image.filters.Invert - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Invert(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Invert', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Invert.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i; - - for (i = 0; i < iLen; i+=4) { - data[i] = 255 - data[i]; - data[i + 1] = 255 - data[i + 1]; - data[i + 2] = 255 - data[i + 2]; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert - */ - fabric.Image.filters.Invert.fromObject = function() { - return new fabric.Image.filters.Invert(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Mask filter class - * See http://resources.aleph-1.com/mask/ - * @class fabric.Image.filters.Mask - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition - */ - fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Mask', - - /** - * Constructor - * @memberOf fabric.Image.filters.Mask.prototype - * @param {Object} [options] Options object - * @param {fabric.Image} [options.mask] Mask image object - * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) - */ - initialize: function(options) { - options = options || { }; - - this.mask = options.mask; - this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - if (!this.mask) return; - - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - maskEl = this.mask.getElement(), - maskCanvasEl = fabric.util.createCanvasElement(), - channel = this.channel, - i, - iLen = imageData.width * imageData.height * 4; - - maskCanvasEl.width = maskEl.width; - maskCanvasEl.height = maskEl.height; - - maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); - - var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), - maskData = maskImageData.data; - - for (i = 0; i < iLen; i += 4) { - data[i + 3] = maskData[i + channel]; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - mask: this.mask.toObject(), - channel: this.channel - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when a mask filter instance is created - */ - fabric.Image.filters.Mask.fromObject = function(object, callback) { - fabric.util.loadImage(object.mask.src, function(img) { - object.mask = new fabric.Image(img, object.mask); - callback && callback(new fabric.Image.filters.Mask(object)); }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.filters.Mask.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - + fabric.Image.filters.Brightness.fromObject = function(object) { + return new fabric.Image.filters.Brightness(object); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Noise filter class - * @class fabric.Image.filters.Noise - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Noise({ - * noise: 700 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Noise', - - /** - * Constructor - * @memberOf fabric.Image.filters.Noise.prototype - * @param {Object} [options] Options object - * @param {Number} [options.noise=0] Noise value - */ - initialize: function(options) { - options = options || { }; - this.noise = options.noise || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - noise = this.noise, rand; - - for (var i = 0, len = data.length; i < len; i += 4) { - - rand = (0.5 - Math.random()) * noise; - - data[i] += rand; - data[i + 1] += rand; - data[i + 2] += rand; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - noise: this.noise - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise - */ - fabric.Image.filters.Noise.fromObject = function(object) { - return new fabric.Image.filters.Noise(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Pixelate filter class - * @class fabric.Image.filters.Pixelate - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Pixelate({ - * blocksize: 8 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Pixelate', - - /** - * Constructor - * @memberOf fabric.Image.filters.Pixelate.prototype - * @param {Object} [options] Options object - * @param {Number} [options.blocksize=4] Blocksize for pixelate - */ - initialize: function(options) { - options = options || { }; - this.blocksize = options.blocksize || 4; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = imageData.height, - jLen = imageData.width, - index, i, j, r, g, b, a; - - for (i = 0; i < iLen; i += this.blocksize) { - for (j = 0; j < jLen; j += this.blocksize) { - - index = (i * 4) * jLen + (j * 4); - - r = data[index]; - g = data[index + 1]; - b = data[index + 2]; - a = data[index + 3]; - - /* - blocksize: 4 - - [1,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [1,1,1,1,1] - */ - - for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { - for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { - index = (_i * 4) * jLen + (_j * 4); - data[index] = r; - data[index + 1] = g; - data[index + 2] = b; - data[index + 3] = a; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Convolute", + initialize: function(options) { + options = options || {}; + this.opaque = options.opaque; + this.matrix = options.matrix || [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ]; + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext("2d"); + }, + _createImageData: function(w, h) { + return this.tmpCtx.createImageData(w, h); + }, + applyTo: function(canvasEl) { + var weights = this.matrix, context = canvasEl.getContext("2d"), pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), side = Math.round(Math.sqrt(weights.length)), halfSide = Math.floor(side / 2), src = pixels.data, sw = pixels.width, sh = pixels.height, w = sw, h = sh, output = this._createImageData(w, h), dst = output.data, alphaFac = this.opaque ? 1 : 0; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var sy = y, sx = x, dstOff = (y * w + x) * 4, r = 0, g = 0, b = 0, a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = sy + cy - halfSide, scx = sx + cx - halfSide; + if (scy < 0 || scy > sh || scx < 0 || scx > sw) continue; + var srcOff = (scy * sw + scx) * 4, wt = weights[cy * side + cx]; + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); + } } - } + context.putImageData(output, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + opaque: this.opaque, + matrix: this.matrix + }); } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - blocksize: this.blocksize - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate - */ - fabric.Image.filters.Pixelate.fromObject = function(object) { - return new fabric.Image.filters.Pixelate(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - + }); + fabric.Image.filters.Convolute.fromObject = function(object) { + return new fabric.Image.filters.Convolute(object); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Remove white filter class - * @class fabric.Image.filters.RemoveWhite - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.RemoveWhite({ - * threshold: 40, - * distance: 140 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'RemoveWhite', - - /** - * Constructor - * @memberOf fabric.Image.filters.RemoveWhite.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=30] Threshold value - * @param {Number} [options.distance=20] Distance value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 30; - this.distance = options.distance || 20; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - distance = this.distance, - limit = 255 - threshold, - abs = Math.abs, - r, g, b; - - for (var i = 0, len = data.length; i < len; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (r > limit && - g > limit && - b > limit && - abs(r - g) < distance && - abs(r - b) < distance && - abs(g - b) < distance - ) { - data[i + 3] = 1; + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "GradientTransparency", + initialize: function(options) { + options = options || {}; + this.threshold = options.threshold || 100; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, total = data.length; + for (var i = 0, len = data.length; i < len; i += 4) { + data[i + 3] = threshold + 255 * (total - i) / total; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + threshold: this.threshold + }); } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold, - distance: this.distance - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite - */ - fabric.Image.filters.RemoveWhite.fromObject = function(object) { - return new fabric.Image.filters.RemoveWhite(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - + }); + fabric.Image.filters.GradientTransparency.fromObject = function(object) { + return new fabric.Image.filters.GradientTransparency(object); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia filter class - * @class fabric.Image.filters.Sepia - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, avg; - - for (i = 0; i < iLen; i+=4) { - avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; - data[i] = avg + 100; - data[i + 1] = avg + 50; - data[i + 2] = avg + 255; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia - */ - fabric.Image.filters.Sepia.fromObject = function() { - return new fabric.Image.filters.Sepia(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Grayscale", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, len = imageData.width * imageData.height * 4, index = 0, average; + while (index < len) { + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + index += 4; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Grayscale.fromObject = function() { + return new fabric.Image.filters.Grayscale(); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - /** - * Sepia2 filter class - * @class fabric.Image.filters.Sepia2 - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia2(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia2', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, r, g, b; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; - data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; - data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 - */ - fabric.Image.filters.Sepia2.fromObject = function() { - return new fabric.Image.filters.Sepia2(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Invert", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i; + for (i = 0; i < iLen; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Invert.fromObject = function() { + return new fabric.Image.filters.Invert(); + }; +})(typeof exports !== "undefined" ? exports : this); (function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Tint filter class - * Adapted from https://github.com/mezzoblue/PaintbrushJS - * @class fabric.Image.filters.Tint - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} - * @example Tint filter with hex color and opacity - * var filter = new fabric.Image.filters.Tint({ - * color: '#3513B0', - * opacity: 0.5 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Tint filter with rgba color - * var filter = new fabric.Image.filters.Tint({ - * color: 'rgba(53, 21, 176, 0.5)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Tint', - - /** - * Constructor - * @memberOf fabric.Image.filters.Tint.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to tint the image with - * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - this.opacity = typeof options.opacity !== 'undefined' - ? options.opacity - : new fabric.Color(this.color).getAlpha(); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - tintR, tintG, tintB, - r, g, b, alpha1, - source; - - source = new fabric.Color(this.color).getSource(); - - tintR = source[0] * this.opacity; - tintG = source[1] * this.opacity; - tintB = source[2] * this.opacity; - - alpha1 = 1 - this.opacity; - - for (i = 0; i < iLen; i+=4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - // alpha compositing - data[i] = tintR + r * alpha1; - data[i + 1] = tintG + g * alpha1; - data[i + 2] = tintB + b * alpha1; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color, - opacity: this.opacity - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint - */ - fabric.Image.filters.Tint.fromObject = function(object) { - return new fabric.Image.filters.Tint(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - /** - * Multiply filter class - * Adapted from http://www.laurenscorijn.com/articles/colormath-basics - * @class fabric.Image.filters.Multiply - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example Multiply filter with hex color - * var filter = new fabric.Image.filters.Multiply({ - * color: '#F0F' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Multiply filter with rgb color - * var filter = new fabric.Image.filters.Multiply({ - * color: 'rgb(53, 21, 176)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Multiply', - - /** - * Constructor - * @memberOf fabric.Image.filters.Multiply.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to multiply the image pixels with - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - source; - - source = new fabric.Color(this.color).getSource(); - - for (i = 0; i < iLen; i+=4) { - data[i] *= source[0]/255; - data[i + 1] *= source[1]/255; - data[i + 2] *= source[2]/255; - - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply - */ - fabric.Image.filters.Multiply.fromObject = function(object) { - return new fabric.Image.filters.Multiply(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Text) { - fabric.warn('fabric.Text is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push( - 'fontFamily', - 'fontWeight', - 'fontSize', - 'text', - 'textDecoration', - 'textAlign', - 'fontStyle', - 'lineHeight', - 'textBackgroundColor', - 'useNative', - 'path' - ); - - /** - * Text class - * @class fabric.Text - * @extends fabric.Object - * @return {fabric.Text} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} - * @see {@link fabric.Text#initialize} for constructor definition - */ - fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { - - /** - * Properties which when set cause object to change dimensions - * @type Object - * @private - */ - _dimensionAffectingProps: { - fontSize: true, - fontWeight: true, - fontFamily: true, - textDecoration: true, - fontStyle: true, - lineHeight: true, - stroke: true, - strokeWidth: true, - text: true - }, - - /** - * @private - */ - _reNewline: /\r?\n/, - - /** - * Retrieves object's fontSize - * @method getFontSize - * @memberOf fabric.Text.prototype - * @return {String} Font size (in pixels) - */ - - /** - * Sets object's fontSize - * @method setFontSize - * @memberOf fabric.Text.prototype - * @param {Number} fontSize Font size (in pixels) - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontWeight - * @method getFontWeight - * @memberOf fabric.Text.prototype - * @return {(String|Number)} Font weight - */ - - /** - * Sets object's fontWeight - * @method setFontWeight - * @memberOf fabric.Text.prototype - * @param {(Number|String)} fontWeight Font weight - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontFamily - * @method getFontFamily - * @memberOf fabric.Text.prototype - * @return {String} Font family - */ - - /** - * Sets object's fontFamily - * @method setFontFamily - * @memberOf fabric.Text.prototype - * @param {String} fontFamily Font family - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's text - * @method getText - * @memberOf fabric.Text.prototype - * @return {String} text - */ - - /** - * Sets object's text - * @method setText - * @memberOf fabric.Text.prototype - * @param {String} text Text - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textDecoration - * @method getTextDecoration - * @memberOf fabric.Text.prototype - * @return {String} Text decoration - */ - - /** - * Sets object's textDecoration - * @method setTextDecoration - * @memberOf fabric.Text.prototype - * @param {String} textDecoration Text decoration - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontStyle - * @method getFontStyle - * @memberOf fabric.Text.prototype - * @return {String} Font style - */ - - /** - * Sets object's fontStyle - * @method setFontStyle - * @memberOf fabric.Text.prototype - * @param {String} fontStyle Font style - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's lineHeight - * @method getLineHeight - * @memberOf fabric.Text.prototype - * @return {Number} Line height - */ - - /** - * Sets object's lineHeight - * @method setLineHeight - * @memberOf fabric.Text.prototype - * @param {Number} lineHeight Line height - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textAlign - * @method getTextAlign - * @memberOf fabric.Text.prototype - * @return {String} Text alignment - */ - - /** - * Sets object's textAlign - * @method setTextAlign - * @memberOf fabric.Text.prototype - * @param {String} textAlign Text alignment - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textBackgroundColor - * @method getTextBackgroundColor - * @memberOf fabric.Text.prototype - * @return {String} Text background color - */ - - /** - * Sets object's textBackgroundColor - * @method setTextBackgroundColor - * @memberOf fabric.Text.prototype - * @param {String} textBackgroundColor Text background color - * @return {fabric.Text} - * @chainable - */ - - /** - * Type of an object - * @type String - * @default - */ - type: 'text', - - /** - * Font size (in pixels) - * @type Number - * @default - */ - fontSize: 40, - - /** - * Font weight (e.g. bold, normal, 400, 600, 800) - * @type {(Number|String)} - * @default - */ - fontWeight: 'normal', - - /** - * Font family - * @type String - * @default - */ - fontFamily: 'Times New Roman', - - /** - * Text decoration Possible values: "", "underline", "overline" or "line-through". - * @type String - * @default - */ - textDecoration: '', - - /** - * Text alignment. Possible values: "left", "center", or "right". - * @type String - * @default - */ - textAlign: 'left', - - /** - * Font style . Possible values: "", "normal", "italic" or "oblique". - * @type String - * @default - */ - fontStyle: '', - - /** - * Line height - * @type Number - * @default - */ - lineHeight: 1.3, - - /** - * Background color of text lines - * @type String - * @default - */ - textBackgroundColor: '', - - /** - * URL of a font file, when using Cufon - * @type String | null - * @default - */ - path: null, - - /** - * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) - * @type Boolean - * @default - */ - useNative: true, - - /** - * List of properties to consider when checking if - * state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * When defined, an object is rendered via stroke and this property specifies its color. - * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 - * @type String - * @default - */ - stroke: null, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Text} thisArg - */ - initialize: function(text, options) { - options = options || { }; - - this.text = text; - this.__skipDimension = true; - this.setOptions(options); - this.__skipDimension = false; - this._initDimensions(); - }, - - /** - * Renders text object on offscreen canvas, so that it would get dimensions - * @private - */ - _initDimensions: function() { - if (this.__skipDimension) return; - var canvasEl = fabric.util.createCanvasElement(); - this._render(canvasEl.getContext('2d')); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of text object - */ - toString: function() { - return '#'; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - - var isInPathGroup = this.group && this.group.type === 'path-group'; - if (isInPathGroup && !this.transformMatrix) { - ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top); - } - else if (isInPathGroup && this.transformMatrix) { - ctx.translate(-this.group.width/2, -this.group.height/2); - } - - if (typeof Cufon === 'undefined' || this.useNative === true) { - this._renderViaNative(ctx); - } - else { - this._renderViaCufon(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderViaNative: function(ctx) { - var textLines = this.text.split(this._reNewline); - - this.transform(ctx, fabric.isLikelyNode); - - this._setTextStyles(ctx); - - this.width = this._getTextWidth(ctx, textLines); - this.height = this._getTextHeight(ctx, textLines); - - this.clipTo && fabric.util.clipContext(this, ctx); - - this._renderTextBackground(ctx, textLines); - this._translateForTextAlign(ctx); - this._renderText(ctx, textLines); - - if (this.textAlign !== 'left' && this.textAlign !== 'justify') { - ctx.restore(); - } - - this._renderTextDecoration(ctx, textLines); - this.clipTo && ctx.restore(); - - this._setBoundaries(ctx, textLines); - this._totalLineHeight = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderText: function(ctx, textLines) { - ctx.save(); - this._setShadow(ctx); - this._renderTextFill(ctx, textLines); - this._renderTextStroke(ctx, textLines); - this._removeShadow(ctx); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _translateForTextAlign: function(ctx) { - if (this.textAlign !== 'left' && this.textAlign !== 'justify') { - ctx.save(); - ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _setBoundaries: function(ctx, textLines) { - this._boundaries = [ ]; - - for (var i = 0, len = textLines.length; i < len; i++) { - - var lineWidth = this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - this._boundaries.push({ - height: this.fontSize * this.lineHeight, - width: lineWidth, - left: lineLeftOffset + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Mask", + initialize: function(options) { + options = options || {}; + this.mask = options.mask; + this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; + }, + applyTo: function(canvasEl) { + if (!this.mask) return; + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, maskEl = this.mask.getElement(), maskCanvasEl = fabric.util.createCanvasElement(), channel = this.channel, i, iLen = imageData.width * imageData.height * 4; + maskCanvasEl.width = maskEl.width; + maskCanvasEl.height = maskEl.height; + maskCanvasEl.getContext("2d").drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); + var maskImageData = maskCanvasEl.getContext("2d").getImageData(0, 0, maskEl.width, maskEl.height), maskData = maskImageData.data; + for (i = 0; i < iLen; i += 4) { + data[i + 3] = maskData[i + channel]; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + mask: this.mask.toObject(), + channel: this.channel + }); + } + }); + fabric.Image.filters.Mask.fromObject = function(object, callback) { + fabric.util.loadImage(object.mask.src, function(img) { + object.mask = new fabric.Image(img, object.mask); + callback && callback(new fabric.Image.filters.Mask(object)); }); - } - }, + }; + fabric.Image.filters.Mask.async = true; +})(typeof exports !== "undefined" ? exports : this); - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setTextStyles: function(ctx) { - this._setFillStyles(ctx); - this._setStrokeStyles(ctx); - ctx.textBaseline = 'alphabetic'; - if (!this.skipTextAlign) { - ctx.textAlign = this.textAlign; - } - ctx.font = this._getFontDeclaration(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - * @return {Number} Height of fabric.Text object - */ - _getTextHeight: function(ctx, textLines) { - return this.fontSize * textLines.length * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - * @return {Number} Maximum width of fabric.Text object - */ - _getTextWidth: function(ctx, textLines) { - var maxWidth = ctx.measureText(textLines[0] || '|').width; - - for (var i = 1, len = textLines.length; i < len; i++) { - var currentLineWidth = ctx.measureText(textLines[i]).width; - if (currentLineWidth > maxWidth) { - maxWidth = currentLineWidth; +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Noise", + initialize: function(options) { + options = options || {}; + this.noise = options.noise || 0; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, noise = this.noise, rand; + for (var i = 0, len = data.length; i < len; i += 4) { + rand = (.5 - Math.random()) * noise; + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + noise: this.noise + }); } - } - return maxWidth; - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Chars to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - */ - _renderChars: function(method, ctx, chars, left, top) { - ctx[method](chars, left, top); - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - * @param {Number} lineIndex Index of a line in a text - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // lift the line by quarter of fontSize - top -= this.fontSize / 4; - - // short-circuit - if (this.textAlign !== 'justify') { - this._renderChars(method, ctx, line, left, top, lineIndex); - return; - } - - var lineWidth = ctx.measureText(line).width, - totalWidth = this.width; - - if (totalWidth > lineWidth) { - // stretch the line - var words = line.split(/\s+/), - wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, - widthDiff = totalWidth - wordsWidth, - numSpaces = words.length - 1, - spaceWidth = widthDiff / numSpaces, - leftOffset = 0; - - for (var i = 0, len = words.length; i < len; i++) { - this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); - leftOffset += ctx.measureText(words[i]).width + spaceWidth; - } - } - else { - this._renderChars(method, ctx, line, left, top, lineIndex); - } - }, - - /** - * @private - * @return {Number} Left offset - */ - _getLeftOffset: function() { - if (fabric.isLikelyNode) { - return 0; - } - return -this.width / 2; - }, - - /** - * @private - * @return {Number} Top offset - */ - _getTopOffset: function() { - return -this.height / 2; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextFill: function(ctx, textLines) { - if (!this.fill && !this._skipFillStrokeCheck) return; - - this._boundaries = [ ]; - var lineHeights = 0; - - for (var i = 0, len = textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i, textLines); - lineHeights += heightOfLine; - - this._renderTextLine( - 'fillText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights, - i - ); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextStroke: function(ctx, textLines) { - if (!this.stroke && !this._skipFillStrokeCheck) return; - - var lineHeights = 0; - - ctx.save(); - 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); - } - supportsLineDash && ctx.setLineDash(this.strokeDashArray); - } - - ctx.beginPath(); - for (var i = 0, len = textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i, textLines); - lineHeights += heightOfLine; - - this._renderTextLine( - 'strokeText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + lineHeights, - i - ); - } - ctx.closePath(); - ctx.restore(); - }, - - _getHeightOfLine: function() { - return this.fontSize * this.lineHeight; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextBackground: function(ctx, textLines) { - this._renderTextBoxBackground(ctx); - this._renderTextLinesBackground(ctx, textLines); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextBoxBackground: function(ctx) { - if (!this.backgroundColor) return; - - ctx.save(); - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset(), - this.width, - this.height - ); - - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextLinesBackground: function(ctx, textLines) { - if (!this.textBackgroundColor) return; - - ctx.save(); - ctx.fillStyle = this.textBackgroundColor; - - for (var i = 0, len = textLines.length; i < len; i++) { - - if (textLines[i] !== '') { - - var lineWidth = this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + (i * this.fontSize * this.lineHeight), - lineWidth, - this.fontSize * this.lineHeight - ); - } - } - ctx.restore(); - }, - - /** - * @private - * @param {Number} lineWidth Width of text line - * @return {Number} Line left offset - */ - _getLineLeftOffset: function(lineWidth) { - if (this.textAlign === 'center') { - return (this.width - lineWidth) / 2; - } - if (this.textAlign === 'right') { - return this.width - lineWidth; - } - return 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text line - * @return {Number} Line width - */ - _getLineWidth: function(ctx, line) { - return this.textAlign === 'justify' - ? this.width - : ctx.measureText(line).width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} textLines Array of all text lines - */ - _renderTextDecoration: function(ctx, textLines) { - if (!this.textDecoration) return; - - // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; - var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, - _this = this; - - /** @ignore */ - function renderLinesAtOffset(offset) { - for (var i = 0, len = textLines.length; i < len; i++) { - - var lineWidth = _this._getLineWidth(ctx, textLines[i]), - lineLeftOffset = _this._getLineLeftOffset(lineWidth); - - ctx.fillRect( - _this._getLeftOffset() + lineLeftOffset, - ~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox), - lineWidth, - 1); - } - } - - if (this.textDecoration.indexOf('underline') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight); - } - if (this.textDecoration.indexOf('line-through') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); - } - if (this.textDecoration.indexOf('overline') > -1) { - renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); - } - }, - - /** - * @private - */ - _getFontDeclaration: function() { - return [ - // node-canvas needs "weight style", while browsers need "style weight" - (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), - (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), - this.fontSize + 'px', - (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) - ].join(' '); - }, - - /** - * Renders text instance on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) return; - - ctx.save(); - var m = this.transformMatrix; - if (m && (!this.group || this.group.type === 'path-group')) { - ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - this._render(ctx); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var object = extend(this.callSuper('toObject', propertiesToInclude), { - text: this.text, - fontSize: this.fontSize, - fontWeight: this.fontWeight, - fontFamily: this.fontFamily, - fontStyle: this.fontStyle, - lineHeight: this.lineHeight, - textDecoration: this.textDecoration, - textAlign: this.textAlign, - path: this.path, - textBackgroundColor: this.textBackgroundColor, - useNative: this.useNative - }); - if (!this.includeDefaultValues) { - this._removeDefaultValues(object); - } - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = [ ], - textLines = this.text.split(this._reNewline), - offsets = this._getSVGLeftTopOffsets(textLines), - textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), - shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); - - // move top offset by an ascent - offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); - - this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - - /** - * @private - */ - _getSVGLeftTopOffsets: function(textLines) { - var lineTop = this.useNative - ? this.fontSize * this.lineHeight - : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), - - textLeft = -(this.width/2), - textTop = this.useNative - ? this.fontSize - 1 - : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight; - - return { - textLeft: textLeft, - textTop: textTop, - lineTop: lineTop - }; - }, - - /** - * @private - */ - _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { - markup.push( - '', - textAndBg.textBgRects.join(''), - '', - shadowSpans.join(''), - textAndBg.textSpans.join(''), - '', - '' - ); - }, - - /** - * @private - * @param {Number} lineHeight - * @param {Array} textLines Array of all text lines - * @return {Array} - */ - _getSVGShadows: function(lineHeight, textLines) { - var shadowSpans = [], - i, len, - lineTopOffsetMultiplier = 1; - - if (!this.shadow || !this._boundaries) { - return shadowSpans; - } - - for (i = 0, len = textLines.length; i < len; i++) { - if (textLines[i] !== '') { - var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; - shadowSpans.push( - '', - fabric.util.string.escapeXml(textLines[i]), - ''); - lineTopOffsetMultiplier = 1; - } - else { - // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier - // prevents empty tspans - lineTopOffsetMultiplier++; - } - } - - return shadowSpans; - }, - - /** - * @private - * @param {Number} lineHeight - * @param {Number} textLeftOffset Text left offset - * @param {Array} textLines Array of all text lines - * @return {Object} - */ - _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { - var textSpans = [ ], - textBgRects = [ ], - lineTopOffsetMultiplier = 1; - - // bounding-box background - this._setSVGBg(textBgRects); - - // text and text-background - for (var i = 0, len = textLines.length; i < len; i++) { - if (textLines[i] !== '') { - this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); - lineTopOffsetMultiplier = 1; - } - else { - // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier - // prevents empty tspans - lineTopOffsetMultiplier++; - } - - if (!this.textBackgroundColor || !this._boundaries) continue; - - this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); - } - - return { - textSpans: textSpans, - textBgRects: textBgRects - }; - }, - - _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { - var lineLeftOffset = (this._boundaries && this._boundaries[i]) - ? toFixed(this._boundaries[i].left, 2) - : 0; - - textSpans.push( - ' elements since setting opacity - // on containing one doesn't work in Illustrator - this._getFillAttributes(this.fill), '>', - fabric.util.string.escapeXml(textLine), - '' - ); - }, - - _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { - textBgRects.push( - ''); - }, - - _setSVGBg: function(textBgRects) { - if (this.backgroundColor && this._boundaries) { - textBgRects.push( - ''); - } - }, - - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {Any} value - * @return {String} - */ - _getFillAttributes: function(value) { - var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; - }, - /* _TO_SVG_END_ */ - - /** - * Sets specified property to a specified value - * @param {String} key - * @param {Any} value - * @return {fabric.Text} thisArg - * @chainable - */ - _set: function(key, value) { - if (key === 'fontFamily' && this.path) { - this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); - } - this.callSuper('_set', key, value); - - if (key in this._dimensionAffectingProps) { - this._initDimensions(); - this.setCoords(); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) - * @static - * @memberOf fabric.Text - * @see: http://www.w3.org/TR/SVG/text.html#TextElement - */ - fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); - - /** - * Default SVG font size - * @static - * @memberOf fabric.Text - */ - fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; - - /** - * Returns fabric.Text instance from an SVG element (not yet implemented) - * @static - * @memberOf fabric.Text - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromElement = function(element, options) { - if (!element) { - return null; - } - - var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); - options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); - - if ('dx' in parsedAttributes) { - options.left += parsedAttributes.dx; - } - if ('dy' in parsedAttributes) { - options.top += parsedAttributes.dy; - } - if (!('fontSize' in options)) { - options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - - if (!options.originX) { - options.originX = 'center'; - } - - var text = new fabric.Text(element.textContent, options); - - /* - Adjust positioning: - x/y attributes in SVG correspond to the bottom-left corner of text bounding box - top/left properties in Fabric correspond to center point of text bounding box - */ - - text.set({ - left: text.getLeft() + text.getWidth() / 2, - top: text.getTop() - text.getHeight() / 2 }); + fabric.Image.filters.Noise.fromObject = function(object) { + return new fabric.Image.filters.Noise(object); + }; +})(typeof exports !== "undefined" ? exports : this); - return text; - }; - /* _FROM_SVG_END_ */ +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Pixelate", + initialize: function(options) { + options = options || {}; + this.blocksize = options.blocksize || 4; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = imageData.height, jLen = imageData.width, index, i, j, r, g, b, a; + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + index = i * 4 * jLen + j * 4; + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { + for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { + index = _i * 4 * jLen + _j * 4; + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + blocksize: this.blocksize + }); + } + }); + fabric.Image.filters.Pixelate.fromObject = function(object) { + return new fabric.Image.filters.Pixelate(object); + }; +})(typeof exports !== "undefined" ? exports : this); - /** - * Returns fabric.Text instance from an object representation - * @static - * @memberOf fabric.Text - * @param object {Object} object Object to create an instance from - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromObject = function(object) { - return new fabric.Text(object.text, clone(object)); - }; +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "RemoveWhite", + initialize: function(options) { + options = options || {}; + this.threshold = options.threshold || 30; + this.distance = options.distance || 20; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, distance = this.distance, limit = 255 - threshold, abs = Math.abs, r, g, b; + for (var i = 0, len = data.length; i < len; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + if (r > limit && g > limit && b > limit && abs(r - g) < distance && abs(r - b) < distance && abs(g - b) < distance) { + data[i + 3] = 1; + } + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + threshold: this.threshold, + distance: this.distance + }); + } + }); + fabric.Image.filters.RemoveWhite.fromObject = function(object) { + return new fabric.Image.filters.RemoveWhite(object); + }; +})(typeof exports !== "undefined" ? exports : this); - fabric.util.createAccessors(fabric.Text); +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Sepia", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, avg; + for (i = 0; i < iLen; i += 4) { + avg = .3 * data[i] + .59 * data[i + 1] + .11 * data[i + 2]; + data[i] = avg + 100; + data[i + 1] = avg + 50; + data[i + 2] = avg + 255; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Sepia.fromObject = function() { + return new fabric.Image.filters.Sepia(); + }; +})(typeof exports !== "undefined" ? exports : this); -})(typeof exports !== 'undefined' ? exports : this); +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}); + fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Sepia2", + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, r, g, b; + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + data[i] = (r * .393 + g * .769 + b * .189) / 1.351; + data[i + 1] = (r * .349 + g * .686 + b * .168) / 1.203; + data[i + 2] = (r * .272 + g * .534 + b * .131) / 2.14; + } + context.putImageData(imageData, 0, 0); + } + }); + fabric.Image.filters.Sepia2.fromObject = function() { + return new fabric.Image.filters.Sepia2(); + }; +})(typeof exports !== "undefined" ? exports : this); +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Tint", + initialize: function(options) { + options = options || {}; + this.color = options.color || "#000000"; + this.opacity = typeof options.opacity !== "undefined" ? options.opacity : new fabric.Color(this.color).getAlpha(); + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, tintR, tintG, tintB, r, g, b, alpha1, source; + source = new fabric.Color(this.color).getSource(); + tintR = source[0] * this.opacity; + tintG = source[1] * this.opacity; + tintB = source[2] * this.opacity; + alpha1 = 1 - this.opacity; + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + data[i] = tintR + r * alpha1; + data[i + 1] = tintG + g * alpha1; + data[i + 2] = tintB + b * alpha1; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + color: this.color, + opacity: this.opacity + }); + } + }); + fabric.Image.filters.Tint.fromObject = function(object) { + return new fabric.Image.filters.Tint(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend; + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, { + type: "Multiply", + initialize: function(options) { + options = options || {}; + this.color = options.color || "#000000"; + }, + applyTo: function(canvasEl) { + var context = canvasEl.getContext("2d"), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, source; + source = new fabric.Color(this.color).getSource(); + for (i = 0; i < iLen; i += 4) { + data[i] *= source[0] / 255; + data[i + 1] *= source[1] / 255; + data[i + 2] *= source[2] / 255; + } + context.putImageData(imageData, 0, 0); + }, + toObject: function() { + return extend(this.callSuper("toObject"), { + color: this.color + }); + } + }); + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; +})(typeof exports !== "undefined" ? exports : this); + +(function(global) { + "use strict"; + var fabric = global.fabric || (global.fabric = {}), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, supportsLineDash = fabric.StaticCanvas.supports("setLineDash"); + if (fabric.Text) { + fabric.warn("fabric.Text is already defined"); + return; + } + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push("fontFamily", "fontWeight", "fontSize", "text", "textDecoration", "textAlign", "fontStyle", "lineHeight", "textBackgroundColor", "useNative", "path"); + fabric.Text = fabric.util.createClass(fabric.Object, { + _dimensionAffectingProps: { + fontSize: true, + fontWeight: true, + fontFamily: true, + textDecoration: true, + fontStyle: true, + lineHeight: true, + stroke: true, + strokeWidth: true, + text: true + }, + _reNewline: /\r?\n/, + type: "text", + fontSize: 40, + fontWeight: "normal", + fontFamily: "Times New Roman", + textDecoration: "", + textAlign: "left", + fontStyle: "", + lineHeight: 1.3, + textBackgroundColor: "", + path: null, + useNative: true, + stateProperties: stateProperties, + stroke: null, + shadow: null, + initialize: function(text, options) { + options = options || {}; + this.text = text; + this.__skipDimension = true; + this.setOptions(options); + this.__skipDimension = false; + this._initDimensions(); + }, + _initDimensions: function() { + if (this.__skipDimension) return; + var canvasEl = fabric.util.createCanvasElement(); + this._render(canvasEl.getContext("2d")); + }, + toString: function() { + return "#'; + }, + _render: function(ctx) { + var isInPathGroup = this.group && this.group.type === "path-group"; + if (isInPathGroup && !this.transformMatrix) { + ctx.translate(-this.group.width / 2 + this.left, -this.group.height / 2 + this.top); + } else if (isInPathGroup && this.transformMatrix) { + ctx.translate(-this.group.width / 2, -this.group.height / 2); + } + if (typeof Cufon === "undefined" || this.useNative === true) { + this._renderViaNative(ctx); + } else { + this._renderViaCufon(ctx); + } + }, + _renderViaNative: function(ctx) { + var textLines = this.text.split(this._reNewline); + this.transform(ctx, fabric.isLikelyNode); + this._setTextStyles(ctx); + this.width = this._getTextWidth(ctx, textLines); + this.height = this._getTextHeight(ctx, textLines); + this.clipTo && fabric.util.clipContext(this, ctx); + this._renderTextBackground(ctx, textLines); + this._translateForTextAlign(ctx); + this._renderText(ctx, textLines); + if (this.textAlign !== "left" && this.textAlign !== "justify") { + ctx.restore(); + } + this._renderTextDecoration(ctx, textLines); + this.clipTo && ctx.restore(); + this._setBoundaries(ctx, textLines); + this._totalLineHeight = 0; + }, + _renderText: function(ctx, textLines) { + ctx.save(); + this._setShadow(ctx); + this._renderTextFill(ctx, textLines); + this._renderTextStroke(ctx, textLines); + this._removeShadow(ctx); + ctx.restore(); + }, + _translateForTextAlign: function(ctx) { + if (this.textAlign !== "left" && this.textAlign !== "justify") { + ctx.save(); + ctx.translate(this.textAlign === "center" ? this.width / 2 : this.width, 0); + } + }, + _setBoundaries: function(ctx, textLines) { + this._boundaries = []; + for (var i = 0, len = textLines.length; i < len; i++) { + var lineWidth = this._getLineWidth(ctx, textLines[i]), lineLeftOffset = this._getLineLeftOffset(lineWidth); + this._boundaries.push({ + height: this.fontSize * this.lineHeight, + width: lineWidth, + left: lineLeftOffset + }); + } + }, + _setTextStyles: function(ctx) { + this._setFillStyles(ctx); + this._setStrokeStyles(ctx); + ctx.textBaseline = "alphabetic"; + if (!this.skipTextAlign) { + ctx.textAlign = this.textAlign; + } + ctx.font = this._getFontDeclaration(); + }, + _getTextHeight: function(ctx, textLines) { + return this.fontSize * textLines.length * this.lineHeight; + }, + _getTextWidth: function(ctx, textLines) { + var maxWidth = ctx.measureText(textLines[0] || "|").width; + for (var i = 1, len = textLines.length; i < len; i++) { + var currentLineWidth = ctx.measureText(textLines[i]).width; + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + _renderChars: function(method, ctx, chars, left, top) { + ctx[method](chars, left, top); + }, + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + top -= this.fontSize / 4; + if (this.textAlign !== "justify") { + this._renderChars(method, ctx, line, left, top, lineIndex); + return; + } + var lineWidth = ctx.measureText(line).width, totalWidth = this.width; + if (totalWidth > lineWidth) { + var words = line.split(/\s+/), wordsWidth = ctx.measureText(line.replace(/\s+/g, "")).width, widthDiff = totalWidth - wordsWidth, numSpaces = words.length - 1, spaceWidth = widthDiff / numSpaces, leftOffset = 0; + for (var i = 0, len = words.length; i < len; i++) { + this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); + leftOffset += ctx.measureText(words[i]).width + spaceWidth; + } + } else { + this._renderChars(method, ctx, line, left, top, lineIndex); + } + }, + _getLeftOffset: function() { + if (fabric.isLikelyNode) { + return 0; + } + return -this.width / 2; + }, + _getTopOffset: function() { + return -this.height / 2; + }, + _renderTextFill: function(ctx, textLines) { + if (!this.fill && !this._skipFillStrokeCheck) return; + this._boundaries = []; + var lineHeights = 0; + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + this._renderTextLine("fillText", ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights, i); + } + }, + _renderTextStroke: function(ctx, textLines) { + if (!this.stroke && !this._skipFillStrokeCheck) return; + var lineHeights = 0; + ctx.save(); + if (this.strokeDashArray) { + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + supportsLineDash && ctx.setLineDash(this.strokeDashArray); + } + ctx.beginPath(); + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + this._renderTextLine("strokeText", ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights, i); + } + ctx.closePath(); + ctx.restore(); + }, + _getHeightOfLine: function() { + return this.fontSize * this.lineHeight; + }, + _renderTextBackground: function(ctx, textLines) { + this._renderTextBoxBackground(ctx); + this._renderTextLinesBackground(ctx, textLines); + }, + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) return; + ctx.save(); + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(this._getLeftOffset(), this._getTopOffset(), this.width, this.height); + ctx.restore(); + }, + _renderTextLinesBackground: function(ctx, textLines) { + if (!this.textBackgroundColor) return; + ctx.save(); + ctx.fillStyle = this.textBackgroundColor; + for (var i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== "") { + var lineWidth = this._getLineWidth(ctx, textLines[i]), lineLeftOffset = this._getLineLeftOffset(lineWidth); + ctx.fillRect(this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + i * this.fontSize * this.lineHeight, lineWidth, this.fontSize * this.lineHeight); + } + } + ctx.restore(); + }, + _getLineLeftOffset: function(lineWidth) { + if (this.textAlign === "center") { + return (this.width - lineWidth) / 2; + } + if (this.textAlign === "right") { + return this.width - lineWidth; + } + return 0; + }, + _getLineWidth: function(ctx, line) { + return this.textAlign === "justify" ? this.width : ctx.measureText(line).width; + }, + _renderTextDecoration: function(ctx, textLines) { + if (!this.textDecoration) return; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, _this = this; + function renderLinesAtOffset(offset) { + for (var i = 0, len = textLines.length; i < len; i++) { + var lineWidth = _this._getLineWidth(ctx, textLines[i]), lineLeftOffset = _this._getLineLeftOffset(lineWidth); + ctx.fillRect(_this._getLeftOffset() + lineLeftOffset, ~~(offset + i * _this._getHeightOfLine(ctx, i, textLines) - halfOfVerticalBox), lineWidth, 1); + } + } + if (this.textDecoration.indexOf("underline") > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight); + } + if (this.textDecoration.indexOf("line-through") > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); + } + if (this.textDecoration.indexOf("overline") > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); + } + }, + _getFontDeclaration: function() { + return [ fabric.isLikelyNode ? this.fontWeight : this.fontStyle, fabric.isLikelyNode ? this.fontStyle : this.fontWeight, this.fontSize + "px", fabric.isLikelyNode ? '"' + this.fontFamily + '"' : this.fontFamily ].join(" "); + }, + render: function(ctx, noTransform) { + if (!this.visible) return; + ctx.save(); + var m = this.transformMatrix; + if (m && (!this.group || this.group.type === "path-group")) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + this._render(ctx); + ctx.restore(); + }, + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper("toObject", propertiesToInclude), { + text: this.text, + fontSize: this.fontSize, + fontWeight: this.fontWeight, + fontFamily: this.fontFamily, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + textDecoration: this.textDecoration, + textAlign: this.textAlign, + path: this.path, + textBackgroundColor: this.textBackgroundColor, + useNative: this.useNative + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + toSVG: function(reviver) { + var markup = [], textLines = this.text.split(this._reNewline), offsets = this._getSVGLeftTopOffsets(textLines), textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); + offsets.textTop += this._fontAscent ? this._fontAscent / 5 * this.lineHeight : 0; + this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); + return reviver ? reviver(markup.join("")) : markup.join(""); + }, + _getSVGLeftTopOffsets: function(textLines) { + var lineTop = this.useNative ? this.fontSize * this.lineHeight : -this._fontAscent - this._fontAscent / 5 * this.lineHeight, textLeft = -(this.width / 2), textTop = this.useNative ? this.fontSize - 1 : this.height / 2 - textLines.length * this.fontSize - this._totalLineHeight; + return { + textLeft: textLeft, + textTop: textTop, + lineTop: lineTop + }; + }, + _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { + markup.push('', textAndBg.textBgRects.join(""), "', shadowSpans.join(""), textAndBg.textSpans.join(""), "", ""); + }, + _getSVGShadows: function(lineHeight, textLines) { + var shadowSpans = [], i, len, lineTopOffsetMultiplier = 1; + if (!this.shadow || !this._boundaries) { + return shadowSpans; + } + for (i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== "") { + var lineLeftOffset = this._boundaries && this._boundaries[i] ? this._boundaries[i].left : 0; + shadowSpans.push('", fabric.util.string.escapeXml(textLines[i]), ""); + lineTopOffsetMultiplier = 1; + } else { + lineTopOffsetMultiplier++; + } + } + return shadowSpans; + }, + _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { + var textSpans = [], textBgRects = [], lineTopOffsetMultiplier = 1; + this._setSVGBg(textBgRects); + for (var i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== "") { + this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); + lineTopOffsetMultiplier = 1; + } else { + lineTopOffsetMultiplier++; + } + if (!this.textBackgroundColor || !this._boundaries) continue; + this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); + } + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { + var lineLeftOffset = this._boundaries && this._boundaries[i] ? toFixed(this._boundaries[i].left, 2) : 0; + textSpans.push('", fabric.util.string.escapeXml(textLine), ""); + }, + _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { + textBgRects.push("'); + }, + _setSVGBg: function(textBgRects) { + if (this.backgroundColor && this._boundaries) { + textBgRects.push("'); + } + }, + _getFillAttributes: function(value) { + var fillColor = value && typeof value === "string" ? new fabric.Color(value) : ""; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + _set: function(key, value) { + if (key === "fontFamily" && this.path) { + this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, "$1" + value + "$3"); + } + this.callSuper("_set", key, value); + if (key in this._dimensionAffectingProps) { + this._initDimensions(); + this.setCoords(); + } + }, + complexity: function() { + return 1; + } + }); + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")); + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + fabric.Text.fromElement = function(element, options) { + if (!element) { + return null; + } + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); + options = fabric.util.object.extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes); + if ("dx" in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ("dy" in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!("fontSize" in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + if (!options.originX) { + options.originX = "center"; + } + var text = new fabric.Text(element.textContent, options); + text.set({ + left: text.getLeft() + text.getWidth() / 2, + top: text.getTop() - text.getHeight() / 2 + }); + return text; + }; + fabric.Text.fromObject = function(object) { + return new fabric.Text(object.text, clone(object)); + }; + fabric.util.createAccessors(fabric.Text); +})(typeof exports !== "undefined" ? exports : this); -/** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ fabric.util.object.extend(fabric.Text.prototype, { - _renderViaCufon: function(ctx) { - - var o = Cufon.textOptions || (Cufon.textOptions = { }); - - // export options to be used by cufon.js - o.left = this.left; - o.top = this.top; - o.context = ctx; - o.color = this.fill; - - var el = this._initDummyElementForCufon(); - - // set "cursor" to top/left corner - this.transform(ctx); - - // draw text - Cufon.replaceElement(el, { - engine: 'canvas', - separate: 'none', - fontFamily: this.fontFamily, - fontWeight: this.fontWeight, - textDecoration: this.textDecoration, - textShadow: this.shadow && this.shadow.toString(), - textAlign: this.textAlign, - fontStyle: this.fontStyle, - lineHeight: this.lineHeight, - stroke: this.stroke, - strokeWidth: this.strokeWidth, - backgroundColor: this.backgroundColor, - textBackgroundColor: this.textBackgroundColor - }); - - // update width, height - this.width = o.width; - this.height = o.height; - - this._totalLineHeight = o.totalLineHeight; - this._fontAscent = o.fontAscent; - this._boundaries = o.boundaries; - - el = null; - - // need to set coords _after_ the width/height was retreived from Cufon - this.setCoords(); - }, - - /** - * @private - */ - _initDummyElementForCufon: function() { - var el = fabric.document.createElement('pre'), - container = fabric.document.createElement('div'); - - // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent - container.appendChild(el); - - if (typeof G_vmlCanvasManager === 'undefined') { - el.innerHTML = this.text; + _renderViaCufon: function(ctx) { + var o = Cufon.textOptions || (Cufon.textOptions = {}); + o.left = this.left; + o.top = this.top; + o.context = ctx; + o.color = this.fill; + var el = this._initDummyElementForCufon(); + this.transform(ctx); + Cufon.replaceElement(el, { + engine: "canvas", + separate: "none", + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, + textDecoration: this.textDecoration, + textShadow: this.shadow && this.shadow.toString(), + textAlign: this.textAlign, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + stroke: this.stroke, + strokeWidth: this.strokeWidth, + backgroundColor: this.backgroundColor, + textBackgroundColor: this.textBackgroundColor + }); + this.width = o.width; + this.height = o.height; + this._totalLineHeight = o.totalLineHeight; + this._fontAscent = o.fontAscent; + this._boundaries = o.boundaries; + el = null; + this.setCoords(); + }, + _initDummyElementForCufon: function() { + var el = fabric.document.createElement("pre"), container = fabric.document.createElement("div"); + container.appendChild(el); + if (typeof G_vmlCanvasManager === "undefined") { + el.innerHTML = this.text; + } else { + el.innerText = this.text.replace(/\r?\n/gi, "\r"); + } + el.style.fontSize = this.fontSize + "px"; + el.style.letterSpacing = "normal"; + return el; } - else { - // IE 7 & 8 drop newlines and white space on text nodes - // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html - // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp - el.innerText = this.text.replace(/\r?\n/gi, '\r'); - } - - el.style.fontSize = this.fontSize + 'px'; - el.style.letterSpacing = 'normal'; - - return el; - } }); - (function() { - - var clone = fabric.util.object.clone; - - /** - * IText class (introduced in v1.4) - * @class fabric.IText - * @extends fabric.Text - * @mixes fabric.Observable - * - * @fires changed ("text:changed" when observing canvas) - * @fires editing:entered ("text:editing:entered" when observing canvas) - * @fires editing:exited ("text:editing:exited" when observing canvas) - * - * @return {fabric.IText} thisArg - * @see {@link fabric.IText#initialize} for constructor definition - * - *