diff --git a/.npmignore b/.npmignore index 8882ec6f..9d0a93ac 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ src/ lib/ +dist/all.min.js +dist/all.min.js.gz .DS_Store HEADER.js -build_docs.js build.js \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 012217c4..9f57b40c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: node_js node_js: - 0.6 - 0.8 + - 0.9 before_install: - sudo apt-get update -qq - sudo apt-get install -qq libgif-dev libpng-dev libjpeg8-dev libpango1.0-dev libcairo2-dev \ No newline at end of file diff --git a/HEADER.js b/HEADER.js index a223052c..c3ccb51f 100644 --- a/HEADER.js +++ b/HEADER.js @@ -1,6 +1,6 @@ -/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */ +/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.0.0" }; +var fabric = fabric || { version: "1.0.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; diff --git a/LICENSE b/LICENSE index 09467c74..cdd6c9d3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008-2012 Printio (Juriy Zaytsev, Maxim Chernyak) +Copyright (c) 2008-2013 Printio (Juriy Zaytsev, Maxim Chernyak) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index dd6cf323..ce0bb464 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ Using Fabric.js, you can create and populate objects on canvas; objects like simple geometrical shapes — rectangles, circles, ellipses, polygons, or more complex shapes consisting of hundreds or thousands of simple paths. You can then scale, move, and rotate these objects with the mouse; modify their properties — color, transparency, z-index, etc. You can also manipulate these objects altogether — grouping them with a simple mouse selection. -Contributions are very much welcome! +[Contributions](https://github.com/kangax/fabric.js/wiki/Love-Fabric%3F-Help-us-by...) are very much welcome! ### Goals - Unit tested (1400+ tests at the moment) -- Modular (~20 small "classes" and modules) +- Modular (~40 small "classes", modules, mixins) - Cross-browser - [Fast](https://github.com/kangax/fabric.js/wiki/Focus-on-speed) - Encapsulated in one object @@ -89,7 +89,7 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print Documentation is always available at [http://fabricjs.com/docs/](http://fabricjs.com/docs/). You can also build it locally, following step 4 from the "Building" section of this README. -Also see [presentation from BK.js](http://www.slideshare.net/kangax/fabricjs-building-acanvaslibrarybk) and [presentation from Falsy Values](http://www.slideshare.net/kangax/fabric-falsy-values-8067834) for an overview of fabric.js, how it works, and its features. +Also see [official 4-part intro series](http://fabricjs.com/articles), [presentation from BK.js](http://www.slideshare.net/kangax/fabricjs-building-acanvaslibrarybk) and [presentation from Falsy Values](http://www.slideshare.net/kangax/fabric-falsy-values-8067834) for an overview of fabric.js, how it works, and its features. ### Optional modules @@ -103,6 +103,7 @@ These are the optional modules that could be specified for inclusion, when build - **easing** - Adds support for animation easing functions - **node** — Adds support for running fabric under node.js, with help of [jsdom](https://github.com/tmpvar/jsdom) and [node-canvas](https://github.com/learnboost/node-canvas) libraries. - **freedrawing** - Adds support for free drawing +- **gestures** - Adds support for multitouch gestures ### Examples of use @@ -131,11 +132,11 @@ Follow [@fabric.js](http://twitter.com/fabricjs) or [@kangax](http://twitter.com - Ernest Delgado for the original idea of [manipulating images on canvas](http://www.ernestdelgado.com/archive/canvas/). - [Maxim "hakunin" Chernyak](http://twitter.com/hakunin) for ideas, and help with various parts of the library throughout its life. - [Sergey Nisnevich](http://nisnya.com) for help with geometry logic. -- Github contributors: @Kingsquare, @cleercode, @jarek-itmore, @sunrei, @khronnuz, @ollym, @Kienz, @willmcneilly, @davidjrice +- Github contributors: @Kingsquare, @cleercode, @jarek-itmore, @sunrei, @khronnuz, @ollym, @Kienz, @garg, @sjpemberton09, @willmcneilly, @davidjrice, @coulix, and [more](https://github.com/kangax/fabric.js/graphs/contributors) ### MIT License -Copyright (c) 2008-2012 Printio (Juriy Zaytsev, Maxim Chernyak) +Copyright (c) 2008-2013 Printio (Juriy Zaytsev, Maxim Chernyak) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build.js b/build.js index 304742b2..5a758890 100644 --- a/build.js +++ b/build.js @@ -18,13 +18,13 @@ var minifier = buildArgsAsObject.minifier || 'uglifyjs'; var mininfierCmd; if (minifier === 'yui') { - mininfierCmd = 'java -jar lib/yuicompressor-2.4.2.jar dist/all.js -o dist/all.min.js'; + mininfierCmd = 'java -jar lib/yuicompressor-2.4.6.jar dist/all.js -o dist/all.min.js'; } else if (minifier === 'closure') { mininfierCmd = 'java -jar lib/google_closure_compiler.jar --js dist/all.js --js_output_file dist/all.min.js'; } else if (minifier === 'uglifyjs') { - mininfierCmd = 'uglifyjs -o dist/all.min.js dist/all.js'; + mininfierCmd = 'uglifyjs --output dist/all.min.js dist/all.js'; } var includeAllModules = modulesToInclude.length === 1 && modulesToInclude[0] === 'ALL'; @@ -91,7 +91,7 @@ var filesToInclude = [ ifSpecifiedInclude('gestures', 'lib/event.js'), 'src/log.js', - 'src/observable.js', + 'src/observable.mixin.js', 'src/util/misc.js', 'src/util/lang_array.js', @@ -109,6 +109,8 @@ var filesToInclude = [ ifSpecifiedInclude('parser', 'src/parser.js'), 'src/gradient.class.js', + 'src/pattern.class.js', + 'src/shadow.class.js', 'src/point.class.js', 'src/intersection.class.js', 'src/color.class.js', @@ -123,13 +125,19 @@ var filesToInclude = [ ifSpecifiedInclude('freedrawing', 'src/pattern_brush.class.js'), ifSpecifiedInclude('interaction', 'src/canvas.class.js'), + ifSpecifiedInclude('interaction', 'src/canvas_events.mixin.js'), - 'src/canvas.animation.js', + 'src/canvas_animation.mixin.js', - ifSpecifiedInclude('serialization', 'src/canvas.serialization.js'), - ifSpecifiedInclude('gestures', 'src/canvas.gestures.js'), + ifSpecifiedInclude('serialization', 'src/canvas_serialization.mixin.js'), + ifSpecifiedInclude('gestures', 'src/canvas_gestures.mixin.js'), 'src/object.class.js', + 'src/object_origin.mixin.js', + 'src/object_geometry.mixin.js', + + ifSpecifiedInclude('interaction', 'src/object_interactivity.mixin.js'), + 'src/line.class.js', 'src/circle.class.js', 'src/triangle.class.js', @@ -142,7 +150,7 @@ var filesToInclude = [ 'src/group.class.js', 'src/image.class.js', - ifSpecifiedInclude('object_straightening', 'src/object_straightening.js'), + ifSpecifiedInclude('object_straightening', 'src/object_straightening.mixin.js'), ifSpecifiedInclude('image_filters', 'src/image_filters.js'), diff --git a/dist/all.js b/dist/all.js index 4634b11e..6e7aeb2c 100644 --- a/dist/all.js +++ b/dist/all.js @@ -1,7 +1,11 @@ /* build: `node build.js modules=ALL exclude=gestures` */ +<<<<<<< HEAD /*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */ +======= +/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ +>>>>>>> master -var fabric = fabric || { version: "1.0.0" }; +var fabric = fabric || { version: "1.0.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; @@ -1860,6 +1864,9 @@ fabric.Observable.on = fabric.Observable.observe; fabric.Observable.off = fabric.Observable.stopObserving; (function() { + var sqrt = Math.sqrt, + atan2 = Math.atan2; + /** * @namespace */ @@ -2155,6 +2162,55 @@ fabric.Observable.off = fabric.Observable.stopObserving; } } + /** + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @method drawDashedLine + * @param ctx {Canvas} context + * @param x {Number} start x coordinate + * @param y {Number} start y coordinate + * @param x2 {Number} end x coordinate + * @param y2 {Number} end y coordinate + * @param da {Array} dash array pattern + */ + function drawDashedLine(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(); + } + + function createCanvasElement() { + var canvasEl = fabric.document.createElement('canvas'); + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); + } + return canvasEl; + } + fabric.util.removeFromArray = removeFromArray; fabric.util.degreesToRadians = degreesToRadians; fabric.util.radiansToDegrees = radiansToDegrees; @@ -2168,6 +2224,9 @@ fabric.Observable.off = fabric.Observable.stopObserving; fabric.util.enlivenObjects = enlivenObjects; fabric.util.groupSVGElements = groupSVGElements; fabric.util.populateWithProperties = populateWithProperties; + fabric.util.drawDashedLine = drawDashedLine; + fabric.util.createCanvasElement = createCanvasElement; + })(); (function() { @@ -2820,27 +2879,41 @@ fabric.util.string = { * @method getPointer * @memberOf fabric.util * @param {Event} event + * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn */ - function getPointer(event) { + function getPointer(event, upperCanvasEl) { event || (event = fabric.window.event); var element = event.target || (typeof event.srcElement !== 'unknown' ? event.srcElement : null), + body = fabric.document.body || {scrollLeft: 0, scrollTop: 0}, + docElement = fabric.document.documentElement, + orgElement = element, scrollLeft = 0, scrollTop = 0, firstFixedAncestor; while (element && element.parentNode && !firstFixedAncestor) { - element = element.parentNode; + element = element.parentNode; - if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element; + if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element; + if (element !== fabric.document && orgElement !== upperCanvasEl && fabric.util.getElementPosition(element) === 'absolute') { + scrollLeft = 0; + scrollTop = 0; + } + else if (element === fabric.document && orgElement !== upperCanvasEl) { + scrollLeft = body.scrollLeft || docElement.scrollLeft || 0; + scrollTop = body.scrollTop || docElement.scrollTop || 0; + } + else { scrollLeft += element.scrollLeft || 0; scrollTop += element.scrollTop || 0; + } } return { - x: pointerX(event) + scrollLeft, - y: pointerY(event) + scrollTop + x: pointerX(event) + scrollLeft, + y: pointerY(event) + scrollTop }; } @@ -2857,10 +2930,10 @@ fabric.util.string = { if (fabric.isTouchSupported) { pointerX = function(event) { - return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX; + return (event.touches && event.touches[0] ? (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX); }; pointerY = function(event) { - return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY; + return (event.touches && event.touches[0] ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY); }; } @@ -3984,8 +4057,8 @@ fabric.util.string = { checkIfDone(); } } - catch(e) { - fabric.log(e.message || e); + catch(err) { + fabric.log(err); } } else { @@ -4410,11 +4483,11 @@ fabric.util.string = { /** * Returns an instance of CanvasGradient - * @method toLiveGradient + * @method toLive * @param ctx * @return {CanvasGradient} */ - toLiveGradient: function(ctx) { + toLive: function(ctx) { var gradient = ctx.createLinearGradient( this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2); @@ -4554,6 +4627,145 @@ fabric.util.string = { fabric.getGradientDefs = getGradientDefs; })(); +/** + * Pattern class + * @class Pattern + * @memberOf fabric + */ +fabric.Pattern = fabric.util.createClass(/** @scope fabric.Pattern.prototype */ { + + /** + * Repeat property of a pattern (one of repeat, repeat-x, repeat-y) + * @property + * @type String + */ + repeat: 'repeat', + + /** + * Constructor + * @method initialize + * @param {Object} [options] + * @return {fabric.Pattern} thisArg + */ + initialize: function(options) { + options || (options = { }); + + if (options.source) { + this.source = typeof options.source === 'string' + ? new Function(options.source) + : options.source; + } + if (options.repeat) { + this.repeat = options.repeat; + } + }, + + /** + * Returns object representation of a pattern + * @method toObject + * @return {Object} + */ + toObject: function() { + + var source; + + // callback + if (typeof this.source === 'function') { + source = String(this.source) + .match(/function\s+\w*\s*\(.*\)\s+\{([\s\S]*)\}/)[1]; + } + // element + else if (typeof this.source.src === 'string') { + source = this.source.src; + } + + return { + source: source, + repeat: this.repeat + }; + }, + + /** + * Returns an instance of CanvasPattern + * @method toLive + * @param ctx + * @return {CanvasPattern} + */ + toLive: function(ctx) { + var source = typeof this.source === 'function' ? this.source() : this.source; + return ctx.createPattern(source, this.repeat); + } +}); +/** + * Shadow class + * @class Shadow + * @memberOf fabric + */ +fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { + + /** + * Shadow color + * @property + * @type String + */ + color: 'rgb(0,0,0)', + + /** + * Shadow blur + * @property + * @type Number + */ + blur: 0, + + /** + * Shadow horizontal offset + * @property + * @type Number + */ + offsetX: 0, + + /** + * Shadow vertical offset + * @property + * @type Number + */ + offsetY: 0, + + /** + * Constructor + * @method initialize + * @param [options] Options object with any of color, blur, offsetX, offsetX properties + * @return {fabric.Shadow} thisArg + */ + initialize: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Returns object representation of a shadow + * @method toObject + * @return {Object} + */ + toObject: function() { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + }, + + /** + * Returns SVG representation of a shadow + * @method toSVG + * @return {String} + */ + toSVG: function() { + + } +}); (function(global) { "use strict"; @@ -5494,6 +5706,9 @@ fabric.util.string = { if (options.backgroundImage) { this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); + } this.calcOffset(); }, @@ -5557,6 +5772,33 @@ fabric.util.string = { return this; }, + /** + * Sets background color for this canvas + * @method setBackgroundColor + * @param {String|fabric.Pattern} Color of pattern to set background color to + * @param {Function} callback callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + */ + setBackgroundColor: function(backgroundColor, callback) { + if (backgroundColor.source) { + var _this = this; + fabric.util.loadImage(backgroundColor.source, function(img) { + _this.backgroundColor = new fabric.Pattern({ + source: img, + pattern: backgroundColor.pattern + }); + callback && callback(); + }); + } + else { + this.backgroundColor = backgroundColor; + callback && callback(); + } + + return this; + }, + /** * @private * @method _createCanvasElement @@ -5578,12 +5820,8 @@ fabric.util.string = { * @param {HTMLElement} element */ _initCanvasElement: function(element) { - if (typeof element.getContext === 'undefined' && - typeof G_vmlCanvasManager !== 'undefined' && - G_vmlCanvasManager.initElement) { + fabric.util.createCanvasElement(element); - G_vmlCanvasManager.initElement(element); - } if (typeof element.getContext === 'undefined') { throw CANVAS_INIT_ERROR; } @@ -5856,6 +6094,7 @@ fabric.util.string = { if (this.contextTop) { this.clearContext(this.contextTop); } + this.fire('canvas:cleared'); this.renderAll(); return this; }, @@ -5879,12 +6118,17 @@ fabric.util.string = { this.clearContext(canvasToDrawOn); } + this.fire('before:render'); + if (this.clipTo) { this._clipCanvas(canvasToDrawOn); } if (this.backgroundColor) { - canvasToDrawOn.fillStyle = this.backgroundColor; + canvasToDrawOn.fillStyle = this.backgroundColor.toLive + ? this.backgroundColor.toLive(canvasToDrawOn) + : this.backgroundColor; + canvasToDrawOn.fillRect(0, 0, this.width, this.height); } @@ -5892,8 +6136,6 @@ fabric.util.string = { this._drawBackroundImage(canvasToDrawOn); } - this.fire('before:render'); - var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { if (!activeGroup || @@ -6033,6 +6275,8 @@ fabric.util.string = { var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) ? canvasEl.toDataURL('image/' + format, quality) : canvasEl.toDataURL('image/' + format); + + this.contextTop && this.clearContext(this.contextTop); this.renderAll(); return data; }, @@ -6086,6 +6330,7 @@ fabric.util.string = { this.setActiveObject(activeObject); } + this.contextTop && this.clearContext(this.contextTop); this.renderAll(); return dataURL; @@ -6220,7 +6465,9 @@ fabric.util.string = { } return object; }, this), - background: this.backgroundColor + background: (this.backgroundColor && this.backgroundColor.toObject) + ? this.backgroundColor.toObject() + : this.backgroundColor }; if (this.backgroundImage) { data.backgroundImage = this.backgroundImage.src; @@ -6240,13 +6487,22 @@ fabric.util.string = { * Returns SVG representation of canvas * @function * @method toSVG + * @param {Object} [options] Options for SVG output ("suppressPreamble: true" + * will start the svg output directly at "', - '', + toSVG: function(options) { + options || (options = { }); + var markup = []; + + if (!options.suppressPreamble) { + markup.push( + '', + '' + ); + } + markup.push( '', 'Created with Fabric.js ', fabric.version, '', fabric.createSVGFontFacesMarkup(this.getObjects()) - ]; + ); if (this.backgroundImage) { markup.push( @@ -6305,14 +6561,22 @@ fabric.util.string = { * @return {Object} removed object */ remove: function (object) { - removeFromArray(this._objects, object); + // removing active object should fire "selection:cleared" events if (this.getActiveObject() === object) { - - // removing active object should fire "selection:cleared" events this.fire('before:selection:cleared', { target: object }); this.discardActiveObject(); this.fire('selection:cleared'); } + + var objects = this._objects; + var index = objects.indexOf(object); + + // removing any object should fire "objct:removed" events + if (index !== -1) { + objects.splice(index,1); + this.fire('object:removed', { target: object }); + } + this.renderAll(); return object; }, @@ -6537,11 +6801,8 @@ fabric.util.string = { * `null` if canvas element or context can not be initialized */ supports: function (methodName) { - var el = fabric.document.createElement('canvas'); + var el = fabric.util.createCanvasElement(); - if (typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(el); - } if (!el || !el.getContext) { return null; } @@ -6845,6 +7106,20 @@ fabric.util.string = { return path; }, + /** + * Creates fabric.Path object to add on canvas + * @method createPath + * @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.canvas.freeDrawingColor; + path.strokeWidth = this.canvas.freeDrawingLineWidth; + return path; + }, + /** * On mouseup after drawing the path on contextTop canvas * we use the points captured to create an new fabric path object @@ -6855,11 +7130,15 @@ fabric.util.string = { _finalizeAndAddPath: function() { var ctx = this.canvas.contextTop; ctx.closePath(); +<<<<<<< HEAD var path = this._getSVGPathData(); path = path.join(''); +======= +>>>>>>> master - if (path === "M 0 0 Q 0 0 0 0 L 0 0") { + 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, @@ -6868,27 +7147,32 @@ fabric.util.string = { return; } +<<<<<<< HEAD var p = new fabric.Path(path); p.fill = null; p.stroke = this.color; p.strokeWidth = this.width; this.canvas.add(p); +======= +>>>>>>> master // set path origin coordinates based on our bounding box var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2; var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2; this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); - p.set({ left: originLeft, top: originTop }); + var path = this.createPath(pathData); + path.set({ left: originLeft, top: originTop }); - // does not change position - p.setCoords(); + this.canvas.add(path); + path.setCoords(); + this.canvas.contextTop && this.canvas.clearContext(this.canvas.contextTop); this.canvas.renderAll(); // fire event 'path' created - this.canvas.fire('path:created', { path: p }); + this.canvas.fire('path:created', { path: path }); } }); })(); @@ -7033,22 +7317,8 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot var extend = fabric.util.object.extend, getPointer = fabric.util.getPointer, - addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, - cursorMap = { - 'tr': 'ne-resize', - 'br': 'se-resize', - 'bl': 'sw-resize', - 'tl': 'nw-resize', - 'ml': 'w-resize', - 'mt': 'n-resize', - 'mr': 'e-resize', - 'mb': 's-resize' - }, - - sqrt = Math.sqrt, atan2 = Math.atan2, abs = Math.abs, min = Math.min, @@ -7200,427 +7470,13 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot _initInteractive: function() { this._currentTransform = null; this._groupSelector = null; + this.freeDrawing = fabric.FreeDrawing && new fabric.FreeDrawing(this); this._initWrapperElement(); this._createUpperCanvas(); this._initEvents(); - - this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); - this.calcOffset(); }, - /** - * Adds mouse listeners to canvas - * @method _initEvents - * @private - * See configuration documentation for more details. - */ - _initEvents: function () { - var _this = this; - - this._onMouseDown = function (e) { - _this.__onMouseDown(e); - - addListener(fabric.document, 'mouseup', _this._onMouseUp); - fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp); - - addListener(fabric.document, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove); - - removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); - }; - - this._onMouseUp = function (e) { - _this.__onMouseUp(e); - - removeListener(fabric.document, 'mouseup', _this._onMouseUp); - fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp); - - removeListener(fabric.document, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove); - - addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); - }; - - this._onMouseMove = function (e) { - e.preventDefault && e.preventDefault(); - _this.__onMouseMove(e); - }; - - this._onResize = function () { - _this.calcOffset(); - }; - - - addListener(fabric.window, 'resize', this._onResize); - - if (fabric.isTouchSupported) { - addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof Event !== 'undefined' && 'add' in Event) { - Event.add(this.upperCanvasEl, 'gesture', function(e, s) { - _this.__onTransformGesture(e, s); - }); - } - } - else { - addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - } - }, - - /** - * 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. - * @method __onMouseUp - * @param {Event} e Event object fired on mouseup - * - */ - __onMouseUp: function (e) { - - var target; - - if (this.isDrawingMode && this._isCurrentlyDrawing) { - this._isCurrentlyDrawing = false; - this.freeDrawingBrush.onMouseUp(); - this.fire('mouse:up', { e: e }); - return; - } - - if (this._currentTransform) { - - var transform = this._currentTransform; - - target = transform.target; - if (target._scaling) { - target._scaling = false; - } - - // determine the new coords everytime the image changes its position - var i = this._objects.length; - while (i--) { - this._objects[i].setCoords(); - } - - target.isMoving = false; - - // 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'); - } - - if (this._previousOriginX) { - this._adjustPosition(this._currentTransform.target, this._previousOriginX); - this._previousOriginX = null; - } - } - - this._currentTransform = null; - - if (this._groupSelector) { - // group selection was completed, determine its bounds - this._findSelectedObjects(e); - } - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.setObjectsCoords(); - activeGroup.set('isMoving', false); - this._setCursor(this.defaultCursor); - } - - // clear selection - this._groupSelector = null; - this.renderAll(); - - this._setCursorFromEvent(e, target); - - // fix for FF - this._setCursor(''); - - var _this = this; - setTimeout(function () { - _this._setCursorFromEvent(e, target); - }, 50); - - this.fire('mouse:up', { target: target, e: e }); - target && target.fire('mouseup', { 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. - * @method __onMouseDown - * @param e {Event} Event object fired on mousedown - * - */ - __onMouseDown: function (e) { - - var pointer; - - // accept only left clicks - var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; - if (!isLeftClick && !fabric.isTouchSupported) return; - - if (this.isDrawingMode) { - pointer = this.getPointer(e); - - this._isCurrentlyDrawing = true; - this.discardActiveObject().renderAll(); - - this.freeDrawingBrush.onMouseDown(pointer); - this.fire('mouse:down', { e: e }); - return; - } - - // ignore if some object is being transformed at this moment - if (this._currentTransform) return; - - var target = this.findTarget(e), corner; - pointer = this.getPointer(e); - - if (this._shouldClearSelection(e)) { - this._groupSelector = { - ex: pointer.x, - ey: pointer.y, - top: 0, - left: 0 - }; - this.deactivateAllWithDispatch(); - } - else { - // determine if it's a drag or rotate case - this.stateful && target.saveState(); - - if ((corner = target._findTargetCorner(e, this._offset))) { - this.onBeforeScaleRotate(target); - } - - if (this._shouldHandleGroupLogic(e, target)) { - this._handleGroupLogic(e, target); - target = this.getActiveGroup(); - } - else { - if (target !== this.getActiveGroup()) { - this.deactivateAll(); - } - this.setActiveObject(target, e); - } - - this._setupCurrentTransform(e, target); - } - // we must renderAll so that active image is placed on the top canvas - this.renderAll(); - - this.fire('mouse:down', { target: target, e: e }); - target && target.fire('mousedown', { e: e }); - - // center origin when rotating - if (corner === 'mtr') { - this._previousOriginX = this._currentTransform.target.originX; - this._adjustPosition(this._currentTransform.target, 'center'); - this._currentTransform.left = this._currentTransform.target.left; - this._currentTransform.top = this._currentTransform.target.top; - } - }, - - /** - * @method _shouldHandleGroupLogic - * @param e {Event} - * @param target {fabric.Object} - * @return {Boolean} - */ - _shouldHandleGroupLogic: function(e, target) { - var activeObject = this.getActiveObject(); - return e.shiftKey && - (this.getActiveGroup() || (activeObject && activeObject !== target)) - && this.selection; - }, - - /** - * 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. - * @method __onMouseMove - * @param e {Event} Event object fired on mousemove - * - */ - __onMouseMove: function (e) { - - var target, pointer; - - if (this.isDrawingMode) { - if (this._isCurrentlyDrawing) { - pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); - } - this.upperCanvasEl.style.cursor = this.freeDrawingCursor; - this.fire('mouse:move', { e: e }); - return; - } - - var groupSelector = this._groupSelector; - - // We initially clicked in an empty area, so we draw a box for multiple selection. - if (groupSelector !== null) { - pointer = getPointer(e); - - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; - this.renderTop(); - } - else if (!this._currentTransform) { - - // alias style to elimintate unnecessary lookup - var style = this.upperCanvasEl.style; - - // Here we are hovering the canvas then we will determine - // what part of the pictures we are hovering to change the caret symbol. - // We won't do that while dragging or rotating in order to improve the - // performance. - target = this.findTarget(e); - - if (!target) { - // image/text was hovered-out from, we remove its borders - for (var i = this._objects.length; i--; ) { - if (this._objects[i] && !this._objects[i].active) { - this._objects[i].setActive(false); - } - } - style.cursor = this.defaultCursor; - } - else { - // set proper cursor - this._setCursorFromEvent(e, target); - } - } - else { - // object is being transformed (scaled/rotated/moved/etc.) - pointer = getPointer(e); - - var x = pointer.x, - y = pointer.y; - - this._currentTransform.target.isMoving = true; - - var t = this._currentTransform, reset = false; - if ( - (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') - && - ( - // Switch from a normal resize to center-based - (e.altKey && (t.originX !== 'center' || t.originY !== 'center')) - || - // Switch from center-based resize to normal one - (!e.altKey && t.originX === 'center' && t.originY === 'center') - ) - ) { - this._resetCurrentTransform(e); - reset = true; - } - - if (this._currentTransform.action === 'rotate') { - this._rotateObject(x, y); - - this.fire('object:rotating', { - target: this._currentTransform.target - }); - this._currentTransform.target.fire('rotating'); - } - else if (this._currentTransform.action === 'scale') { - // rotate object only if shift key is not pressed - // and if it is not a group we are transforming - - // TODO - /*if (!e.shiftKey) { - this._rotateObject(x, y); - - this.fire('object:rotating', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('rotating'); - }*/ - - // if (!this._currentTransform.target.hasRotatingPoint) { - // this._scaleObject(x, y); - // this.fire('object:scaling', { - // target: this._currentTransform.target - // }); - // this._currentTransform.target.fire('scaling'); - // } - - if (e.shiftKey || this.uniScaleTransform) { - this._currentTransform.currentAction = 'scale'; - this._scaleObject(x, y); - } - else { - if (!reset && t.currentAction === 'scale') { - // Switch from a normal resize to proportional - this._resetCurrentTransform(e); - } - - this._currentTransform.currentAction = 'scaleEqually'; - this._scaleObject(x, y, 'equally'); - } - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - } - // else if (this._currentTransform.action === 'scale') { - // this._scaleObject(x, y); - // this.fire('object:scaling', { - // target: this._currentTransform.target - // }); - // this._currentTransform.target.fire('scaling'); - // } - else if (this._currentTransform.action === 'scaleX') { - this._scaleObject(x, y, 'x'); - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('scaling', { e: e }); - } - else if (this._currentTransform.action === 'scaleY') { - this._scaleObject(x, y, 'y'); - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('scaling', { e: e }); - } - else { - this._translateObject(x, y); - - this.fire('object:moving', { - target: this._currentTransform.target, - e: e - }); - - this._setCursor(this.moveCursor); - - this._currentTransform.target.fire('moving', { e: e }); - } - // only commit here. when we are actually moving the pictures - this.renderAll(); - } - this.fire('mouse:move', { target: target, e: e }); - target && target.fire('mousemove', { e: e }); - }, - /** * Resets the current transform to its original values and chooses the type of resizing based on the event * @method _resetCurrentTransform @@ -7628,6 +7484,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot */ _resetCurrentTransform: function(e) { var t = this._currentTransform; + t.target.set('scaleX', t.original.scaleX); t.target.set('scaleY', t.original.scaleY); t.target.set('left', t.original.left); @@ -7692,6 +7549,18 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot * @private * @method _normalizePointer */ +<<<<<<< HEAD + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEvents(); + + this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); + + this.calcOffset(); +======= _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), @@ -7709,6 +7578,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot y -= activeGroup.top; } return { x: x, y: y }; +>>>>>>> master }, /** @@ -7783,7 +7653,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot _setupCurrentTransform: function (e, target) { var action = 'drag', corner, - pointer = getPointer(e); + pointer = getPointer(e, target.canvas.upperCanvasEl); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -7837,6 +7707,14 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot mouseYSign: 1 }; +<<<<<<< HEAD + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._isCurrentlyDrawing = false; + this.freeDrawingBrush.onMouseUp(); + this.fire('mouse:up', { e: e }); + return; + } +======= this._currentTransform.original = { left: target.left, top: target.top, @@ -7845,10 +7723,24 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot originX: originX, originY: originY }; +>>>>>>> master this._resetCurrentTransform(e); }, + /** + * @method _shouldHandleGroupLogic + * @param e {Event} + * @param target {fabric.Object} + * @return {Boolean} + */ + _shouldHandleGroupLogic: function(e, target) { + var activeObject = this.getActiveObject(); + return e.shiftKey && + (this.getActiveGroup() || (activeObject && activeObject !== target)) + && this.selection; + }, + /** * @private * @method _handleGroupLogic @@ -7933,6 +7825,17 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot var lockScalingX = target.get('lockScalingX'), lockScalingY = target.get('lockScalingY'); +<<<<<<< HEAD + if (this.isDrawingMode) { + pointer = this.getPointer(e); + + this._isCurrentlyDrawing = true; + this.discardActiveObject().renderAll(); + + this.freeDrawingBrush.onMouseDown(pointer); + this.fire('mouse:down', { e: e }); + return; +======= if (lockScalingX && lockScalingY) return; // Get the constraint point @@ -7941,6 +7844,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot if (t.originX === 'right') { localMouse.x *= -1; +>>>>>>> master } else if (t.originX === 'center') { localMouse.x *= t.mouseXSign * 2; @@ -7993,6 +7897,16 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot lockScalingY || target.set('scaleY', newScaleY); } +<<<<<<< HEAD + if (this.isDrawingMode) { + if (this._isCurrentlyDrawing) { + pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseMove(pointer); + } + this.upperCanvasEl.style.cursor = this.freeDrawingCursor; + this.fire('mouse:move', { e: e }); + return; +======= // Check if we flipped if (newScaleX < 0) { @@ -8008,6 +7922,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot t.originY = 'bottom'; else if (t.originY === 'bottom') t.originY = 'top'; +>>>>>>> master } // Make sure the constraints apply @@ -8050,43 +7965,6 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot target.setAngle(0); }, - /** - * Sets the cursor depending on where the canvas is being hovered. - * Note: very buggy in Opera - * @method _setCursorFromEvent - * @param e {Event} Event object - * @param target {Object} Object that the mouse is hovering, if so. - */ - _setCursorFromEvent: function (e, target) { - var s = this.upperCanvasEl.style; - if (!target) { - s.cursor = this.defaultCursor; - return false; - } - else { - var activeGroup = this.getActiveGroup(); - // only show proper corner when group selection is not active - var corner = !!target._findTargetCorner - && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(e, this._offset); - - if (!corner) { - s.cursor = this.hoverCursor; - } - else { - if (corner in cursorMap) { - s.cursor = cursorMap[corner]; - } else if (corner === 'mtr' && target.hasRotatingPoint) { - s.cursor = this.rotationCursor; - } else { - s.cursor = this.defaultCursor; - return false; - } - } - } - return true; - }, - /** * @method _drawSelection * @private @@ -8113,13 +7991,17 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot // selection border if (this.selectionDashArray.length > 1) { + var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft); var py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); + ctx.beginPath(); - this.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray); - this.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray); - this.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray); - this.drawDashedLine(ctx, px+aleft-1, py, px+aleft-1, py+atop, this.selectionDashArray); + + 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(); } @@ -8133,47 +8015,6 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot } }, - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @method drawDashedLine - * @param ctx {Canvas} context - * @param x {Number} start x coordinate - * @param y {Number} start y coordinate - * @param x2 {Number} end x coordinate - * @param y2 {Number} end y coordinate - * @param da {Array} 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(); - }, - /** * @private * @method _findSelectedObjects @@ -8230,7 +8071,8 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && - this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay)) { + this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && + this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) { target = this.lastRenderedObjectWithControlsAboveOverlay; return target; } @@ -8278,7 +8120,7 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot * @return {Object} object with "x" and "y" number values */ getPointer: function (e) { - var pointer = getPointer(e); + var pointer = getPointer(e, this.upperCanvasEl); return { x: pointer.x - this._offset.left, y: pointer.y - this._offset.top @@ -8483,51 +8325,6 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot this.fire('selection:cleared'); } return this; - }, - - /** - * @private - * @method _adjustPosition - * @param obj - * @param {String} to One of left, center, right - */ - _adjustPosition: function(obj, to) { - - var angle = fabric.util.degreesToRadians(obj.angle); - - var hypotHalf = obj.getWidth() / 2; - var xHalf = Math.cos(angle) * hypotHalf; - var yHalf = Math.sin(angle) * hypotHalf; - - var hypotFull = obj.getWidth(); - var xFull = Math.cos(angle) * hypotFull; - var yFull = Math.sin(angle) * hypotFull; - - if (obj.originX === 'center' && to === 'left' || - obj.originX === 'right' && to === 'center') { - // move half left - obj.left -= xHalf; - obj.top -= yHalf; - } - else if (obj.originX === 'left' && to === 'center' || - obj.originX === 'center' && to === 'right') { - // move half right - obj.left += xHalf; - obj.top += yHalf; - } - else if (obj.originX === 'left' && to === 'right') { - // move full right - obj.left += xFull; - obj.top += yFull; - } - else if (obj.originX === 'right' && to === 'left') { - // move full left - obj.left -= xFull; - obj.top -= yFull; - } - - obj.setCoords(); - obj.originX = to; } }; @@ -8554,7 +8351,491 @@ fabric.CircleBrush = fabric.util.createClass( /** @scope fabric.CircleBrush.prot */ fabric.Element = fabric.Canvas; })(); -fabric.util.object.extend(fabric.StaticCanvas.prototype, { + +(function(){ + + var cursorMap = { + 'tr': 'ne-resize', + 'br': 'se-resize', + 'bl': 'sw-resize', + 'tl': 'nw-resize', + 'ml': 'w-resize', + 'mt': 'n-resize', + 'mr': 'e-resize', + 'mb': 's-resize' + }, + addListener = fabric.util.addListener, + removeListener = fabric.util.removeListener, + getPointer = fabric.util.getPointer; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ { + + /** + * Adds mouse listeners to canvas + * @method _initEvents + * @private + * See configuration documentation for more details. + */ + _initEvents: function () { + var _this = this; + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onResize = this._onResize.bind(this); + + addListener(fabric.window, 'resize', this._onResize); + + if (fabric.isTouchSupported) { + addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); + addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (typeof Event !== 'undefined' && 'add' in Event) { + Event.add(this.upperCanvasEl, 'gesture', function(e, s) { + _this.__onTransformGesture(e, s); + }); + } + } + else { + addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + } + }, + + /** + * @method _onMouseDown + * @private + */ + _onMouseDown: function (e) { + this.__onMouseDown(e); + + !fabric.isTouchSupported && addListener(fabric.document, 'mouseup', this._onMouseUp); + fabric.isTouchSupported && addListener(fabric.document, 'touchend', this._onMouseUp); + + !fabric.isTouchSupported && addListener(fabric.document, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && addListener(fabric.document, 'touchmove', this._onMouseMove); + + !fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + }, + + /** + * @method _onMouseUp + * @private + */ + _onMouseUp: function (e) { + this.__onMouseUp(e); + + !fabric.isTouchSupported && removeListener(fabric.document, 'mouseup', this._onMouseUp); + fabric.isTouchSupported && removeListener(fabric.document, 'touchend', this._onMouseUp); + + !fabric.isTouchSupported && removeListener(fabric.document, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', this._onMouseMove); + + !fabric.isTouchSupported && addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + }, + + /** + * @method _onMouseMove + * @private + */ + _onMouseMove: function (e) { + e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + + /** + * @method _onResize + * @private + */ + _onResize: function () { + this.calcOffset(); + }, + + /** + * 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. + * @method __onMouseUp + * @param {Event} e Event object fired on mouseup + * + */ + __onMouseUp: function (e) { + + var target; + + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this.freeDrawing._finalizeAndAddPath(); + this.fire('mouse:up', { e: e }); + return; + } + + if (this._currentTransform) { + + var transform = this._currentTransform; + + target = transform.target; + if (target._scaling) { + target._scaling = false; + } + + // determine the new coords everytime the image changes its position + var i = this._objects.length; + while (i--) { + this._objects[i].setCoords(); + } + + target.isMoving = false; + + // 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'); + } + + if (this._previousOriginX) { + this._currentTransform.target.adjustPosition(this._previousOriginX); + this._previousOriginX = null; + } + } + + this._currentTransform = null; + + if (this._groupSelector) { + // group selection was completed, determine its bounds + this._findSelectedObjects(e); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.setObjectsCoords(); + activeGroup.set('isMoving', false); + this._setCursor(this.defaultCursor); + } + + // clear selection + this._groupSelector = null; + this.renderAll(); + + this._setCursorFromEvent(e, target); + + // fix for FF + this._setCursor(''); + + var _this = this; + setTimeout(function () { + _this._setCursorFromEvent(e, target); + }, 50); + + this.fire('mouse:up', { target: target, e: e }); + target && target.fire('mouseup', { 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. + * @method __onMouseDown + * @param e {Event} Event object fired on mousedown + * + */ + __onMouseDown: function (e) { + + var pointer; + + // accept only left clicks + var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; + if (!isLeftClick && !fabric.isTouchSupported) return; + + if (this.isDrawingMode) { + pointer = this.getPointer(e); + this.freeDrawing._prepareForDrawing(pointer); + + // capture coordinates immediately; + // this allows to draw dots (when movement never occurs) + this.freeDrawing._captureDrawingPath(pointer); + + this.fire('mouse:down', { e: e }); + return; + } + + // ignore if some object is being transformed at this moment + if (this._currentTransform) return; + + var target = this.findTarget(e), corner; + pointer = this.getPointer(e); + + if (this._shouldClearSelection(e)) { + this._groupSelector = { + ex: pointer.x, + ey: pointer.y, + top: 0, + left: 0 + }; + this.deactivateAllWithDispatch(); + } + else { + // determine if it's a drag or rotate case + this.stateful && target.saveState(); + + if ((corner = target._findTargetCorner(e, this._offset))) { + this.onBeforeScaleRotate(target); + } + + if (this._shouldHandleGroupLogic(e, target)) { + this._handleGroupLogic(e, target); + target = this.getActiveGroup(); + } + else { + if (target !== this.getActiveGroup()) { + this.deactivateAll(); + } + this.setActiveObject(target, e); + } + + this._setupCurrentTransform(e, target); + } + // we must renderAll so that active image is placed on the top canvas + this.renderAll(); + + this.fire('mouse:down', { target: target, e: e }); + target && target.fire('mousedown', { e: e }); + + // center origin when rotating + if (corner === 'mtr') { + this._previousOriginX = this._currentTransform.target.originX; + this._currentTransform.target.adjustPosition('center'); + this._currentTransform.left = this._currentTransform.target.left; + this._currentTransform.top = this._currentTransform.target.top; + } + }, + + /** + * 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. + * @method __onMouseMove + * @param e {Event} Event object fired on mousemove + * + */ + __onMouseMove: function (e) { + + var target, pointer; + + if (this.isDrawingMode) { + if (this._isCurrentlyDrawing) { + pointer = this.getPointer(e); + this.freeDrawing._captureDrawingPath(pointer); + + // redraw curve + // clear top canvas + this.clearContext(this.contextTop); + this.freeDrawing._render(this.contextTop); + } + this.upperCanvasEl.style.cursor = this.freeDrawingCursor; + this.fire('mouse:move', { e: e }); + return; + } + + var groupSelector = this._groupSelector; + + // We initially clicked in an empty area, so we draw a box for multiple selection. + if (groupSelector !== null) { + pointer = getPointer(e, this.upperCanvasEl); + + groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; + groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + this.renderTop(); + } + else if (!this._currentTransform) { + + // alias style to elimintate unnecessary lookup + var style = this.upperCanvasEl.style; + + // Here we are hovering the canvas then we will determine + // what part of the pictures we are hovering to change the caret symbol. + // We won't do that while dragging or rotating in order to improve the + // performance. + target = this.findTarget(e); + + if (!target) { + // image/text was hovered-out from, we remove its borders + for (var i = this._objects.length; i--; ) { + if (this._objects[i] && !this._objects[i].active) { + this._objects[i].setActive(false); + } + } + style.cursor = this.defaultCursor; + } + else { + // set proper cursor + this._setCursorFromEvent(e, target); + } + } + else { + // object is being transformed (scaled/rotated/moved/etc.) + pointer = getPointer(e, this.upperCanvasEl); + + var x = pointer.x, + y = pointer.y; + + this._currentTransform.target.isMoving = true; + + var t = this._currentTransform, reset = false; + if ( + (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') + && + ( + // Switch from a normal resize to center-based + (e.altKey && (t.originX !== 'center' || t.originY !== 'center')) + || + // Switch from center-based resize to normal one + (!e.altKey && t.originX === 'center' && t.originY === 'center') + ) + ) { + this._resetCurrentTransform(e); + reset = true; + } + + if (this._currentTransform.action === 'rotate') { + this._rotateObject(x, y); + + this.fire('object:rotating', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('rotating'); + } + else if (this._currentTransform.action === 'scale') { + // rotate object only if shift key is not pressed + // and if it is not a group we are transforming + + // TODO + /*if (!e.shiftKey) { + this._rotateObject(x, y); + + this.fire('object:rotating', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('rotating'); + }*/ + + // if (!this._currentTransform.target.hasRotatingPoint) { + // this._scaleObject(x, y); + // this.fire('object:scaling', { + // target: this._currentTransform.target + // }); + // this._currentTransform.target.fire('scaling'); + // } + + if (e.shiftKey || this.uniScaleTransform) { + this._currentTransform.currentAction = 'scale'; + this._scaleObject(x, y); + } + else { + if (!reset && t.currentAction === 'scale') { + // Switch from a normal resize to proportional + this._resetCurrentTransform(e); + } + + this._currentTransform.currentAction = 'scaleEqually'; + this._scaleObject(x, y, 'equally'); + } + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + } + // else if (this._currentTransform.action === 'scale') { + // this._scaleObject(x, y); + // this.fire('object:scaling', { + // target: this._currentTransform.target + // }); + // this._currentTransform.target.fire('scaling'); + // } + else if (this._currentTransform.action === 'scaleX') { + this._scaleObject(x, y, 'x'); + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('scaling', { e: e }); + } + else if (this._currentTransform.action === 'scaleY') { + this._scaleObject(x, y, 'y'); + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('scaling', { e: e }); + } + else { + this._translateObject(x, y); + + this.fire('object:moving', { + target: this._currentTransform.target, + e: e + }); + + this._setCursor(this.moveCursor); + + this._currentTransform.target.fire('moving', { e: e }); + } + // only commit here. when we are actually moving the pictures + this.renderAll(); + } + this.fire('mouse:move', { target: target, e: e }); + target && target.fire('mousemove', { e: e }); + }, + /** + * Sets the cursor depending on where the canvas is being hovered. + * Note: very buggy in Opera + * @method _setCursorFromEvent + * @param e {Event} Event object + * @param target {Object} Object that the mouse is hovering, if so. + */ + _setCursorFromEvent: function (e, target) { + var s = this.upperCanvasEl.style; + if (!target) { + s.cursor = this.defaultCursor; + return false; + } + else { + var activeGroup = this.getActiveGroup(); + // only show proper corner when group selection is not active + var corner = target._findTargetCorner + && (!activeGroup || !activeGroup.contains(target)) + && target._findTargetCorner(e, this._offset); + + if (!corner) { + s.cursor = this.hoverCursor; + } + else { + if (corner in cursorMap) { + s.cursor = cursorMap[corner]; + } + else if (corner === 'mtr' && target.hasRotatingPoint) { + s.cursor = this.rotationCursor; + } + else { + s.cursor = this.defaultCursor; + return false; + } + } + } + return true; + } + }); +})(); + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { /** * Animation duration (in ms) for fx* methods @@ -8667,7 +8948,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { return this; } }); -fabric.util.object.extend(fabric.StaticCanvas.prototype, { +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { /** * Populates canvas with data from the specified dataless JSON @@ -8682,9 +8963,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { */ loadFromDatalessJSON: function (json, callback) { - if (!json) { - return; - } + if (!json) return; // serialize if it wasn't already var serialized = (typeof json === 'string') @@ -8695,9 +8974,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.clear(); - // TODO: test this - this.backgroundColor = serialized.background; - this._enlivenDatalessObjects(serialized.objects, callback); + var _this = this; + this._enlivenDatalessObjects(serialized.objects, function() { + _this._setBgOverlayImages(serialized, callback); + }); }, /** @@ -8706,6 +8986,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @param {Function} callback */ _enlivenDatalessObjects: function (objects, callback) { + var _this = this, + numLoadedObjects = 0, + numTotalObjects = objects.length; /** @ignore */ function onObjectLoaded(object, index) { @@ -8797,10 +9080,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { } } - var _this = this, - numLoadedObjects = 0, - numTotalObjects = objects.length; - if (numTotalObjects === 0 && callback) { callback(); } @@ -8809,7 +9088,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { objects.forEach(loadObject, this); } catch(e) { - fabric.log(e.message); + fabric.log(e); } }, @@ -8832,55 +9111,72 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { ? JSON.parse(json) : json; - if (!serialized || (serialized && !serialized.objects)) return; - - this.clear(); - var _this = this; this._enlivenObjects(serialized.objects, function () { - _this.backgroundColor = serialized.background; - var backgroundImageLoaded, overlayImageLoaded; - - if (serialized.backgroundImage) { - _this.setBackgroundImage(serialized.backgroundImage, function() { - - _this.backgroundImageOpacity = serialized.backgroundImageOpacity; - _this.backgroundImageStretch = serialized.backgroundImageStretch; - - _this.renderAll(); - backgroundImageLoaded = true; - - callback && overlayImageLoaded && callback(); - }); - } - else { - backgroundImageLoaded = true; - } - - if (serialized.overlayImage) { - _this.setOverlayImage(serialized.overlayImage, function() { - - _this.overlayImageLeft = serialized.overlayImageLeft || 0; - _this.overlayImageTop = serialized.overlayImageTop || 0; - - _this.renderAll(); - overlayImageLoaded = true; - - callback && backgroundImageLoaded && callback(); - }); - } - else { - overlayImageLoaded = true; - } - - if (!serialized.backgroundImage && !serialized.overlayImage) { - callback && callback(); - } + _this._setBgOverlayImages(serialized, callback); }); return this; }, + _setBgOverlayImages: function(serialized, callback) { + + var _this = this, + backgroundPatternLoaded, + backgroundImageLoaded, + overlayImageLoaded; + + if (serialized.backgroundImage) { + this.setBackgroundImage(serialized.backgroundImage, function() { + + _this.backgroundImageOpacity = serialized.backgroundImageOpacity; + _this.backgroundImageStretch = serialized.backgroundImageStretch; + + _this.renderAll(); + + backgroundImageLoaded = true; + + callback && overlayImageLoaded && backgroundPatternLoaded && callback(); + }); + } + else { + backgroundImageLoaded = true; + } + + if (serialized.overlayImage) { + this.setOverlayImage(serialized.overlayImage, function() { + + _this.overlayImageLeft = serialized.overlayImageLeft || 0; + _this.overlayImageTop = serialized.overlayImageTop || 0; + + _this.renderAll(); + overlayImageLoaded = true; + + callback && backgroundImageLoaded && backgroundPatternLoaded && callback(); + }); + } + else { + overlayImageLoaded = true; + } + + if (serialized.background) { + this.setBackgroundColor(serialized.background, function() { + + _this.renderAll(); + backgroundPatternLoaded = true; + + callback && overlayImageLoaded && backgroundImageLoaded && callback(); + }); + } + else { + backgroundPatternLoaded = true; + } + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background) { + callback && callback(); + } + }, + /** * @method _enlivenObjects * @param {Array} objects @@ -8971,7 +9267,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { extend = fabric.util.object.extend, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, - getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians; if (fabric.Object) { @@ -8997,11 +9292,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ { /** - * Type of an object (rect, circle, path, etc) + * Type of an object (rect, circle, path, etc.) * @property * @type String */ - type: 'object', + type: 'object', /** * Horizontal origin of transformation of an object (one of "left", "right", "center") @@ -9164,6 +9459,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { */ strokeDashArray: null, + /** + * Shadow object representing shadow of this shape + * @property + * @type fabric.Shadow + */ + shadow: null, + /** * Border opacity when object is active and moving * @property @@ -9251,7 +9553,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { 'top left width height scaleX scaleY flipX flipY ' + 'angle opacity cornerSize fill overlayFill originX originY ' + 'stroke strokeWidth strokeDashArray fillRule ' + - 'borderScaleFactor transformMatrix selectable' + 'borderScaleFactor transformMatrix selectable shadow' ).split(' '), /** @@ -9270,11 +9572,34 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @method _initGradient */ _initGradient: function(options) { - if (options.fill && typeof options.fill === 'object' && !(options.fill instanceof fabric.Gradient)) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { this.set('fill', new fabric.Gradient(options.fill)); } }, + /** + * @private + * @method _initPattern + */ + _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 + * @method _initShadow + */ + _initShadow: function(options) { + if (options.shadow && !(options.shadow instanceof fabric.Shadow)) { + this.setShadow(options.shadow); + } + }, + /** * Sets object's properties from options * @method setOptions @@ -9285,6 +9610,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.set(prop, options[prop]); } this._initGradient(options); + this._initPattern(options); + this._initShadow(options); }, /** @@ -9315,30 +9642,31 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; var 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, - overlayFill: this.overlayFill, - stroke: this.stroke, - strokeWidth: this.strokeWidth, - strokeDashArray: this.strokeDashArray, - 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), - selectable: this.selectable, - hasControls: this.hasControls, - hasBorders: this.hasBorders, - hasRotatingPoint: this.hasRotatingPoint, + 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, + overlayFill: this.overlayFill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: this.strokeWidth, + strokeDashArray: this.strokeDashArray, + 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), + selectable: this.selectable, + hasControls: this.hasControls, + hasBorders: this.hasBorders, + hasRotatingPoint: this.hasRotatingPoint, transparentCorners: this.transparentCorners, - perPixelTargetFind: this.perPixelTargetFind + perPixelTargetFind: this.perPixelTargetFind, + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow }; if (!this.includeDefaultValues) { @@ -9454,21 +9782,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { }, /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @method _constrainScale - * @param {Number} value - * @return {Number} + * Basic getter + * @method get + * @param {String} property + * @return {Any} value of a property */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) - return -this.minScaleLimit; - else - return this.minScaleLimit; - } - - return value; + get: function(property) { + return this[property]; }, /** @@ -9552,16 +9872,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { return this; }, - /** - * Basic getter - * @method get - * @param {String} property - * @return {Any} value of a property - */ - get: function(property) { - return this[property]; - }, - /** * Renders an object on a specified context * @method render @@ -9586,15 +9896,20 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (this.stroke || this.strokeDashArray) { ctx.lineWidth = this.strokeWidth; - ctx.strokeStyle = this.stroke; + if (this.stroke && this.stroke.toLive) { + ctx.strokeStyle = this.stroke.toLive(ctx); + } + else { + ctx.strokeStyle = this.stroke; + } } if (this.overlayFill) { ctx.fillStyle = this.overlayFill; } else if (this.fill) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; } @@ -9603,7 +9918,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } + this._setShadow(ctx); this._render(ctx, noTransform); + this._removeShadow(ctx); if (this.active && !noTransform) { this.drawBorders(ctx); @@ -9613,23 +9930,429 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { }, /** - * Returns width of an object - * @method getWidth - * @return {Number} width value + * @private + * @method _setShadow */ - getWidth: function() { - return this.width * this.scaleX; + _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; }, /** - * Returns height of an object - * @method getHeight - * @return {Number} height value + * @private + * @method _removeShadow */ - getHeight: function() { - return this.height * this.scaleY; + _removeShadow: function(ctx) { + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, + /** + * Clones an instance + * @method clone + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} propertiesToInclude + * @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 + * @method cloneAsImage + * @param callback {Function} callback, invoked with an instance as a first argument + * @return {fabric.Object} thisArg + * @chainable + */ + cloneAsImage: function(callback) { + if (fabric.Image) { + var i = new Image(); + + /** @ignore */ + i.onload = function() { + if (callback) { + callback(new fabric.Image(i), orig); + } + i = i.onload = null; + }; + + var orig = { + angle: this.get('angle'), + flipX: this.get('flipX'), + flipY: this.get('flipY') + }; + + // normalize angle + this.set('angle', 0).set('flipX', false).set('flipY', false); + this.toDataURL(function(dataURL) { + i.src = dataURL; + }); + } + return this; + }, + + /** + * Converts an object into a data-url-like string + * @method toDataURL + * @param callback {Function} callback that recieves resulting data-url string + */ + toDataURL: function(callback) { + var el = fabric.util.createCanvasElement(); + + el.width = this.getBoundingRectWidth(); + el.height = this.getBoundingRectHeight(); + + fabric.util.wrapElement(el, 'div'); + + var canvas = new fabric.Canvas(el); + canvas.backgroundColor = 'transparent'; + canvas.renderAll(); + + if (this.constructor.async) { + this.clone(proceed); + } + else { + proceed(this.clone()); + } + + function proceed(clone) { + clone.left = el.width / 2; + clone.top = el.height / 2; + + clone.setActive(false); + + canvas.add(clone); + var data = canvas.toDataURL('png'); + + canvas.dispose(); + canvas = clone = null; + + callback && callback(data); + } + }, + + /** + * Returns true if object state (one of its state properties) was changed + * @method hasStateChanged + * @return {Boolean} true if instance' state has changed + */ + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this[prop] !== this.originalState[prop]; + }, this); + }, + + /** + * Saves state of an object + * @method saveState + * @return {fabric.Object} thisArg + * @chainable + */ + saveState: function() { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + return this; + }, + + /** + * Setups state of an object + * @method setupState + */ + setupState: function() { + this.originalState = { }; + this.saveState(); + }, + + /** + * Returns true if specified type is identical to the type of an instance + * @method isType + * @param type {String} type to check against + * @return {Boolean} + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Makes object's color grayscale + * @method toGrayscale + * @return {fabric.Object} thisArg + */ + toGrayscale: function() { + var fillValue = this.get('fill'); + if (fillValue) { + this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb()); + } + return this; + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return 0; + }, + + /** + * Returns a JSON representation of an instance + * @method toJSON + * @param {Array} propertiesToInclude + * @return {String} json + */ + toJSON: function(propertiesToInclude) { + // delegate, not alias + return this.toObject(propertiesToInclude); + }, + + /** + * Sets gradient fill of an object + * @method setGradientFill + */ + setGradientFill: function(options) { + this.set('fill', fabric.Gradient.forObject(this, options)); + }, + + /** + * Sets pattern fill of an object + * @method setPatternFill + */ + setPatternFill: function(options) { + this.set('fill', new fabric.Pattern(options)); + }, + + /** + * Sets shadow of an object + * @method setShadow + */ + setShadow: function(options) { + this.set('shadow', new fabric.Shadow(options)); + }, + + /** + * Animates object's properties + * @method animate + * + * 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') { + for (var prop in arguments[0]) { + this._animate(prop, arguments[0][prop], arguments[1]); + } + } + else { + this._animate.apply(this, arguments); + } + return this; + }, + + /** + * @private + * @method _animate + */ + _animate: function(property, to, options) { + var obj = 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, + onChange: function(value) { + if (propPair) { + obj[propPair[0]][propPair[1]] = value; + } + else { + obj.set(property, value); + } + options.onChange && options.onChange(); + }, + onComplete: function() { + obj.setCoords(); + options.onComplete && options.onComplete(); + } + }); + }, + + /** + * Centers object horizontally on canvas to which it was added last + * @method centerH + * @return {fabric.Object} thisArg + */ + centerH: function () { + this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last + * @method centerV + * @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 + * @method center + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + return this.centerH().centerV(); + }, + + /** + * Removes object from canvas to which it was added last + * @method remove + * @return {fabric.Object} thisArg + * @chainable + */ + remove: function() { + return this.canvas.remove(this); + }, + + /** + * Moves an object to the bottom of the stack of drawn objects + * @method sendToBack + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + this.canvas.sendToBack(this); + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @method bringToFront + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + this.canvas.bringToFront(this); + return this; + }, + + /** + * Moves an object one level down in stack of drawn objects + * @method sendBackwards + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function() { + this.canvas.sendBackwards(this); + return this; + }, + + /** + * Moves an object one level up in stack of drawn objects + * @method bringForward + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function() { + this.canvas.bringForward(this); + return this; + } + }); + + var proto = fabric.Object.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); + } + } + + /** + * Alias for {@link fabric.Object.prototype.setAngle} + * @alias rotate -> setAngle + */ + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + + extend(fabric.Object.prototype, fabric.Observable); + + /** + * @static + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; + +})(typeof exports !== 'undefined' ? exports : this); + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { + /** * Translates the coordinates from origin to center coordinates (based on the object's dimensions) * @method translateToCenterPoint @@ -9704,9 +10427,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @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); - }, + // getOriginPoint: function(center) { + // return this.translateToOriginPoint(center, this.originX, this.originY); + // }, /** * Returns the coordinates of the object as if it has a different origin @@ -9715,11 +10438,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @param {string} enum('top', 'center', 'bottom') Vertical origin * @return {fabric.Point} */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); + // getPointByOrigin: function(originX, originY) { + // var center = this.getCenterPoint(); - return this.translateToOriginPoint(center, originX, originY); - }, + // return this.translateToOriginPoint(center, originX, originY); + // }, /** * Returns the point in local coordinates @@ -9766,9 +10489,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @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)); - }, + // 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 @@ -9786,6 +10509,217 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.set('top', position.y); }, + /** + * @method adjustPosition + * @param {String} to One of left, center, right + */ + adjustPosition: function(to) { + + var angle = degreesToRadians(this.angle); + + var hypotHalf = this.getWidth() / 2; + var xHalf = Math.cos(angle) * hypotHalf; + var yHalf = Math.sin(angle) * hypotHalf; + + var hypotFull = this.getWidth(); + var xFull = Math.cos(angle) * hypotFull; + var 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; + } + }); + +})(); +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { + + /** + * Returns true if object intersects with an area formed by 2 points + * @method intersectsWithRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + intersectsWithRect: function(selectionTL, selectionBR) { + 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); + + var intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + selectionTL, + selectionBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Returns true if object intersects with another object + * @method intersectsWithObject + * @param {Object} other Object to test + * @return {Boolean} + */ + 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); + + var intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); + + return intersection.status === 'Intersection'; + }, + + /** + * Returns true if object is fully contained within area of another object + * @method isContainedWithinObject + * @param {Object} other Object to test + * @return {Boolean} + */ + isContainedWithinObject: function(other) { + return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br); + }, + + /** + * Returns true if object is fully contained within area formed by 2 points + * @method isContainedWithinRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + isContainedWithinRect: function(selectionTL, selectionBR) { + 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); + + return tl.x > selectionTL.x + && tr.x < selectionBR.x + && tl.y > selectionTL.y + && bl.y < selectionBR.y; + }, + + /** + * Returns width of an object's bounding rectangle + * @deprecated since 1.0.4 + * @method getBoundingRectWidth + * @return {Number} width value + */ + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + + /** + * Returns height of an object's bounding rectangle + * @deprecated since 1.0.4 + * @method getBoundingRectHeight + * @return {Number} height value + */ + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * @method getBoundingRect + * @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]; + var minX = fabric.util.array.min(xCoords); + var maxX = fabric.util.array.max(xCoords); + var width = Math.abs(minX - maxX); + + var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; + var minY = fabric.util.array.min(yCoords); + var maxY = fabric.util.array.max(yCoords); + var height = Math.abs(minY - maxY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns width of an object + * @method getWidth + * @return {Number} width value + */ + getWidth: function() { + return this.width * this.scaleX; + }, + + /** + * Returns height of an object + * @method getHeight + * @return {Number} height value + */ + getHeight: function() { + return this.height * this.scaleY; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @method _constrainScale + * @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) * @method scale @@ -9849,9 +10783,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; - //If width is negative, make postive. Fixes path selection issue - if(this.currentWidth < 0){ - this.currentWidth = Math.abs(this.currentWidth); + // If width is negative, make postive. Fixes path selection issue + if (this.currentWidth < 0) { + this.currentWidth = Math.abs(this.currentWidth); } var _hypotenuse = Math.sqrt( @@ -9925,477 +10859,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this._setCornerCoords(); return this; - }, + } + }); +})(); +(function(){ - /** - * Returns width of an object's bounding rectangle - * @method getBoundingRectWidth - * @return {Number} width value - */ - getBoundingRectWidth: function() { - this.oCoords || this.setCoords(); - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; - var minX = fabric.util.array.min(xCoords); - var maxX = fabric.util.array.max(xCoords); - return Math.abs(minX - maxX); - }, + var getPointer = fabric.util.getPointer, + degreesToRadians = fabric.util.degreesToRadians; - /** - * Returns height of an object's bounding rectangle - * @method getBoundingRectHeight - * @return {Number} height value - */ - getBoundingRectHeight: function() { - this.oCoords || this.setCoords(); - var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; - var minY = fabric.util.array.min(yCoords); - var maxY = fabric.util.array.max(yCoords); - return Math.abs(minY - maxY); - }, - - /** - * Draws borders of an object's bounding box. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @method drawBorders - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawBorders: function(ctx) { - if (!this.hasBorders) return; - - var padding = this.padding, - padding2 = padding * 2, - strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0; - - 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; - - ctx.scale(scaleX, scaleY); - - var w = this.getWidth(), - h = this.getHeight(); - - 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), - ~~(h + padding2 + strokeWidth * this.scaleY) - ); - - if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { - - var rotateHeight = ( - this.flipY - ? h + (strokeWidth * this.scaleY) + (padding * 2) - : -h - (strokeWidth * this.scaleY) - (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; - }, - - /** - * @private - * @method _renderDashedStroke - */ - _renderDashedStroke: function(ctx) { - - if (1 & this.strokeDashArray.length /* if odd number of items */) { - /* duplicate items */ - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - - var i = 0, - x = -this.width/2, y = -this.height/2, - _this = this, - padding = this.padding, - dashedArrayLength = this.strokeDashArray.length; - - ctx.save(); - ctx.beginPath(); - - /** @ignore */ - function renderSide(xMultiplier, yMultiplier) { - - var lineLength = 0, - lengthDiff = 0, - sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2; - - while (lineLength < sideLength) { - - var lengthOfSubPath = _this.strokeDashArray[i++]; - lineLength += lengthOfSubPath; - - if (lineLength > sideLength) { - lengthDiff = lineLength - sideLength; - } - - // track coords - if (xMultiplier) { - x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0); - } - else { - y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0); - } - - ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y); - if (i >= dashedArrayLength) { - i = 0; - } - } - } - - renderSide(1, 0); - renderSide(0, 1); - renderSide(-1, 0); - renderSide(0, -1); - - ctx.stroke(); - ctx.closePath(); - ctx.restore(); - }, - - /** - * Draws corners of an object's bounding box. - * Requires public properties: width, height, scaleX, scaleY - * Requires public options: cornerSize, padding - * @method drawCorners - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawCorners: function(ctx) { - if (!this.hasControls) return; - - var size = this.cornerSize, - size2 = size / 2, - strokeWidth2 = this.strokeWidth / 2, - left = -(this.width / 2), - top = -(this.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, - methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', - isVML = typeof G_vmlCanvasManager !== 'undefined'; - - ctx.save(); - - ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); - - 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; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // top-right - _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // bottom-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // bottom-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - if (!this.get('lockUniScaling')) { - // middle-top - _left = left + width/2 - scaleOffsetX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-bottom - _left = left + width/2 - scaleOffsetX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height/2 - scaleOffsetY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height/2 - scaleOffsetY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - } - - // middle-top-rotate - if (this.hasRotatingPoint) { - - _left = left + width/2 - scaleOffsetX; - _top = this.flipY ? - (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - } - - ctx.restore(); - - return this; - }, - - /** - * Clones an instance - * @method clone - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} propertiesToInclude - * @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 - * @method cloneAsImage - * @param callback {Function} callback, invoked with an instance as a first argument - * @return {fabric.Object} thisArg - * @chainable - */ - cloneAsImage: function(callback) { - if (fabric.Image) { - var i = new Image(); - - /** @ignore */ - i.onload = function() { - if (callback) { - callback(new fabric.Image(i), orig); - } - i = i.onload = null; - }; - - var orig = { - angle: this.get('angle'), - flipX: this.get('flipX'), - flipY: this.get('flipY') - }; - - // normalize angle - this.set('angle', 0).set('flipX', false).set('flipY', false); - this.toDataURL(function(dataURL) { - i.src = dataURL; - }); - } - return this; - }, - - /** - * Converts an object into a data-url-like string - * @method toDataURL - * @param callback {Function} callback that recieves resulting data-url string - */ - toDataURL: function(callback) { - var el = fabric.document.createElement('canvas'); - if (!el.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(el); - } - - el.width = this.getBoundingRectWidth(); - el.height = this.getBoundingRectHeight(); - - fabric.util.wrapElement(el, 'div'); - - var canvas = new fabric.Canvas(el); - canvas.backgroundColor = 'transparent'; - canvas.renderAll(); - - if (this.constructor.async) { - this.clone(proceed); - } - else { - proceed(this.clone()); - } - - function proceed(clone) { - clone.left = el.width / 2; - clone.top = el.height / 2; - - clone.setActive(false); - - canvas.add(clone); - var data = canvas.toDataURL('png'); - - canvas.dispose(); - canvas = clone = null; - - callback && callback(data); - } - }, - - /** - * Returns true if object state (one of its state properties) was changed - * @method hasStateChanged - * @return {Boolean} true if instance' state has changed - */ - hasStateChanged: function() { - return this.stateProperties.some(function(prop) { - return this[prop] !== this.originalState[prop]; - }, this); - }, - - /** - * Saves state of an object - * @method saveState - * @return {fabric.Object} thisArg - * @chainable - */ - saveState: function() { - this.stateProperties.forEach(function(prop) { - this.originalState[prop] = this.get(prop); - }, this); - return this; - }, - - /** - * Setups state of an object - * @method setupState - */ - setupState: function() { - this.originalState = { }; - this.saveState(); - }, - - /** - * Returns true if object intersects with an area formed by 2 points - * @method intersectsWithRect - * @param {Object} selectionTL - * @param {Object} selectionBR - * @return {Boolean} - */ - intersectsWithRect: function(selectionTL, selectionBR) { - 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); - - var intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - selectionTL, - selectionBR - ); - return (intersection.status === 'Intersection'); - }, - - /** - * Returns true if object intersects with another object - * @method intersectsWithObject - * @param {Object} other Object to test - * @return {Boolean} - */ - 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); - - var intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); - - return (intersection.status === 'Intersection'); - }, - - /** - * Returns true if object is fully contained within area of another object - * @method isContainedWithinObject - * @param {Object} other Object to test - * @return {Boolean} - */ - isContainedWithinObject: function(other) { - return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br); - }, - - /** - * Returns true if object is fully contained within area formed by 2 points - * @method isContainedWithinRect - * @param {Object} selectionTL - * @param {Object} selectionBR - * @return {Boolean} - */ - isContainedWithinRect: function(selectionTL, selectionBR) { - 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); - - return tl.x > selectionTL.x - && tr.x < selectionBR.x - && tl.y > selectionTL.y - && bl.y < selectionBR.y; - }, - - /** - * Returns true if specified type is identical to the type of an instance - * @method isType - * @param type {String} type to check against - * @return {Boolean} - */ - isType: function(type) { - return this.type === type; - }, + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { /** * Determines which one of the four corners has been clicked @@ -10408,7 +10880,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { _findTargetCorner: function(e, offset) { if (!this.hasControls || !this.active) return false; - var pointer = getPointer(e), + var pointer = getPointer(e, this.canvas.upperCanvasEl), ex = pointer.x - offset.left, ey = pointer.y - offset.top, xpoints, @@ -10714,246 +11186,178 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { } }; }, - /** - * Makes object's color grayscale - * @method toGrayscale + * Draws borders of an object's bounding box. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @method drawBorders + * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg + * @chainable */ - toGrayscale: function() { - var fillValue = this.get('fill'); - if (fillValue) { - this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb()); + drawBorders: function(ctx) { + if (!this.hasBorders) return; + + var padding = this.padding, + padding2 = padding * 2, + strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0; + + 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; + + ctx.scale(scaleX, scaleY); + + var w = this.getWidth(), + h = this.getHeight(); + + 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), + ~~(h + padding2 + strokeWidth * this.scaleY) + ); + + if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { + + var rotateHeight = ( + this.flipY + ? h + (strokeWidth * this.scaleY) + (padding * 2) + : -h - (strokeWidth * this.scaleY) - (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; }, /** - * Returns complexity of an instance - * @method complexity - * @return {Number} complexity - */ - complexity: function() { - return 0; - }, - - /** - * Returns a JSON representation of an instance - * @method toJSON - * @param {Array} propertiesToInclude - * @return {String} json - */ - toJSON: function(propertiesToInclude) { - // delegate, not alias - return this.toObject(propertiesToInclude); - }, - - /** - * Sets gradient fill of an object - * @method setGradientFill - */ - setGradientFill: function(options) { - this.set('fill', fabric.Gradient.forObject(this, options)); - }, - - /** - * Animates object's properties - * @method animate - * - * 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') { - for (var prop in arguments[0]) { - this._animate(prop, arguments[0][prop], arguments[1]); - } - } - else { - this._animate.apply(this, arguments); - } - return this; - }, - - /** - * @private - * @method _animate - */ - _animate: function(property, to, options) { - var obj = this; - - to = to.toString(); - - if (!options) { - options = { }; - } - else { - options = fabric.util.object.clone(options); - } - - if (!('from' in options)) { - options.from = this.get(property); - } - - if (~to.indexOf('=')) { - to = this.get(property) + parseFloat(to.replace('=', '')); - } - else { - to = parseFloat(to); - } - - fabric.util.animate({ - startValue: options.from, - endValue: to, - byValue: options.by, - easing: options.easing, - duration: options.duration, - onChange: function(value) { - obj.set(property, value); - options.onChange && options.onChange(); - }, - onComplete: function() { - obj.setCoords(); - options.onComplete && options.onComplete(); - } - }); - }, - - /** - * Centers object horizontally on canvas to which it was added last - * @method centerH - * @return {fabric.Object} thisArg - */ - centerH: function () { - this.canvas.centerObjectH(this); - return this; - }, - - /** - * Centers object vertically on canvas to which it was added last - * @method centerV + * Draws corners of an object's bounding box. + * Requires public properties: width, height, scaleX, scaleY + * Requires public options: cornerSize, padding + * @method drawCorners + * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg * @chainable */ - centerV: function () { - this.canvas.centerObjectV(this); - return this; - }, + drawCorners: function(ctx) { + if (!this.hasControls) return; - /** - * Centers object vertically and horizontally on canvas to which is was added last - * @method center - * @return {fabric.Object} thisArg - * @chainable - */ - center: function () { - return this.centerH().centerV(); - }, + var size = this.cornerSize, + size2 = size / 2, + strokeWidth2 = this.strokeWidth / 2, + left = -(this.width / 2), + top = -(this.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, + methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', + isVML = typeof G_vmlCanvasManager !== 'undefined'; - /** - * Removes object from canvas to which it was added last - * @method remove - * @return {fabric.Object} thisArg - * @chainable - */ - remove: function() { - return this.canvas.remove(this); - }, + ctx.save(); - /** - * Moves an object to the bottom of the stack of drawn objects - * @method sendToBack - * @return {fabric.Object} thisArg - * @chainable - */ - sendToBack: function() { - this.canvas.sendToBack(this); - return this; - }, + ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); - /** - * Moves an object to the top of the stack of drawn objects - * @method bringToFront - * @return {fabric.Object} thisArg - * @chainable - */ - bringToFront: function() { - this.canvas.bringToFront(this); - return this; - }, + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = ctx.fillStyle = this.cornerColor; - /** - * Moves an object one level down in stack of drawn objects - * @method sendBackwards - * @return {fabric.Object} thisArg - * @chainable - */ - sendBackwards: function() { - this.canvas.sendBackwards(this); - return this; - }, + // top-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // top-right + _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + if (!this.get('lockUniScaling')) { + // middle-top + _left = left + width/2 - scaleOffsetX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-bottom + _left = left + width/2 - scaleOffsetX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height/2 - scaleOffsetY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height/2 - scaleOffsetY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + // middle-top-rotate + if (this.hasRotatingPoint) { + + _left = left + width/2 - scaleOffsetX; + _top = this.flipY ? + (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) + : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + ctx.restore(); - /** - * Moves an object one level up in stack of drawn objects - * @method bringForward - * @return {fabric.Object} thisArg - * @chainable - */ - bringForward: function() { - this.canvas.bringForward(this); return this; } }); - - var proto = fabric.Object.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); - } - } - - /** - * Alias for {@link fabric.Object.prototype.setAngle} - * @alias rotate -> setAngle - */ - fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; - - extend(fabric.Object.prototype, fabric.Observable); - - extend(fabric.Object, { - - /** - * @static - * @constant - * @type Number - */ - NUM_FRACTION_DIGITS: 2 - }); - -})(typeof exports !== 'undefined' ? exports : this); - +})(); (function(global) { "use strict"; @@ -11225,6 +11629,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.stroke(); } @@ -11534,6 +11939,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (this.stroke) { ctx.stroke(); } + this._removeShadow(ctx); if (this.fill) { ctx.fill(); } @@ -11722,6 +12128,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { ctx.fill(); } + this._removeShadow(ctx); + if (this.strokeDashArray) { this._renderDashedStroke(ctx); } @@ -11730,6 +12138,67 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { } }, + /** + * @private + * @method _renderDashedStroke + */ + _renderDashedStroke: function(ctx) { + + if (1 & this.strokeDashArray.length /* if odd number of items */) { + /* duplicate items */ + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + + var i = 0, + x = -this.width/2, y = -this.height/2, + _this = this, + padding = this.padding, + dashedArrayLength = this.strokeDashArray.length; + + ctx.save(); + ctx.beginPath(); + + /** @ignore */ + function renderSide(xMultiplier, yMultiplier) { + + var lineLength = 0, + lengthDiff = 0, + sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2; + + while (lineLength < sideLength) { + + var lengthOfSubPath = _this.strokeDashArray[i++]; + lineLength += lengthOfSubPath; + + if (lineLength > sideLength) { + lengthDiff = lineLength - sideLength; + } + + // track coords + if (xMultiplier) { + x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0); + } + else { + y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0); + } + + ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y); + if (i >= dashedArrayLength) { + i = 0; + } + } + } + + renderSide(1, 0); + renderSide(0, 1); + renderSide(-1, 0); + renderSide(0, -1); + + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + }, + /** * @method _normalizeLeftTopProperties * @private @@ -11928,6 +12397,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.stroke(); } @@ -12047,6 +12517,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.width = (maxX - minX) || 1; this.height = (maxY - minY) || 1; + // var halfWidth = this.width / 2, + // halfHeight = this.height / 2; + + // change points to offset polygon into a bounding box + // this.points.forEach(function(p) { + // p.x -= halfWidth; + // p.y -= halfHeight; + // }, this); + this.minX = minX; this.minY = minY; }, @@ -12099,6 +12578,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.closePath(); ctx.stroke(); @@ -12705,21 +13185,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { ctx.fillStyle = this.overlayFill; } else if (this.fill) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; } if (this.stroke) { - ctx.strokeStyle = this.stroke; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; } ctx.beginPath(); + this._setShadow(ctx); this._render(ctx); if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); + if (this.stroke) { ctx.strokeStyle = this.stroke; ctx.lineWidth = this.strokeWidth; @@ -13026,9 +13511,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { } this.transform(ctx); + + this._setShadow(ctx); for (var i = 0, l = this.paths.length; i < l; ++i) { this.paths[i].render(ctx, true); } + this._removeShadow(ctx); + if (this.active) { this.drawBorders(ctx); this.hideCorners || this.drawCorners(ctx); @@ -13849,7 +14338,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { if (!noTransform) { this.transform(ctx); } + + this._setShadow(ctx); this._render(ctx); + this._removeShadow(ctx); + if (this.active && !noTransform) { this.drawBorders(ctx); this.hideCorners || this.drawCorners(ctx); @@ -13933,14 +14426,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { var isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined', imgEl = this._originalImage, - canvasEl = fabric.document.createElement('canvas'), + canvasEl = fabric.util.createCanvasElement(), replacement = isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'), _this = this; - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - canvasEl.width = imgEl.width; canvasEl.height = imgEl.height; @@ -14166,7 +14655,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { })(typeof exports !== 'undefined' ? exports : this); -fabric.util.object.extend(fabric.Object.prototype, { +fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { /** * @private @@ -14882,7 +15371,8 @@ fabric.Image.filters.Convolute = fabric.util.createClass(/** @scope fabric.Image 0, 1, 0, 0, 0, 0 ]; - this.tmpCtx = fabric.document.createElement('canvas').getContext('2d'); + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext('2d'); }, /** @@ -15217,11 +15707,7 @@ fabric.Image.filters.Pixelate.fromObject = function(object) { * @method _initDimensions */ _initDimensions: function() { - var canvasEl = fabric.document.createElement('canvas'); - - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } + var canvasEl = fabric.util.createCanvasElement(); this._render(canvasEl.getContext('2d')); }, @@ -15392,8 +15878,8 @@ fabric.Image.filters.Pixelate.fromObject = function(object) { * @method _setTextStyles */ _setTextStyles: function(ctx) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; ctx.strokeStyle = this.strokeStyle; ctx.lineWidth = this.strokeWidth; @@ -16140,6 +16626,10 @@ fabric.Image.filters.Pixelate.fromObject = function(object) { 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); @@ -16161,3 +16651,4 @@ fabric.Image.filters.Pixelate.fromObject = function(object) { } })(); + diff --git a/dist/all.min.js b/dist/all.min.js index d504c4ac..367decf6 100644 --- a/dist/all.min.js +++ b/dist/all.min.js @@ -1,5 +1,13 @@ +<<<<<<< HEAD /* build: `node build.js modules=ALL exclude=gestures` *//*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.0.0"};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";var Cufon=function(){function r(e){var t=this.face=e.face;this.glyphs=e.glyphs,this.w=e.w,this.baseSize=parseInt(t["units-per-em"],10),this.family=t["font-family"].toLowerCase(),this.weight=t["font-weight"],this.style=t["font-style"]||"normal",this.viewBox=function(){var e=t.bbox.split(/\s+/),n={minX:parseInt(e[0],10),minY:parseInt(e[1],10),maxX:parseInt(e[2],10),maxY:parseInt(e[3],10)};return n.width=n.maxX-n.minX,n.height=n.maxY-n.minY,n.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},n}(),this.ascent=-parseInt(t.ascent,10),this.descent=-parseInt(t.descent,10),this.height=-this.ascent+this.descent}function i(){var e={},t={oblique:"italic",italic:"oblique"};this.add=function(t){(e[t.style]||(e[t.style]={}))[t.weight]=t},this.get=function(n,r){var i=e[n]||e[t[n]]||e.normal||e.italic||e.oblique;if(!i)return null;r={normal:400,bold:700}[r]||parseInt(r,10);if(i[r])return i[r];var s={1:1,99:0}[r%100],o=[],u,a;s===undefined&&(s=r>400),r==500&&(r=400);for(var f in i){f=parseInt(f,10);if(!u||fa)a=f;o.push(f)}return ra&&(r=a),o.sort(function(e,t){return(s?e>r&&t>r?et:et:e=i.length+e?r():setTimeout(arguments.callee,10)}),function(t){e?t():n.push(t)}}(),supports:function(e,t){var n=fabric.document.createElement("span").style;return n[e]===undefined?!1:(n[e]=t,n[e]===t)},textAlign:function(e,t,n,r){return t.get("textAlign")=="right"?n>0&&(e=" "+e):nk&&(k=N),A.push(N),N=0;continue}var O=t.glyphs[T[b]]||t.missingGlyph;if(!O)continue;N+=C=Number(O.w||t.w)+h}A.push(N),N=Math.max(k,N);var M=[];for(var b=A.length;b--;)M[b]=N-A[b];if(C===null)return null;d+=l.width-C,m+=l.minX;var _,D;if(f)_=u,D=u.firstChild;else{_=fabric.document.createElement("span"),_.className="cufon cufon-canvas",_.alt=n,D=fabric.document.createElement("canvas"),_.appendChild(D);if(i.printable){var P=fabric.document.createElement("span");P.className="cufon-alt",P.appendChild(fabric.document.createTextNode(n)),_.appendChild(P)}}var H=_.style,B=D.style||{},j=c.convert(l.height-p+v),F=Math.ceil(j),I=F/j;D.width=Math.ceil(c.convert(N+d-m)*I),D.height=F,p+=l.minY,B.top=Math.round(c.convert(p-t.ascent))+"px",B.left=Math.round(c.convert(m))+"px";var q=Math.ceil(c.convert(N*I)),R=q+"px",U=c.convert(t.height),z=(i.lineHeight-1)*c.convert(-t.ascent/5)*(L-1);Cufon.textOptions.width=q,Cufon.textOptions.height=U*L+z,Cufon.textOptions.lines=L,Cufon.textOptions.totalLineHeight=z,e?(H.width=R,H.height=U+"px"):(H.paddingLeft=R,H.paddingBottom=U-1+"px");var W=Cufon.textOptions.context||D.getContext("2d"),X=F/l.height;Cufon.textOptions.fontAscent=t.ascent*X,Cufon.textOptions.boundaries=null;for(var V=Cufon.textOptions.shadowOffsets,b=y.length;b--;)V[b]=[y[b][0]*X,y[b][1]*X];W.save(),W.scale(X,X),W.translate(-m-1/X*D.width/2+(Cufon.fonts[t.family].offsetLeft||0),-p-Cufon.textOptions.height/X/2+(Cufon.fonts[t.family].offsetTop||0)),W.lineWidth=t.face["underline-thickness"],W.save();var J=Cufon.getTextDecoration(i),K=i.fontStyle==="italic";W.save(),Q();if(g)for(var b=0,w=g.length;b.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(e,t,i,s,o,u,a){var f=t===null;f&&(t=o.alt);var l=e.viewBox,c=i.computedFontSize||(i.computedFontSize=new Cufon.CSS.Size(n(u,i.get("fontSize"))+"px",e.baseSize)),h=i.computedLSpacing;h==undefined&&(h=i.get("letterSpacing"),i.computedLSpacing=h=h=="normal"?0:~~c.convertFrom(r(u,h)));var p,d;if(f)p=o,d=o.firstChild;else{p=fabric.document.createElement("span"),p.className="cufon cufon-vml",p.alt=t,d=fabric.document.createElement("span"),d.className="cufon-vml-canvas",p.appendChild(d);if(s.printable){var v=fabric.document.createElement("span");v.className="cufon-alt",v.appendChild(fabric.document.createTextNode(t)),p.appendChild(v)}a||p.appendChild(fabric.document.createElement("cvml:shape"))}var m=p.style,g=d.style,y=c.convert(l.height),b=Math.ceil(y),w=b/y,E=l.minX,S=l.minY;g.height=b,g.top=Math.round(c.convert(S-e.ascent)),g.left=Math.round(c.convert(E)),m.height=c.convert(e.height)+"px";var x=Cufon.getTextDecoration(s),T=i.get("color"),N=Cufon.CSS.textTransform(t,i).split(""),C=0,k=0,L=null,A,O,M=s.textShadow;for(var _=0,D=0,P=N.length;_r?n:i-t;s(u(f,a,c,n));if(i>r||o()){e.onComplete&&e.onComplete();return}l(h)}()}function c(e,t,n){if(e){var r=new Image;r.onload=function(){t&&t.call(n,r),r=r.onload=null},r.src=e}else t&&t.call(n,e)}function h(e,t){function n(e){return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(e))]}function r(){++s===o&&t&&t(i)}var i=[],s=0,o=e.length;e.forEach(function(e,t){if(!e.type)return;var s=n(e.type);s.async?s.fromObject(e,function(e,n){n||(i[t]=e),r()}):(i[t]=s.fromObject(e),r())})}function p(e,t,n){var r;if(e.length>1){var i=e.some(function(e){return e.type==="text"});i?(r=new fabric.Group([],t),e.reverse().forEach(function(e){e.cx&&(e.left=e.cx),e.cy&&(e.top=e.cy),r.addWithUpdate(e)})):r=new fabric.PathGroup(e,t)}else r=e[0];return typeof n!="undefined"&&r.setSourcePath(n),r}function d(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;r=r&&(r=e[n][t]);else while(n--)e[n]>=r&&(r=e[n]);return r}function r(e,t){if(!e||e.length===0)return undefined;var n=e.length-1,r=t?e[n][t]:e[n];if(t)while(n--)e[n][t]>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==1/0&&r!==-1/0&&(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 e(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.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){var t=0,n=0;do t+=e.offsetTop||0,n+=e.offsetLeft||0,e=e.offsetParent;while(e);return{left:n,top:t}}var e=Array.prototype.slice,n=function(t){return e.call(t,0)},r;try{r=n(fabric.document.childNodes)instanceof Array}catch(i){}r||(n=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var f;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?f=function(e){return fabric.document.defaultView.getComputedStyle(e,null).position}:f=function(e){var t=e.style.position;return!t&&e.currentStyle&&(t=e.currentStyle.position),t},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.type="text/javascript",r.setAttribute("runat","server"),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=n,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getElementOffset=a,fabric.util.getElementPosition=f}(),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}(),function(){function e(e,t,n,r){return n*(e/=r)*e+t}function t(e,t,n,r){return-n*(e/=r)*(e-2)+t}function n(e,t,n,r){return e/=r/2,e<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}function r(e,t,n,r){return n*(e/=r)*e*e+t}function i(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function s(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e+t:n/2*((e-=2)*e*e+2)+t}function o(e,t,n,r){return n*(e/=r)*e*e*e+t}function u(e,t,n,r){return-n*((e=e/r-1)*e*e*e-1)+t}function a(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e*e+t:-n/2*((e-=2)*e*e*e-2)+t}function f(e,t,n,r){return n*(e/=r)*e*e*e*e+t}function l(e,t,n,r){return n*((e=e/r-1)*e*e*e*e+1)+t}function c(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e*e*e+t:n/2*((e-=2)*e*e*e*e+2)+t}function h(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t}function p(e,t,n,r){return n*Math.sin(e/r*(Math.PI/2))+t}function d(e,t,n,r){return-n/2*(Math.cos(Math.PI*e/r)-1)+t}function v(e,t,n,r){return e===0?t:n*Math.pow(2,10*(e/r-1))+t}function m(e,t,n,r){return e===r?t+n:n*(-Math.pow(2,-10*e/r)+1)+t}function g(e,t,n,r){return e===0?t:e===r?t+n:(e/=r/2,e<1?n/2*Math.pow(2,10*(e-1))+t:n/2*(-Math.pow(2,-10*--e)+2)+t)}function y(e,t,n,r){return-n*(Math.sqrt(1-(e/=r)*e)-1)+t}function b(e,t,n,r){return n*Math.sqrt(1-(e=e/r-1)*e)+t}function w(e,t,n,r){return e/=r/2,e<1?-n/2*(Math.sqrt(1-e*e)-1)+t:n/2*(Math.sqrt(1-(e-=2)*e)+1)+t}function E(e,t,n,r){var i=1.70158,s=0,o=n;return e===0?t:(e/=r,e===1?t+n:(s||(s=r*.3),o-1;e=e.split(/\s+/);var n=[],r,i;if(t){r=0,i=e.length;for(;r/i,"")));if(!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){d.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),d.has(e,function(r){r?d.get(e,function(e){var t=m(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})}function m(e){var n=e.objects,i=e.options;return n=n.map(function(e){return t[r(e.type)].fromObject(e)}),{objects:n,options:i}}function g(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)}function y(e){var t="";for(var n=0,r=e.length;n",'",""].join("")),t}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s={cx:"left",x:"left",cy:"top",y:"top",r:"radius","fill-opacity":"opacity","fill-rule":"fillRule","stroke-width":"strokeWidth",transform:"transformMatrix","text-decoration":"textDecoration","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle","font-family":"fontFamily"};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 t(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function n(e,t){e[2]=t[0]}function r(e,t){e[1]=t[0]}function i(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var s=[1,0,0,1,0,0],o="(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)",u="(?:\\s+,?\\s*|,\\s*)",a="(?:(skewX)\\s*\\(\\s*("+o+")\\s*\\))",f="(?:(skewY)\\s*\\(\\s*("+o+")\\s*\\))",l="(?:(rotate)\\s*\\(\\s*("+o+")(?:"+u+"("+o+")"+u+"("+o+"))?\\s*\\))",c="(?:(scale)\\s*\\(\\s*("+o+")(?:"+u+"("+o+"))?\\s*\\))",h="(?:(translate)\\s*\\(\\s*("+o+")(?:"+u+"("+o+"))?\\s*\\))",p="(?:(matrix)\\s*\\(\\s*("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+"\\s*\\))",d="(?:"+p+"|"+h+"|"+c+"|"+l+"|"+a+"|"+f+")",v="(?:"+d+"(?:"+u+d+")*"+")",m="^\\s*(?:"+v+"?)\\s*$",g=new RegExp(m),y=new RegExp(d);return function(o){var u=s.concat();return!o||o&&!g.test(o)?u:(o.replace(y,function(s){var o=(new RegExp(d)).exec(s).filter(function(e){return e!==""&&e!=null}),a=o[1],f=o.slice(2).map(parseFloat);switch(a){case"translate":i(u,f);break;case"rotate":e(u,f);break;case"scale":t(u,f);break;case"skewX":n(u,f);break;case"skewY":r(u,f);break;case"matrix":u=f}}),u)}}(),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+)?(?: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){f=n.selectNodes("//*[name(.)!='svg']");var l=[];for(var c=0,p=f.length;c0&&this.init(e,t)}var t=e.fabric||(e.fabric={});if(t.Point){t.warn("fabric.Point is already defined");return}t.Point=n,n.prototype={constructor:n,init:function(e,t){this.x=e,this.y=t},add:function(e){return new n(this.x+e.x,this.y+e.y)},addEquals:function(e){return this.x+=e.x,this.y+=e.y,this},scalarAdd:function(e){return new n(this.x+e,this.y+e)},scalarAddEquals:function(e){return this.x+=e,this.y+=e,this},subtract:function(e){return new n(this.x-e.x,this.y-e.y)},subtractEquals:function(e){return this.x-=e.x,this.y-=e.y,this},scalarSubtract:function(e){return new n(this.x-e,this.y-e)},scalarSubtractEquals:function(e){return this.x-=e,this.y-=e,this},multiply:function(e){return new n(this.x*e,this.y*e)},multiplyEquals:function(e){return this.x*=e,this.y*=e,this},divide:function(e){return new n(this.x/e,this.y/e)},divideEquals:function(e){return this.x/=e,this.y/=e,this},eq:function(e){return this.x===e.x&&this.y===e.y},lt:function(e){return this.xe.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){arguments.length>0&&this.init(e)}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={init:function(e){this.status=e,this.points=[]},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("No Intersection")}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("No Intersection"),s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n("No Intersection"),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("No Intersection");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])}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=n.sourceFromHex(e);t||(t=n.sourceFromRgb(e)),t&&this.setSource(t)},getSource:function(){return this._source},setSource:function(e){this._source=e},toRgb:function(){var e=this.getSource();return"rgb("+e[0]+","+e[1]+","+e[2]+")"},toRgba:function(){var e=this.getSource();return"rgba("+e[0]+","+e[1]+","+e[2]+","+e[3]+")"},toHex:function(){var e=this.getSource(),t=e[0].toString(16);t=t.length===1?"0"+t:t;var n=e[1].toString(16);n=n.length===1?"0"+n:n;var r=e[2].toString(16);return r=r.length===1?"0"+r:r,t.toUpperCase()+n.toUpperCase()+r.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(e){var t=this.getSource();return t[3]=e,this.setSource(t),this},toGrayscale:function(){var e=this.getSource(),t=parseInt((e[0]*.3+e[1]*.59+e[2]*.11).toFixed(0),10),n=e[3];return this.setSource([t,t,t,n]),this},toBlackWhite:function(e){var t=this.getSource(),n=(t[0]*.3+t[1]*.59+t[2]*.11).toFixed(0),r=t[3];return e=e||127,n=Number(n)','',"',"Created with Fabric.js ",fabric.version,"",fabric.createSVGFontFacesMarkup(this.getObjects())];this.backgroundImage&&e.push(''),this.overlayImage&&e.push('');for(var t=0,n=this.getObjects(),r=n.length;t"),e.join("")},isEmpty:function(){return this._objects.length===0},remove:function(e){return n(this._objects,e),this.getActiveObject()===e&&(this.fire("before:selection:cleared",{target:e}),this.discardActiveObject(),this.fire("selection:cleared")),this.renderAll(),e},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll()},sendBackwards:function(e){var t=this._objects.indexOf(e),r=t;if(t!==0){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}}n(this._objects,e),this._objects.splice(r,0,e)}return this.renderAll()},bringForward:function(e){var t=this.getObjects(),r=t.indexOf(e),i=r;if(r!==t.length-1){for(var s=r+1,o=this._objects.length;s"},e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',toGrayscale:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=n.width,s=n.height,o,u,a,f;for(a=0;a0&&(t>this.targetFindTolerance?t-=this.targetFindTolerance:t=0,n>this.targetFindTolerance?n-=this.targetFindTolerance:n=0);var o=!0,u=r.getImageData(t,n,this.targetFindTolerance*2||1,this.targetFindTolerance*2||1);for(var a=3;a0?0:-n),t.ey-(r>0?0:-r),i,s),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var o=t.ex+h-(n>0?0:i),u=t.ey+h-(r>0?0:s);e.beginPath(),this.drawDashedLine(e,o,u,o+i,u,this.selectionDashArray),this.drawDashedLine(e,o,u+s-1,o+i,u+s-1,this.selectionDashArray),this.drawDashedLine(e,o,u,o,u+s,this.selectionDashArray),this.drawDashedLine(e,o+i-1,u,o+i-1,u+s,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+h-(n>0?0:i),t.ey+h-(r>0?0:s),i,s)},drawDashedLine:function(e,t,n,r,i,s){var o=r-t,f=i-n,l=u(o*o+f*f),c=a(f,o),h=s.length,p=0,d=!0;e.save(),e.translate(t,n),e.moveTo(0,0),e.rotate(c),t=0;while(l>t)t+=s[p++%h],t>l&&(t=l),e[d?"lineTo":"moveTo"](t,0),d=!d;e.restore()},_findSelectedObjects:function(e){var t=[],n=this._groupSelector.ex,r=this._groupSelector.ey,i=n+this._groupSelector.left,s=r+this._groupSelector.top,o,u=new fabric.Point(l(n,i),l(r,s)),a=new fabric.Point(c(n,i),c(r,s));for(var f=0,h=this._objects.length;f1&&(t=new fabric.Group(t),this.setActiveGroup(t),t.saveCoords(),this.fire("selection:created",{target:t})),this.renderAll()},findTarget:function(e,t){var n,r=this.getPointer(e);if(this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay))return n=this.lastRenderedObjectWithControlsAboveOverlay,n;var i=this.getActiveGroup();if(i&&!t&&this.containsPoint(e,i))return n=i,n;var s=[];for(var o=this._objects.length;o--;)if(this._objects[o]&&this.containsPoint(e,this._objects[o])){if(!this.perPixelTargetFind&&!this._objects[o].perPixelTargetFind){n=this._objects[o],this.relatedTarget=n;break}s[s.length]=this._objects[o]}for(var u=0,a=s.length;u"},_constrainScale:function(e){return Math.abs(e)1?this.strokeWidth:0,t=this.padding,n=o(this.angle);this.currentWidth=(this.width+e)*this.scaleX+t*2,this.currentHeight=(this.height+e)*this.scaleY+t*2,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var r=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),i=Math.atan(this.currentHeight/this.currentWidth),s=Math.cos(i+n)*r,u=Math.sin(i+n)*r,a=Math.sin(n),f=Math.cos(n),l=this.getCenterPoint(),c={x:l.x-s,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:c.x+this.currentWidth/2*f,y:c.y+this.currentWidth/2*a};return this.oCoords={tl:c,tr:h,br:p,bl:d,ml:v,mt:m,mr:g,mb:y,mtr:b},this._setCornerCoords(),this},getBoundingRectWidth:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],n=t.util.array.min(e),r=t.util.array.max(e);return Math.abs(n-r)},getBoundingRectHeight:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],n=t.util.array.min(e),r=t.util.array.max(e);return Math.abs(n-r)},drawBorders:function(e){if(!this.hasBorders)return;var t=this.padding,n=t*2,r=this.strokeWidth>1?this.strokeWidth:0;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),~~(u+n+r*this.scaleY));if(this.hasRotatingPoint&&!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},_renderDashedStroke:function(e){function u(u,a){var f=0,l=0,c=(a?i.height:i.width)+s*2;while(fc&&(l=f-c),u?n+=h*u-(l*u||0):r+=h*a-(l*a||0),e[1&t?"moveTo":"lineTo"](n,r),t>=o&&(t=0)}}1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray);var t=0,n=-this.width/2,r=-this.height/2,i=this,s=this.padding,o=this.strokeDashArray.length;e.save(),e.beginPath(),u(1,0),u(0,1),u(-1,0),u(0,-1),e.stroke(),e.closePath(),e.restore()},drawCorners:function(e){if(!this.hasControls)return;var t=this.cornerSize,n=t/2,r=this.strokeWidth/2,i=-(this.width/2),s=-(this.height/2),o,u,a=t/this.scaleX,f=t/this.scaleY,l=this.padding/this.scaleX,c=this.padding/this.scaleY,h=n/this.scaleY,p=n/this.scaleX,d=(n-t)/this.scaleX,v=(n-t)/this.scaleY,m=this.height,g=this.width,y=this.transparentCorners?"strokeRect":"fillRect",b=typeof G_vmlCanvasManager!="undefined";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,o=i-p-r-l,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g-p+r+l,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i-p-r-l,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g+d+r+l,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),this.get("lockUniScaling")||(o=i+g/2-p,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g/2-p,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g+d+r+l,u=s+m/2-h,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i-p-r-l,u=s+m/2-h,b||e.clearRect(o,u,a,f),e[y](o,u,a,f)),this.hasRotatingPoint&&(o=i+g/2-p,u=this.flipY?s+m+this.rotatingPointOffset/this.scaleY-f/2+r+c:s-this.rotatingPointOffset/this.scaleY-f/2-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f)),e.restore(),this},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){if(t.Image){var n=new u;n.onload=function(){e&&e(new t.Image(n),r),n=n.onload=null};var r={angle:this.get("angle"),flipX:this.get("flipX"),flipY:this.get("flipY")};this.set("angle",0).set("flipX",!1).set("flipY",!1),this.toDataURL(function(e){n.src=e})}return this},toDataURL:function(e){function i(t){t.left=n.width/2,t.top=n.height/2,t.setActive(!1),r.add(t);var i=r.toDataURL("png");r.dispose(),r=t=null,e&&e(i)}var n=t.document.createElement("canvas");!n.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(n),n.width=this.getBoundingRectWidth(),n.height=this.getBoundingRectHeight(),t.util.wrapElement(n,"div");var r=new t.Canvas(n);r.backgroundColor="transparent",r.renderAll(),this.constructor.async?this.clone(i):i(this.clone())},hasStateChanged:function(){return this.stateProperties.some(function(e){return this[e]!==this.originalState[e]},this)},saveState:function(){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){this.originalState={},this.saveState()},intersectsWithRect:function(e,n){var r=this.oCoords,i=new t.Point(r.tl.x,r.tl.y),s=new t.Point(r.tr.x,r.tr.y),o=new t.Point(r.bl.x,r.bl.y),u=new t.Point(r.br.x,r.br.y),a=t.Intersection.intersectPolygonRectangle([i,s,u,o],e,n);return a.status==="Intersection"},intersectsWithObject:function(e){function n(e){return{tl:new t.Point(e.tl.x,e.tl.y),tr:new t.Point(e.tr.x,e.tr.y),bl:new t.Point(e.bl.x,e.bl.y),br:new t.Point(e.br.x,e.br.y)}}var r=n(this.oCoords),i=n(e.oCoords),s=t.Intersection.intersectPolygonPolygon([r.tl,r.tr,r.br,r.bl],[i.tl,i.tr,i.br,i.bl]);return s.status==="Intersection"},isContainedWithinObject:function(e){return this.isContainedWithinRect(e.oCoords.tl,e.oCoords.br)},isContainedWithinRect:function(e,n){var r=this.oCoords,i=new t.Point(r.tl.x,r.tl.y),s=new t.Point(r.tr.x,r.tr.y),o=new t.Point(r.bl.x,r.bl.y);return i.x>e.x&&s.xe.y&&o.y=t&&l.d.y>=t)continue;l.o.x===l.d.x&&l.o.x>=e?(u=l.o.x,a=t):(r=0,i=(l.d.y-l.o.y)/(l.d.x-l.o.x),s=t-r*e,o=l.o.y-i*l.o.x,u=-(s-o)/(r-i),a=s+r*u),u>=e&&(f+=1);if(f===2)break}return f},_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}}},_setCornerCoords:function(){var e=this.oCoords,t=o(this.angle),n=o(45-this.angle),r=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,i=r*Math.cos(n),s=r*Math.sin(n),u=Math.sin(t),a=Math.cos(t);e.tl.corner={tl:{x:e.tl.x-s,y:e.tl.y-i},tr:{x:e.tl.x+i,y:e.tl.y-s},bl:{x:e.tl.x-i,y:e.tl.y+s},br:{x:e.tl.x+s,y:e.tl.y+i}},e.tr.corner={tl:{x:e.tr.x-s,y:e.tr.y-i},tr:{x:e.tr.x+i,y:e.tr.y-s},br:{x:e.tr.x+s,y:e.tr.y+i},bl:{x:e.tr.x-i,y:e.tr.y+s}},e.bl.corner={tl:{x:e.bl.x-s,y:e.bl.y-i},bl:{x:e.bl.x-i,y:e.bl.y+s},br:{x:e.bl.x+s,y:e.bl.y+i},tr:{x:e.bl.x+i,y:e.bl.y-s}},e.br.corner={tr:{x:e.br.x+i,y:e.br.y-s},bl:{x:e.br.x-i,y:e.br.y+s},br:{x:e.br.x+s,y:e.br.y+i},tl:{x:e.br.x-s,y:e.br.y-i}},e.ml.corner={tl:{x:e.ml.x-s,y:e.ml.y-i},tr:{x:e.ml.x+i,y:e.ml.y-s},bl:{x:e.ml.x-i,y:e.ml.y+s},br:{x:e.ml.x+s,y:e.ml.y+i}},e.mt.corner={tl:{x:e.mt.x-s,y:e.mt.y-i},tr:{x:e.mt.x+i,y:e.mt.y-s},bl:{x:e.mt.x-i,y:e.mt.y+s},br:{x:e.mt.x+s,y:e.mt.y+i}},e.mr.corner={tl:{x:e.mr.x-s,y:e.mr.y-i},tr:{x:e.mr.x+i,y:e.mr.y-s},bl:{x:e.mr.x-i,y:e.mr.y+s},br:{x:e.mr.x+s,y:e.mr.y+i}},e.mb.corner={tl:{x:e.mb.x-s,y:e.mb.y-i},tr:{x:e.mb.x+i,y:e.mb.y-s},bl:{x:e.mb.x-i,y:e.mb.y+s},br:{x:e.mb.x+s,y:e.mb.y+i}},e.mtr.corner={tl:{x:e.mtr.x-s+u*this.rotatingPointOffset,y:e.mtr.y-i-a*this.rotatingPointOffset},tr:{x:e.mtr.x+i+u*this.rotatingPointOffset,y:e.mtr.y-s-a*this.rotatingPointOffset},bl:{x:e.mtr.x-i+u*this.rotatingPointOffset,y:e.mtr.y+s-a*this.rotatingPointOffset},br:{x:e.mtr.x+s+u*this.rotatingPointOffset,y:e.mtr.y+i-a*this.rotatingPointOffset}}},toGrayscale:function(){var e=this.get("fill");return e&&this.set("overlayFill",(new t.Color(e)).toGrayscale().toRgb()),this},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradientFill:function(e){this.set("fill",t.Gradient.forObject(this,e))},animate:function(){if(arguments[0]&&typeof arguments[0]=="object")for(var e in arguments[0])this._animate(e,arguments[0][e],arguments[1]);else this._animate.apply(this,arguments);return this},_animate:function(e,n,r){var i=this;n=n.toString(),r?r=t.util.object.clone(r):r={},"from"in r||(r.from=this.get(e)),~n.indexOf("=")?n=this.get(e)+parseFloat(n.replace("=","")):n=parseFloat(n),t.util.animate({startValue:r.from,endValue:n,byValue:r.by,easing:r.easing,duration:r.duration,onChange:function(t){i.set(e,t),r.onChange&&r.onChange()},onComplete:function(){i.setCoords(),r.onComplete&&r.onComplete()}})},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.centerH().centerV()},remove:function(){return this.canvas.remove(this)},sendToBack:function(){return this.canvas.sendToBack(this),this},bringToFront:function(){return this.canvas.bringToFront(this),this},sendBackwards:function(){return this.canvas.sendBackwards(this),this},bringForward:function(){return this.canvas.bringForward(this),this}});var l=t.Object.prototype;for(var c=l.stateProperties.length;c--;){var h=l.stateProperties[c],p=h.charAt(0).toUpperCase()+h.slice(1),d="set"+p,v="get"+p;l[v]||(l[v]=function(e){return new Function('return this.get("'+e+'")')}(h)),l[d]||(l[d]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(h))}t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),n(t.Object,{NUM_FRACTION_DIGITS:2})}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r={x1:1,x2:1,y1:1,y2:1};if(t.Line){t.warn("fabric.Line is already defined");return}t.Line=t.util.createClass(t.Object,{type:"line",initialize:function(e,t){t=t||{},e||(e=[0,0,0,0]),this.callSuper("initialize",t),this.set("x1",e[0]),this.set("y1",e[1]),this.set("x2",e[2]),this.set("y2",e[3]),this._setWidthHeight(t)},_setWidthHeight:function(e){e||(e={}),this.set("width",this.x2-this.x1||1),this.set("height",this.y2-this.y1||1),this.set("left","left"in e?e.left:this.x1+this.width/2),this.set("top","top"in e?e.top:this.y1+this.height/2)},_set:function(e,t){return this[e]=t,e in r&&this._setWidthHeight(),this},_render:function(e){e.beginPath(),this.group&&e.translate(-this.group.width/2+this.left,-this.group.height/2+this.top),e.moveTo(this.width===1?0:-this.width/2,this.height===1?0:-this.height/2),e.lineTo(this.width===1?0:this.width/2,this.height===1?0:this.height/2),e.lineWidth=this.strokeWidth;var t=e.strokeStyle;e.strokeStyle=e.fillStyle,e.stroke(),e.strokeStyle=t},complexity:function(){return 1},toObject:function(e){return n(this.callSuper("toObject",e),{x1:this.get("x1"),y1:this.get("y1"),x2:this.get("x2"),y2:this.get("y2")})},toSVG:function(){return[""].join("")}}),t.Line.ATTRIBUTE_NAMES="x1 y1 x2 y2 stroke stroke-width transform".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",initialize:function(e){e=e||{},this.set("radius",e.radius||0),this.callSuper("initialize",e);var t=this.get("radius")*2;this.set("width",t).set("height",t)},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius")})},toSVG:function(){return'"},_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.fill&&e.fill(),this.stroke&&e.stroke()},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="cx cy r fill fill-opacity opacity stroke stroke-width transform".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.fill&&e.fill(),this.stroke&&e.stroke()},complexity:function(){return 1},toSVG:function(){var e=this.width/2,t=this.height/2,n=[-e+" "+t,"0 "+ -t,e+" "+t].join(",");return'"}}),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",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(){return[""].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),this.stroke&&e.stroke(),this.fill&&e.fill(),e.restore()},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES="cx cy rx ry fill fill-opacity opacity stroke stroke-width transform".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 r(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}t.Rect=t.util.createClass(t.Object,{type:"rect",rx:0,ry:0,initialize:function(e){e=e||{},this._initStateProperties(),this.callSuper("initialize",e),this._initRxRy(),this.x=0,this.y=0},_initStateProperties:function(){this.stateProperties=this.stateProperties.concat(["rx","ry"])},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e){var t=this.rx||0,n=this.ry||0,r=-this.width/2,i=-this.height/2,s=this.width,o=this.height;e.beginPath(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&this.group&&e.translate(this.width/2+this.x,this.height/2+this.y),!this.transformMatrix&&this.group&&e.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y),e.moveTo(r+t,i),e.lineTo(r+s-t,i),e.quadraticCurveTo(r+s,i,r+s,i+n,r+s,i+n),e.lineTo(r+s,i+o-n),e.quadraticCurveTo(r+s,i+o,r+s-t,i+o,r+s-t,i+o),e.lineTo(r+t,i+o),e.quadraticCurveTo(r,i+o,r,i+o-n,r,i+o-n),e.lineTo(r,i+n),e.quadraticCurveTo(r,i,r+t,i,r+t,i),e.closePath(),this.fill&&e.fill(),this.strokeDashArray?this._renderDashedStroke(e):this.stroke&&e.stroke()},_normalizeLeftTopProperties:function(e){return e.left&&this.set("left",e.left+this.getWidth()/2),this.set("x",e.left||0),e.top&&this.set("top",e.top+this.getHeight()/2),this.set("y",e.top||0),this},complexity:function(){return 1},toObject:function(e){return n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0})},toSVG:function(){return'"}}),t.Rect.ATTRIBUTE_NAMES="x y width height rx ry fill fill-opacity opacity stroke stroke-width transform".split(" "),t.Rect.fromElement=function(e,i){if(!e)return null;var s=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);s=r(s);var o=new t.Rect(n(i?t.util.object.clone(i):{},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",initialize:function(e,t){t=t||{},this.set("points",e),this.callSuper("initialize",t),this._calcDimensions()},_calcDimensions:function(){return t.Polygon.prototype._calcDimensions.call(this)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(){var e=[];for(var t=0,r=this.points.length;t"].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"].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;n1&&(g=Math.sqrt(g),n*=g,i*=g);var y=d/n,b=p/n,w=-p/i,E=d/i,S=y*l+b*c,x=w*l+E*c,T=y*e+b*t,N=w*e+E*t,C=(T-S)*(T-S)+(N-x)*(N-x),k=1/C-.25;k<0&&(k=0);var L=Math.sqrt(k);a===u&&(L=-L);var A=.5*(S+T)-L*(N-x),O=.5*(x+N)+L*(T-S),M=Math.atan2(x-O,S-A),_=Math.atan2(N-O,T-A),D=_-M;D<0&&a===1?D+=2*Math.PI:D>0&&a===0&&(D-=2*Math.PI);var P=Math.ceil(Math.abs(D/(Math.PI*.5+.001))),H=[];for(var B=0;B"},toObject:function(e){var t=h(this.callSuper("toObject",e),{path:this.path});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(){var e=[];for(var t=0,n=this.path.length;t',"',""].join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],n,r,i;for(var s=0,o,u=this.path.length;sc)for(var h=1,p=o.length;h"];for(var n=0,r=e.length;n"),t.join("")},toString:function(){return"#"},isSameColor:function(){var e=this.getObjects()[0].get("fill");return this.getObjects().every(function(t){return t.get("fill")===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},toGrayscale:function(){var e=this.paths.length;while(e--)this.paths[e].toGrayscale();return this},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e){var n=u(e.paths);return new t.PathGroup(n,e)}}(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,o=t.util.removeFromArray;if(t.Group)return;var u={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,{type:"group",initialize:function(e,t){t=t||{},this.objects=e||[],this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this._setOpacityIfSame(),this.setCoords(!0),this.saveCoords()},_updateObjectsCoords:function(){var e=this.left,t=this.top;this.forEachObject(function(n){var r=n.get("left"),i=n.get("top");n.set("originalLeft",r),n.set("originalTop",i),n.set("left",r-e),n.set("top",i-t),n.setCoords(),n.hideCorners=!0},this)},toString:function(){return"#"},getObjects:function(){return this.objects},addWithUpdate:function(e){return this._restoreObjectsState(),this.objects.push(e),this._calcBounds(),this._updateObjectsCoords(),this},removeWithUpdate:function(e){return this._restoreObjectsState(),o(this.objects,e),e.setActive(!1),this._calcBounds(),this._updateObjectsCoords(),this},add:function(e){return this.objects.push(e),this},remove:function(e){return o(this.objects,e),this},size:function(){return this.getObjects().length},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,lineHeight:!0,textDecoration:!0,textShadow:!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},contains:function(e){return this.objects.indexOf(e)>-1},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this.objects,"toObject",e)})},render:function(e,t){e.save(),this.transform(e);var n=Math.max(this.scaleX,this.scaleY);for(var r=this.objects.length;r>0;r--){var i=this.objects[r-1],s=i.borderScaleFactor,o=i.hasRotatingPoint;i.borderScaleFactor=n,i.hasRotatingPoint=!1,i.render(e),i.borderScaleFactor=s,i.hasRotatingPoint=o}!t&&this.active&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore(),this.setCoords()},item:function(e){return this.getObjects()[e]},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=typeof t.complexity=="function"?t.complexity():0,e},0)},_restoreObjectsState:function(){return this.objects.forEach(this._restoreObjectState,this),this},_restoreObjectState:function(e){var t=this.get("left"),n=this.get("top"),r=this.getAngle()*(Math.PI/180),i=Math.cos(r)*e.get("top")+Math.sin(r)*e.get("left"),s=-Math.sin(r)*e.get("top")+Math.cos(r)*e.get("left");return e.setAngle(e.getAngle()+this.getAngle()),e.set("left",t+s*this.get("scaleX")),e.set("top",n+i*this.get("scaleY")),e.set("scaleX",e.get("scaleX")*this.get("scaleX")),e.set("scaleY",e.get("scaleY")*this.get("scaleY")),e.setCoords(),e.hideCorners=!1,e.setActive(!1),e.setCoords(),this},destroy:function(){return this._restoreObjectsState()},saveCoords:function(){return this._originalLeft=this.get("left"),this._originalTop=this.get("top"),this},hasMoved:function(){return this._originalLeft!==this.get("left")||this._originalTop!==this.get("top")},setObjectsCoords:function(){return this.forEachObject(function(e){e.setCoords()}),this},activateAllObjects:function(){return this.forEachObject(function(e){e.setActive()}),this},forEachObject:t.StaticCanvas.prototype.forEachObject,_setOpacityIfSame:function(){var e=this.getObjects(),t=e[0]?e[0].get("opacity"):1,n=e.every(function(e){return e.get("opacity")===t});n&&(this.opacity=t)},_calcBounds:function(){var e=[],t=[],n,s,o,u,a,f,l,c=0,h=this.objects.length;for(;ce.x&&i-ne.y},toGrayscale:function(){var e=this.objects.length;while(e--)this.objects[e].toGrayscale();return this},toSVG:function(){var e=[];for(var t=0,n=this.objects.length;t'+e.join("")+""},get:function(e){if(e in u){if(this[e])return this[e];for(var t=0,n=this.objects.length;t'+'"+""},getSrc:function(){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.filters.length===0){this.setElement(this._originalImage),e&&e();return}var t=typeof Buffer!="undefined"&&typeof window=="undefined",n=this._originalImage,r=fabric.document.createElement("canvas"),i=t?new(require("canvas").Image):fabric.document.createElement("img"),s=this;!r.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(r),r.width=n.width,r.height=n.height,r.getContext("2d").drawImage(n,0,0,n.width,n.height),this.filters.forEach(function(e){e&&e.applyTo(r)}),i.onload=function(){s._element=i,e&&e(),i.onload=r=n=null},i.width=n.width,i.height=n.height;if(t){var o=r.toDataURL("image/png").substring(22);i.src=new Buffer(o,"base64"),s._element=i,e&&e()}else i.src=r.toDataURL("image/png");return this},_render:function(e){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)},_initFilters:function(e){e.filters&&e.filters.length&&(this.filters=e.filters.map(function(e){return e&&fabric.Image.filters[e.type].fromObject(e)}))},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement().width||0,this.height="height"in e?e.height:this.getElement().height||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){var n=fabric.document.createElement("img"),r=e.src;e.width&&(n.width=e.width),e.height&&(n.height=e.height),n.onload=function(){fabric.Image.prototype._initFilters.call(e,e);var r=new fabric.Image(n,e);t&&t(r),n=n.onload=n.onerror=null},n.onerror=function(){fabric.log("Error loading "+n.src),t&&t(null,!0),n=n.onload=n.onerror=null},n.src=r},fabric.Image.fromURL=function(e,t,n){var r=fabric.document.createElement("img");r.onload=function(){t&&t(new fabric.Image(r,n)),r=r.onload=null},r.src=e},fabric.Image.ATTRIBUTE_NAMES="x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href".split(" "),fabric.Image.fromElement=function(e,n,r){r||(r={});var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(i,r))},fabric.Image.async=!0}(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.setActive(!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.Grayscale=fabric.util.createClass({type:"Grayscale",applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=n.width,s=n.height,o,u,a,f;for(a=0;ao&&f>o&&l>o&&u(a-f)0&&(r[s]=a,r[s+1]=f,r[s+2]=l);t.putImageData(n,0,0)},toJSON:function(){return{type:this.type,color:this.color}}}),fabric.Image.filters.Tint.fromObject=function(e){return new fabric.Image.filters.Tint(e)},fabric.Image.filters.Convolute=fabric.util.createClass({type:"Convolute",initialize:function(e){e||(e={}),this.opaque=e.opaque,this.matrix=e.matrix||[0,0,0,0,1,0,0,0,0],this.tmpCtx=fabric.document.createElement("canvas").getContext("2d")},_createImageData:function(e,t){return this.tmpCtx.createImageData(e,t)},applyTo:function(e){var t=this.matrix,n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=Math.round(Math.sqrt(t.length)),s=Math.floor(i/2),o=r.data,u=r.width,a=r.height,f=u,l=a,c=this._createImageData(f,l),h=c.data,p=this.opaque?1:0;for(var d=0;d=0&&N=0&&C'},_render:function(e){typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaCufon:function(e){var t=Cufon.textOptions||(Cufon.textOptions={});t.left=this.left,t.top=this.top,t.context=e,t.color=this.fill;var n=this._initDummyElementForCufon();this.transform(e),Cufon.replaceElement(n,{engine:"canvas",separate:"none",fontFamily:this.fontFamily,fontWeight:this.fontWeight,textDecoration:this.textDecoration,textShadow:this.textShadow,textAlign:this.textAlign,fontStyle:this.fontStyle,lineHeight:this.lineHeight,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor}),this.width=t.width,this.height=t.height,this._totalLineHeight=t.totalLineHeight,this._fontAscent=t.fontAscent,this._boundaries=t.boundaries,this._shadowOffsets=t.shadowOffsets,this._shadows=t.shadows||[],n=null,this.setCoords()},_renderViaNative:function(e){this.transform(e),this._setTextStyles(e);var t=this.text.split(/\r?\n/);this.width=this._getTextWidth(e,t),this.height=this._getTextHeight(e,t),this._renderTextBackground(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0)),this._setTextShadow(e),this._renderTextFill(e,t),this.textShadow&&e.restore(),this._renderTextStroke(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,t),this._setBoundaries(e,t),this._totalLineHeight=0,this.setCoords()},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_setTextShadow:function(e){if(this.textShadow){var t=/\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/,n=this.textShadow,r=t.exec(this.textShadow),i=n.replace(t,"");e.save(),e.shadowColor=i,e.shadowOffsetX=parseInt(r[1],10),e.shadowOffsetY=parseInt(r[2],10),e.shadowBlur=parseInt(r[3],10),this._shadows=[{blur:e.shadowBlur,color:e.shadowColor,offX:e.shadowOffsetX,offY:e.shadowOffsetY}],this._shadowOffsets=[[parseInt(e.shadowOffsetX,10),parseInt(e.shadowOffsetY,10)]]}},_drawTextLine:function(e,t,n,r,i){if(this.textAlign!=="justify"){t[e](n,r,i);return}var s=t.measureText(n).width,o=this.width;if(o>s){var u=n.split(/\s+/),a=t.measureText(n.replace(/\s+/g,"")).width,f=o-a,l=u.length-1,c=f/l,h=0;for(var p=0,d=u.length;p-1&&i(this.fontSize),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(0)},_getFontDeclaration:function(){return[this.fontStyle,this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},_initDummyElementForCufon:function(){var e=t.document.createElement("pre"),n=t.document.createElement("div");return n.appendChild(e),typeof G_vmlCanvasManager=="undefined"?e.innerHTML=this.text:e.innerText=this.text.replace(/\r?\n/gi,"\r"),e.style.fontSize=this.fontSize+"px",e.style.letterSpacing="normal",e},render:function(e,t){e.save(),this._render(e),!t&&this.active&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore()},toObject:function(e){return 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,textShadow:this.textShadow,textAlign:this.textAlign,path:this.path,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative})},toSVG:function(){var e=this.text.split(/\r?\n/),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,s=this._getSVGTextAndBg(t,n,e),o=this._getSVGShadows(t,e);return r+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,['',s.textBgRects.join(""),"',o.join(""),s.textSpans.join(""),"",""].join("")},_getSVGShadows:function(e,n){var r=[],s,o,u,a,f=1;if(!this._shadows||!this._boundaries)return r;for(s=0,u=this._shadows.length;s",t.util.string.escapeXml(n[o]),""),f=1}else f++;return r},_getSVGTextAndBg:function(e,n,r){var s=[],o=[],u,a,f,l=1;this.backgroundColor&&this._boundaries&&o.push("');for(u=0,f=r.length;u",t.util.string.escapeXml(r[u]),""),l=1):l++;if(!this.textBackgroundColor||!this._boundaries)continue;o.push("')}return{textSpans:s,textBgRects:o}},_getFillAttributes:function(e){var n=e?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},setColor:function(e){return this.set("fill",e),this},setFontsize:function(e){return this.set("fontSize",e),this._initDimensions(),this.setCoords(),this},getText:function(){return this.text},setText:function(e){return this.set("text",e),this._initDimensions(),this.setCoords(),this},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t)}}),t.Text.ATTRIBUTE_NAMES="x y fill fill-opacity opacity stroke stroke-width transform font-family font-style font-weight font-size text-decoration".split(" "),t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},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);var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i}}(typeof exports!="undefined"?exports:this),function(){function request(e,t,n){var r=URL.parse(e),i=HTTP.createClient(r.port,r.hostname),s=i.request("GET",r.pathname,{host:r.hostname});i.addListener("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+i.host+":"+i.port):fabric.log(e.message)}),s.end(),s.on("response",function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=(new require("xmldom")).DOMParser,URL=require("url"),HTTP=require("http"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){var r=new Image;e&&e.indexOf("data")===0?(r.src=r._src=e,t&&t.call(n,r)):e&&request(e,"binary",function(i){r.src=new Buffer(i,"binary"),r._src=e,t&&t.call(n,r)})},fabric.loadSVGFromURL=function(e,t){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),request(e,"",function(e){fabric.loadSVGFromString(e,t)})},fabric.loadSVGFromString=function(e,t){var n=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(n.documentElement,function(e,n){t(e,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),t(r)})},fabric.createCanvasForNode=function(e,t){var n=fabric.document.createElement("canvas"),r=new Canvas(e||600,t||600);n.style={},n.width=r.width,n.height=r.height;var i=fabric.Canvas||fabric.StaticCanvas,s=new i(n);return s.contextContainer=r.getContext("2d"),s.nodeCanvas=r,s},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this),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),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); \ No newline at end of file +)e.push(a.oCoords[p].x),t.push(a.oCoords[p].y)}n=r(e),o=i(e),s=r(t),u=i(t),f=o-n||0,l=u-s||0,this.width=f,this.height=l,this.left=n+f/2||0,this.top=s+l/2||0},containsPoint:function(e){var t=this.get("width")/2,n=this.get("height")/2,r=this.get("left"),i=this.get("top");return r-te.x&&i-ne.y},toGrayscale:function(){var e=this.objects.length;while(e--)this.objects[e].toGrayscale();return this},toSVG:function(){var e=[];for(var t=0,n=this.objects.length;t'+e.join("")+""},get:function(e){if(e in u){if(this[e])return this[e];for(var t=0,n=this.objects.length;t'+'"+""},getSrc:function(){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.filters.length===0){this.setElement(this._originalImage),e&&e();return}var t=typeof Buffer!="undefined"&&typeof window=="undefined",n=this._originalImage,r=fabric.document.createElement("canvas"),i=t?new(require("canvas").Image):fabric.document.createElement("img"),s=this;!r.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(r),r.width=n.width,r.height=n.height,r.getContext("2d").drawImage(n,0,0,n.width,n.height),this.filters.forEach(function(e){e&&e.applyTo(r)}),i.onload=function(){s._element=i,e&&e(),i.onload=r=n=null},i.width=n.width,i.height=n.height;if(t){var o=r.toDataURL("image/png").substring(22);i.src=new Buffer(o,"base64"),s._element=i,e&&e()}else i.src=r.toDataURL("image/png");return this},_render:function(e){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)},_initFilters:function(e){e.filters&&e.filters.length&&(this.filters=e.filters.map(function(e){return e&&fabric.Image.filters[e.type].fromObject(e)}))},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement().width||0,this.height="height"in e?e.height:this.getElement().height||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){var n=fabric.document.createElement("img"),r=e.src;e.width&&(n.width=e.width),e.height&&(n.height=e.height),n.onload=function(){fabric.Image.prototype._initFilters.call(e,e);var r=new fabric.Image(n,e);t&&t(r),n=n.onload=n.onerror=null},n.onerror=function(){fabric.log("Error loading "+n.src),t&&t(null,!0),n=n.onload=n.onerror=null},n.src=r},fabric.Image.fromURL=function(e,t,n){var r=fabric.document.createElement("img");r.onload=function(){t&&t(new fabric.Image(r,n)),r=r.onload=null},r.src=e},fabric.Image.ATTRIBUTE_NAMES="x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href".split(" "),fabric.Image.fromElement=function(e,n,r){r||(r={});var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(i,r))},fabric.Image.async=!0}(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.setActive(!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.Grayscale=fabric.util.createClass({type:"Grayscale",applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=n.width,s=n.height,o,u,a,f;for(a=0;ao&&f>o&&l>o&&u(a-f)0&&(r[s]=a,r[s+1]=f,r[s+2]=l);t.putImageData(n,0,0)},toJSON:function(){return{type:this.type,color:this.color}}}),fabric.Image.filters.Tint.fromObject=function(e){return new fabric.Image.filters.Tint(e)},fabric.Image.filters.Convolute=fabric.util.createClass({type:"Convolute",initialize:function(e){e||(e={}),this.opaque=e.opaque,this.matrix=e.matrix||[0,0,0,0,1,0,0,0,0],this.tmpCtx=fabric.document.createElement("canvas").getContext("2d")},_createImageData:function(e,t){return this.tmpCtx.createImageData(e,t)},applyTo:function(e){var t=this.matrix,n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=Math.round(Math.sqrt(t.length)),s=Math.floor(i/2),o=r.data,u=r.width,a=r.height,f=u,l=a,c=this._createImageData(f,l),h=c.data,p=this.opaque?1:0;for(var d=0;d=0&&N=0&&C'},_render:function(e){typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaCufon:function(e){var t=Cufon.textOptions||(Cufon.textOptions={});t.left=this.left,t.top=this.top,t.context=e,t.color=this.fill;var n=this._initDummyElementForCufon();this.transform(e),Cufon.replaceElement(n,{engine:"canvas",separate:"none",fontFamily:this.fontFamily,fontWeight:this.fontWeight,textDecoration:this.textDecoration,textShadow:this.textShadow,textAlign:this.textAlign,fontStyle:this.fontStyle,lineHeight:this.lineHeight,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor}),this.width=t.width,this.height=t.height,this._totalLineHeight=t.totalLineHeight,this._fontAscent=t.fontAscent,this._boundaries=t.boundaries,this._shadowOffsets=t.shadowOffsets,this._shadows=t.shadows||[],n=null,this.setCoords()},_renderViaNative:function(e){this.transform(e),this._setTextStyles(e);var t=this.text.split(/\r?\n/);this.width=this._getTextWidth(e,t),this.height=this._getTextHeight(e,t),this._renderTextBackground(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0)),this._setTextShadow(e),this._renderTextFill(e,t),this.textShadow&&e.restore(),this._renderTextStroke(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,t),this._setBoundaries(e,t),this._totalLineHeight=0,this.setCoords()},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_setTextShadow:function(e){if(this.textShadow){var t=/\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/,n=this.textShadow,r=t.exec(this.textShadow),i=n.replace(t,"");e.save(),e.shadowColor=i,e.shadowOffsetX=parseInt(r[1],10),e.shadowOffsetY=parseInt(r[2],10),e.shadowBlur=parseInt(r[3],10),this._shadows=[{blur:e.shadowBlur,color:e.shadowColor,offX:e.shadowOffsetX,offY:e.shadowOffsetY}],this._shadowOffsets=[[parseInt(e.shadowOffsetX,10),parseInt(e.shadowOffsetY,10)]]}},_drawTextLine:function(e,t,n,r,i){if(this.textAlign!=="justify"){t[e](n,r,i);return}var s=t.measureText(n).width,o=this.width;if(o>s){var u=n.split(/\s+/),a=t.measureText(n.replace(/\s+/g,"")).width,f=o-a,l=u.length-1,c=f/l,h=0;for(var p=0,d=u.length;p-1&&i(this.fontSize),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(0)},_getFontDeclaration:function(){return[this.fontStyle,this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},_initDummyElementForCufon:function(){var e=t.document.createElement("pre"),n=t.document.createElement("div");return n.appendChild(e),typeof G_vmlCanvasManager=="undefined"?e.innerHTML=this.text:e.innerText=this.text.replace(/\r?\n/gi,"\r"),e.style.fontSize=this.fontSize+"px",e.style.letterSpacing="normal",e},render:function(e,t){e.save(),this._render(e),!t&&this.active&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore()},toObject:function(e){return 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,textShadow:this.textShadow,textAlign:this.textAlign,path:this.path,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative})},toSVG:function(){var e=this.text.split(/\r?\n/),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,s=this._getSVGTextAndBg(t,n,e),o=this._getSVGShadows(t,e);return r+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,['',s.textBgRects.join(""),"',o.join(""),s.textSpans.join(""),"",""].join("")},_getSVGShadows:function(e,n){var r=[],s,o,u,a,f=1;if(!this._shadows||!this._boundaries)return r;for(s=0,u=this._shadows.length;s",t.util.string.escapeXml(n[o]),""),f=1}else f++;return r},_getSVGTextAndBg:function(e,n,r){var s=[],o=[],u,a,f,l=1;this.backgroundColor&&this._boundaries&&o.push("');for(u=0,f=r.length;u",t.util.string.escapeXml(r[u]),""),l=1):l++;if(!this.textBackgroundColor||!this._boundaries)continue;o.push("')}return{textSpans:s,textBgRects:o}},_getFillAttributes:function(e){var n=e?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},setColor:function(e){return this.set("fill",e),this},setFontsize:function(e){return this.set("fontSize",e),this._initDimensions(),this.setCoords(),this},getText:function(){return this.text},setText:function(e){return this.set("text",e),this._initDimensions(),this.setCoords(),this},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t)}}),t.Text.ATTRIBUTE_NAMES="x y fill fill-opacity opacity stroke stroke-width transform font-family font-style font-weight font-size text-decoration".split(" "),t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},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);var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i}}(typeof exports!="undefined"?exports:this),function(){function request(e,t,n){var r=URL.parse(e),i=HTTP.createClient(r.port,r.hostname),s=i.request("GET",r.pathname,{host:r.hostname});i.addListener("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+i.host+":"+i.port):fabric.log(e.message)}),s.end(),s.on("response",function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=(new require("xmldom")).DOMParser,URL=require("url"),HTTP=require("http"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){var r=new Image;e&&e.indexOf("data")===0?(r.src=r._src=e,t&&t.call(n,r)):e&&request(e,"binary",function(i){r.src=new Buffer(i,"binary"),r._src=e,t&&t.call(n,r)})},fabric.loadSVGFromURL=function(e,t){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),request(e,"",function(e){fabric.loadSVGFromString(e,t)})},fabric.loadSVGFromString=function(e,t){var n=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(n.documentElement,function(e,n){t(e,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),t(r)})},fabric.createCanvasForNode=function(e,t){var n=fabric.document.createElement("canvas"),r=new Canvas(e||600,t||600);n.style={},n.width=r.width,n.height=r.height;var i=fabric.Canvas||fabric.StaticCanvas,s=new i(n);return s.contextContainer=r.getContext("2d"),s.nodeCanvas=r,s},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this),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),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); +======= +/* build: `node build.js modules=ALL exclude=gestures` *//*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.0.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";var Cufon=function(){function r(e){var t=this.face=e.face;this.glyphs=e.glyphs,this.w=e.w,this.baseSize=parseInt(t["units-per-em"],10),this.family=t["font-family"].toLowerCase(),this.weight=t["font-weight"],this.style=t["font-style"]||"normal",this.viewBox=function(){var e=t.bbox.split(/\s+/),n={minX:parseInt(e[0],10),minY:parseInt(e[1],10),maxX:parseInt(e[2],10),maxY:parseInt(e[3],10)};return n.width=n.maxX-n.minX,n.height=n.maxY-n.minY,n.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},n}(),this.ascent=-parseInt(t.ascent,10),this.descent=-parseInt(t.descent,10),this.height=-this.ascent+this.descent}function i(){var e={},t={oblique:"italic",italic:"oblique"};this.add=function(t){(e[t.style]||(e[t.style]={}))[t.weight]=t},this.get=function(n,r){var i=e[n]||e[t[n]]||e.normal||e.italic||e.oblique;if(!i)return null;r={normal:400,bold:700}[r]||parseInt(r,10);if(i[r])return i[r];var s={1:1,99:0}[r%100],o=[],u,a;s===undefined&&(s=r>400),r==500&&(r=400);for(var f in i){f=parseInt(f,10);if(!u||fa)a=f;o.push(f)}return ra&&(r=a),o.sort(function(e,t){return(s?e>r&&t>r?et:et:e=i.length+e?r():setTimeout(arguments.callee,10)}),function(t){e?t():n.push(t)}}(),supports:function(e,t){var n=fabric.document.createElement("span").style;return n[e]===undefined?!1:(n[e]=t,n[e]===t)},textAlign:function(e,t,n,r){return t.get("textAlign")=="right"?n>0&&(e=" "+e):nk&&(k=N),A.push(N),N=0;continue}var O=t.glyphs[T[b]]||t.missingGlyph;if(!O)continue;N+=C=Number(O.w||t.w)+h}A.push(N),N=Math.max(k,N);var M=[];for(var b=A.length;b--;)M[b]=N-A[b];if(C===null)return null;d+=l.width-C,m+=l.minX;var _,D;if(f)_=u,D=u.firstChild;else{_=fabric.document.createElement("span"),_.className="cufon cufon-canvas",_.alt=n,D=fabric.document.createElement("canvas"),_.appendChild(D);if(i.printable){var P=fabric.document.createElement("span");P.className="cufon-alt",P.appendChild(fabric.document.createTextNode(n)),_.appendChild(P)}}var H=_.style,B=D.style||{},j=c.convert(l.height-p+v),F=Math.ceil(j),I=F/j;D.width=Math.ceil(c.convert(N+d-m)*I),D.height=F,p+=l.minY,B.top=Math.round(c.convert(p-t.ascent))+"px",B.left=Math.round(c.convert(m))+"px";var q=Math.ceil(c.convert(N*I)),R=q+"px",U=c.convert(t.height),z=(i.lineHeight-1)*c.convert(-t.ascent/5)*(L-1);Cufon.textOptions.width=q,Cufon.textOptions.height=U*L+z,Cufon.textOptions.lines=L,Cufon.textOptions.totalLineHeight=z,e?(H.width=R,H.height=U+"px"):(H.paddingLeft=R,H.paddingBottom=U-1+"px");var W=Cufon.textOptions.context||D.getContext("2d"),X=F/l.height;Cufon.textOptions.fontAscent=t.ascent*X,Cufon.textOptions.boundaries=null;for(var V=Cufon.textOptions.shadowOffsets,b=y.length;b--;)V[b]=[y[b][0]*X,y[b][1]*X];W.save(),W.scale(X,X),W.translate(-m-1/X*D.width/2+(Cufon.fonts[t.family].offsetLeft||0),-p-Cufon.textOptions.height/X/2+(Cufon.fonts[t.family].offsetTop||0)),W.lineWidth=t.face["underline-thickness"],W.save();var J=Cufon.getTextDecoration(i),K=i.fontStyle==="italic";W.save(),Q();if(g)for(var b=0,w=g.length;b.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(e,t,i,s,o,u,a){var f=t===null;f&&(t=o.alt);var l=e.viewBox,c=i.computedFontSize||(i.computedFontSize=new Cufon.CSS.Size(n(u,i.get("fontSize"))+"px",e.baseSize)),h=i.computedLSpacing;h==undefined&&(h=i.get("letterSpacing"),i.computedLSpacing=h=h=="normal"?0:~~c.convertFrom(r(u,h)));var p,d;if(f)p=o,d=o.firstChild;else{p=fabric.document.createElement("span"),p.className="cufon cufon-vml",p.alt=t,d=fabric.document.createElement("span"),d.className="cufon-vml-canvas",p.appendChild(d);if(s.printable){var v=fabric.document.createElement("span");v.className="cufon-alt",v.appendChild(fabric.document.createTextNode(t)),p.appendChild(v)}a||p.appendChild(fabric.document.createElement("cvml:shape"))}var m=p.style,g=d.style,y=c.convert(l.height),b=Math.ceil(y),w=b/y,E=l.minX,S=l.minY;g.height=b,g.top=Math.round(c.convert(S-e.ascent)),g.left=Math.round(c.convert(E)),m.height=c.convert(e.height)+"px";var x=Cufon.getTextDecoration(s),T=i.get("color"),N=Cufon.CSS.textTransform(t,i).split(""),C=0,k=0,L=null,A,O,M=s.textShadow;for(var _=0,D=0,P=N.length;_r?n:i-t;s(u(f,a,l,n));if(i>r||o()){e.onComplete&&e.onComplete();return}h(c)}()}function p(e,t,n){if(e){var r=new Image;r.onload=function(){t&&t.call(n,r),r=r.onload=null},r.src=e}else t&&t.call(n,e)}function d(e,t){function n(e){return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(e))]}function r(){++s===o&&t&&t(i)}var i=[],s=0,o=e.length;e.forEach(function(e,t){if(!e.type)return;var s=n(e.type);s.async?s.fromObject(e,function(e,n){n||(i[t]=e),r()}):(i[t]=s.fromObject(e),r())})}function v(e,t,n){var r;if(e.length>1){var i=e.some(function(e){return e.type==="text"});i?(r=new fabric.Group([],t),e.reverse().forEach(function(e){e.cx&&(e.left=e.cx),e.cy&&(e.top=e.cy),r.addWithUpdate(e)})):r=new fabric.PathGroup(e,t)}else r=e[0];return typeof n!="undefined"&&r.setSourcePath(n),r}function m(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),n[d?"lineTo":"moveTo"](r,0),d=!d;n.restore()}function y(){var e=fabric.document.createElement("canvas");return!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e}var e=Math.sqrt,t=Math.atan2;fabric.util={};var i=Math.PI/180,c=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)},h=function(){return c.apply(fabric.window,arguments)};fabric.util.removeFromArray=n,fabric.util.degreesToRadians=s,fabric.util.radiansToDegrees=o,fabric.util.rotatePoint=u,fabric.util.toFixed=a,fabric.util.getRandomInt=r,fabric.util.falseFunction=f,fabric.util.animate=l,fabric.util.requestAnimFrame=h,fabric.util.loadImage=p,fabric.util.enlivenObjects=d,fabric.util.groupSVGElements=v,fabric.util.populateWithProperties=m,fabric.util.drawDashedLine=g,fabric.util.createCanvasElement=y}(),function(){function t(t,n){var r=e.call(arguments,2),i=[];for(var s=0,o=t.length;s=r&&(r=e[n][t]);else while(n--)e[n]>=r&&(r=e[n]);return r}function r(e,t){if(!e||e.length===0)return undefined;var n=e.length-1,r=t?e[n][t]:e[n];if(t)while(n--)e[n][t]>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==1/0&&r!==-1/0&&(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 e(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.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){var t=0,n=0;do t+=e.offsetTop||0,n+=e.offsetLeft||0,e=e.offsetParent;while(e);return{left:n,top:t}}var e=Array.prototype.slice,n=function(t){return e.call(t,0)},r;try{r=n(fabric.document.childNodes)instanceof +Array}catch(i){}r||(n=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var f;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?f=function(e){return fabric.document.defaultView.getComputedStyle(e,null).position}:f=function(e){var t=e.style.position;return!t&&e.currentStyle&&(t=e.currentStyle.position),t},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.type="text/javascript",r.setAttribute("runat","server"),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=n,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getElementOffset=a,fabric.util.getElementPosition=f}(),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}(),function(){function e(e,t,n,r){return n*(e/=r)*e+t}function t(e,t,n,r){return-n*(e/=r)*(e-2)+t}function n(e,t,n,r){return e/=r/2,e<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}function r(e,t,n,r){return n*(e/=r)*e*e+t}function i(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function s(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e+t:n/2*((e-=2)*e*e+2)+t}function o(e,t,n,r){return n*(e/=r)*e*e*e+t}function u(e,t,n,r){return-n*((e=e/r-1)*e*e*e-1)+t}function a(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e*e+t:-n/2*((e-=2)*e*e*e-2)+t}function f(e,t,n,r){return n*(e/=r)*e*e*e*e+t}function l(e,t,n,r){return n*((e=e/r-1)*e*e*e*e+1)+t}function c(e,t,n,r){return e/=r/2,e<1?n/2*e*e*e*e*e+t:n/2*((e-=2)*e*e*e*e+2)+t}function h(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t}function p(e,t,n,r){return n*Math.sin(e/r*(Math.PI/2))+t}function d(e,t,n,r){return-n/2*(Math.cos(Math.PI*e/r)-1)+t}function v(e,t,n,r){return e===0?t:n*Math.pow(2,10*(e/r-1))+t}function m(e,t,n,r){return e===r?t+n:n*(-Math.pow(2,-10*e/r)+1)+t}function g(e,t,n,r){return e===0?t:e===r?t+n:(e/=r/2,e<1?n/2*Math.pow(2,10*(e-1))+t:n/2*(-Math.pow(2,-10*--e)+2)+t)}function y(e,t,n,r){return-n*(Math.sqrt(1-(e/=r)*e)-1)+t}function b(e,t,n,r){return n*Math.sqrt(1-(e=e/r-1)*e)+t}function w(e,t,n,r){return e/=r/2,e<1?-n/2*(Math.sqrt(1-e*e)-1)+t:n/2*(Math.sqrt(1-(e-=2)*e)+1)+t}function E(e,t,n,r){var i=1.70158,s=0,o=n;return e===0?t:(e/=r,e===1?t+n:(s||(s=r*.3),o-1;e=e.split(/\s+/);var n=[],r,i;if(t){r=0,i=e.length;for(;r/i,"")));if(!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){d.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),d.has(e,function(r){r?d.get(e,function(e){var t=m(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})}function m(e){var n=e.objects,i=e.options;return n=n.map(function(e){return t[r(e.type)].fromObject(e)}),{objects:n,options:i}}function g(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)}function y(e){var t="";for(var n=0,r=e.length;n",'",""].join("")),t}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s={cx:"left",x:"left",cy:"top",y:"top",r:"radius","fill-opacity":"opacity","fill-rule":"fillRule","stroke-width":"strokeWidth",transform:"transformMatrix","text-decoration":"textDecoration","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle","font-family":"fontFamily"};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 t(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function n(e,t){e[2]=t[0]}function r(e,t){e[1]=t[0]}function i(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var s=[1,0,0,1,0,0],o="(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)",u="(?:\\s+,?\\s*|,\\s*)",a="(?:(skewX)\\s*\\(\\s*("+o+")\\s*\\))",f="(?:(skewY)\\s*\\(\\s*("+o+")\\s*\\))",l="(?:(rotate)\\s*\\(\\s*("+o+")(?:"+u+"("+o+")"+u+"("+o+"))?\\s*\\))",c="(?:(scale)\\s*\\(\\s*("+o+")(?:"+u+"("+o+"))?\\s*\\))",h="(?:(translate)\\s*\\(\\s*("+o+")(?:"+u+"("+o+"))?\\s*\\))",p="(?:(matrix)\\s*\\(\\s*("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+u+"("+o+")"+"\\s*\\))",d="(?:"+p+"|"+h+"|"+c+"|"+l+"|"+a+"|"+f+")",v="(?:"+d+"(?:"+u+d+")*"+")",m="^\\s*(?:"+v+"?)\\s*$",g=new RegExp(m),y=new RegExp(d);return function(o){var u=s.concat();return!o||o&&!g.test(o)?u:(o.replace(y,function(s){var o=(new RegExp(d)).exec(s).filter(function(e){return e!==""&&e!=null}),a=o[1],f=o.slice(2).map(parseFloat);switch(a){case"translate":i(u,f);break;case"rotate":e(u,f);break;case"scale":t(u,f);break;case"skewX":n(u,f);break;case"skewY":r(u,f);break;case"matrix":u=f}}),u)}}(),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+)?(?: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){f=n.selectNodes("//*[name(.)!='svg']");var l=[];for(var c=0,p=f.length;c0&&this.init(e,t)}var t=e.fabric||(e.fabric={});if(t.Point){t.warn("fabric.Point is already defined");return}t.Point=n,n.prototype={constructor:n,init:function(e,t){this.x=e,this.y=t},add:function(e){return new n(this.x+e.x,this.y+e.y)},addEquals:function(e){return this.x+=e.x,this.y+=e.y,this},scalarAdd:function(e){return new n(this.x+e,this.y+e)},scalarAddEquals:function(e){return this.x+=e,this.y+=e,this},subtract:function(e){return new n(this.x-e.x,this.y-e.y)},subtractEquals:function(e){return this.x-=e.x,this.y-=e.y,this},scalarSubtract:function(e){return new n(this.x-e,this.y-e)},scalarSubtractEquals:function(e){return this.x-=e,this.y-=e,this},multiply:function(e){return new n(this.x*e,this.y*e)},multiplyEquals:function(e){return this.x*=e,this.y*=e,this},divide:function(e){return new n(this.x/e,this.y/e)},divideEquals:function(e){return this.x/=e,this.y/=e,this},eq:function(e){return this.x===e.x&&this.y===e.y},lt:function(e){return this.xe.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){arguments.length>0&&this.init(e)}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={init:function(e){this.status=e,this.points=[]},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("No Intersection")}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("No Intersection"),s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n("No Intersection"),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("No Intersection");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])}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=n.sourceFromHex(e);t||(t=n.sourceFromRgb(e)),t&&this.setSource(t)},getSource:function(){return this._source},setSource:function(e){this._source=e},toRgb:function(){var e=this.getSource();return"rgb("+e[0]+","+e[1]+","+e[2]+")"},toRgba:function(){var e=this.getSource();return"rgba("+e[0]+","+e[1]+","+e[2]+","+e[3]+")"},toHex:function(){var e=this.getSource(),t=e[0].toString(16);t=t.length===1?"0"+t:t;var n=e[1].toString(16);n=n.length===1?"0"+n:n;var r=e[2].toString(16);return r=r.length===1?"0"+r:r,t.toUpperCase()+n.toUpperCase()+r.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(e){var t=this.getSource();return t[3]=e,this.setSource(t),this},toGrayscale:function(){var e=this.getSource(),t=parseInt((e[0]*.3+e[1]*.59+e[2]*.11).toFixed(0),10),n=e[3];return this.setSource([t,t,t,n]),this},toBlackWhite:function(e){var t=this.getSource(),n=(t[0]*.3+t[1]*.59+t[2]*.11).toFixed(0),r=t[3];return e=e||127,n=Number(n)',''),t.push("',"Created with Fabric.js ",fabric.version,"",fabric.createSVGFontFacesMarkup(this.getObjects())),this.backgroundImage&&t.push(''),this.overlayImage&&t.push('');for(var n=0,r=this.getObjects(),i=r.length;n"),t.join("")},isEmpty:function(){return this._objects.length===0},remove:function(e){this.getActiveObject()===e&&(this.fire("before:selection:cleared",{target:e}),this.discardActiveObject(),this.fire("selection:cleared"));var t=this._objects,n=t.indexOf(e);return n!==-1&&(t.splice(n,1),this.fire("object:removed",{target:e})),this.renderAll(),e},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll()},sendBackwards:function(e){var t=this._objects.indexOf(e),r=t;if(t!==0){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}}n(this._objects,e),this._objects.splice(r,0,e)}return this.renderAll()},bringForward:function(e){var t=this.getObjects(),r=t.indexOf(e),i=r;if(r!==t.length-1){for(var s=r+1,o=this._objects.length;s"},e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',toGrayscale:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=n.width,s=n.height,o,u,a,f;for(a=0;a0&&(t>this.targetFindTolerance?t-=this.targetFindTolerance:t=0,n>this.targetFindTolerance?n-=this.targetFindTolerance:n=0);var o=!0,u=r.getImageData(t,n,this.targetFindTolerance*2||1,this.targetFindTolerance*2||1);for(var a=3;a0?0:-n),t.ey-(r>0?0:-r),i,o),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var u=t.ex+a-(n>0?0:i),f=t.ey+a-(r>0?0:o);e.beginPath(),fabric.util.drawDashedLine(e,u,f,u+i,f,this.selectionDashArray),fabric.util.drawDashedLine(e,u,f+o-1,u+i,f+o-1,this.selectionDashArray),fabric.util.drawDashedLine(e,u,f,u,f+o,this.selectionDashArray),fabric.util.drawDashedLine(e,u+i-1,f,u+i-1,f+o,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+a-(n>0?0:i),t.ey+a-(r>0?0:o),i,o)},_findSelectedObjects:function(e){var t=[],n=this._groupSelector.ex,r=this._groupSelector.ey,i=n+this._groupSelector.left,s=r+this._groupSelector.top,a,f=new fabric.Point(o(n,i),o(r,s)),l=new fabric.Point(u(n,i),u(r,s));for(var c=0,h=this._objects.length;c1&&(t=new fabric.Group(t),this.setActiveGroup(t),t.saveCoords(),this.fire("selection:created",{target:t})),this.renderAll()},findTarget:function(e,t){var n,r=this.getPointer(e);if(this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e,this._offset))return n=this.lastRenderedObjectWithControlsAboveOverlay,n;var i=this.getActiveGroup();if(i&&!t&&this.containsPoint(e,i))return n=i,n;var s=[];for(var o=this._objects.length;o--;)if(this._objects[o]&&this.containsPoint(e,this._objects[o])){if(!this.perPixelTargetFind&&!this._objects[o].perPixelTargetFind){n=this._objects[o],this.relatedTarget=n;break}s[s.length]=this._objects[o]}for(var u=0,a=s.length;u"},get:function(e){return this[e]},set:function(e,t){if(typeof e=="object")for(var n in e)this._set(n,e[n]);else typeof t=="function"?this._set(e,t(this.get(e))):this._set(e,t);return this},_set:function(e,t){var n=e==="scaleX"||e==="scaleY";n&&(t=this._constrainScale(t));if(e==="scaleX"&&t<0)this.flipX=!this.flipX,t*=-1;else if(e==="scaleY"&&t<0)this.flipY=!this.flipY,t*=-1;else if(e==="width"||e==="height")this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2);return this[e]=t,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,t){if(this.width===0||this.height===0)return;e.save();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);if(this.stroke||this.strokeDashArray)e.lineWidth=this.strokeWidth,this.stroke&&this.stroke.toLive?e.strokeStyle=this.stroke.toLive(e):e.strokeStyle=this.stroke;this.overlayFill?e.fillStyle=this.overlayFill:this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e):this.fill),n&&this.group&&(e.translate(-this.group.width/2,-this.group.height/2),e.transform(n[0],n[1],n[2],n[3],n[4],n[5])),this._setShadow(e),this._render(e,t),this._removeShadow(e),this.active&&!t&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore()},_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){e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){if(t.Image){var n=new o;n.onload=function(){e&&e(new t.Image(n),r),n=n.onload=null};var r={angle:this.get("angle"),flipX:this.get("flipX"),flipY:this.get("flipY")};this.set("angle",0).set("flipX",!1).set("flipY",!1),this.toDataURL(function(e){n.src=e})}return this},toDataURL:function(e){function i(t){t.left=n.width/2,t.top=n.height/2,t.setActive(!1),r.add(t);var i=r.toDataURL("png");r.dispose(),r=t=null,e&&e(i)}var n=t.util.createCanvasElement();n.width=this.getBoundingRectWidth(),n.height=this.getBoundingRectHeight(),t.util.wrapElement(n,"div");var r=new t.Canvas(n);r.backgroundColor="transparent",r.renderAll(),this.constructor.async?this.clone(i):i(this.clone())},hasStateChanged:function(){return this.stateProperties.some(function(e){return this[e]!==this.originalState[e]},this)},saveState:function(){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){this.originalState={},this.saveState()},isType:function(e){return this.type===e},toGrayscale:function(){var e=this.get("fill");return e&&this.set("overlayFill",(new t.Color(e)).toGrayscale().toRgb()),this},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradientFill:function(e){this.set("fill",t.Gradient.forObject(this,e))},setPatternFill:function(e){this.set("fill",new t.Pattern(e))},setShadow:function(e){this.set("shadow",new t.Shadow(e))},animate:function(){if(arguments[0]&&typeof arguments[0]=="object")for(var e in arguments[0])this._animate(e,arguments[0][e],arguments[1]);else this._animate.apply(this,arguments);return this},_animate:function(e,n,r){var i=this,s;n=n.toString(),r?r=t.util.object.clone(r):r={},~e.indexOf(".")&&(s=e.split("."));var o=s?this.get(s[0])[s[1]]:this.get(e);"from"in r||(r.from= +o),~n.indexOf("=")?n=o+parseFloat(n.replace("=","")):n=parseFloat(n),t.util.animate({startValue:r.from,endValue:n,byValue:r.by,easing:r.easing,duration:r.duration,onChange:function(t){s?i[s[0]][s[1]]=t:i.set(e,t),r.onChange&&r.onChange()},onComplete:function(){i.setCoords(),r.onComplete&&r.onComplete()}})},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.centerH().centerV()},remove:function(){return this.canvas.remove(this)},sendToBack:function(){return this.canvas.sendToBack(this),this},bringToFront:function(){return this.canvas.bringToFront(this),this},sendBackwards:function(){return this.canvas.sendBackwards(this),this},bringForward:function(){return this.canvas.bringForward(this),this}});var f=t.Object.prototype;for(var l=f.stateProperties.length;l--;){var c=f.stateProperties[l],h=c.charAt(0).toUpperCase()+c.slice(1),p="set"+h,d="get"+h;f[d]||(f[d]=function(e){return new Function('return this.get("'+e+'")')}(c)),f[p]||(f[p]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(c))}t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2}(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;return n==="left"?i=t.x+this.getWidth()/2:n==="right"&&(i=t.x-this.getWidth()/2),r==="top"?s=t.y+this.getHeight()/2:r==="bottom"&&(s=t.y-this.getHeight()/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;return n==="left"?i=t.x-this.getWidth()/2:n==="right"&&(i=t.x+this.getWidth()/2),r==="top"?s=t.y-this.getHeight()/2:r==="bottom"&&(s=t.y+this.getHeight()/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){return this.translateToCenterPoint(new fabric.Point(this.left,this.top),this.originX,this.originY)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s,o;return n!==undefined&&r!==undefined?(n==="left"?s=i.x-this.getWidth()/2:n==="right"?s=i.x+this.getWidth()/2:s=i.x,r==="top"?o=i.y-this.getHeight()/2:r==="bottom"?o=i.y+this.getHeight()/2:o=i.y):(s=this.left,o=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(s,o))},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}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{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){return this.isContainedWithinRect(e.oCoords.tl,e.oCoords.br)},isContainedWithinRect: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);return r.x>e.x&&i.xe.y&&s.y1?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(this.currentHeight/this.currentWidth),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:c.x+this.currentWidth/2*f,y:c.y+this.currentWidth/2*a};return this.oCoords={tl:c,tr:h,br:p,bl:d,ml:v,mt:m,mr:g,mb:y,mtr:b},this._setCornerCoords(),this}})}(),function(){var e=fabric.util.getPointer,t=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{_findTargetCorner:function(t,n){if(!this.hasControls||!this.active)return!1;var r=e(t,this.canvas.upperCanvasEl),i=r.x-n.left,s=r.y-n.top,o,u;for(var a in this.oCoords){if(a==="mtr"&&!this.hasRotatingPoint)continue;if(!(!this.get("lockUniScaling")||a!=="mt"&&a!=="mr"&&a!=="mb"&&a!=="ml"))continue;u=this._getImageLines(this.oCoords[a].corner,a),o=this._findCrossPoints(i,s,u);if(o%2===1&&o!==0)return this.__corner=a,a}return!1},_findCrossPoints:function(e,t,n){var r,i,s,o,u,a,f=0,l;for(var c in n){l=n[c];if(l.o.y=t&&l.d.y>=t)continue;l.o.x===l.d.x&&l.o.x>=e?(u=l.o.x,a=t):(r=0,i=(l.d.y-l.o.y)/(l.d.x-l.o.x),s=t-r*e,o=l.o.y-i*l.o.x,u=-(s-o)/(r-i),a=s+r*u),u>=e&&(f+=1);if(f===2)break}return f},_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}}},_setCornerCoords:function(){var e=this.oCoords,n=t(this.angle),r=t(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);e.tl.corner={tl:{x:e.tl.x-o,y:e.tl.y-s},tr:{x:e.tl.x+s,y:e.tl.y-o},bl:{x:e.tl.x-s,y:e.tl.y+o},br:{x:e.tl.x+o,y:e.tl.y+s}},e.tr.corner={tl:{x:e.tr.x-o,y:e.tr.y-s},tr:{x:e.tr.x+s,y:e.tr.y-o},br:{x:e.tr.x+o,y:e.tr.y+s},bl:{x:e.tr.x-s,y:e.tr.y+o}},e.bl.corner={tl:{x:e.bl.x-o,y:e.bl.y-s},bl:{x:e.bl.x-s,y:e.bl.y+o},br:{x:e.bl.x+o,y:e.bl.y+s},tr:{x:e.bl.x+s,y:e.bl.y-o}},e.br.corner={tr:{x:e.br.x+s,y:e.br.y-o},bl:{x:e.br.x-s,y:e.br.y+o},br:{x:e.br.x+o,y:e.br.y+s},tl:{x:e.br.x-o,y:e.br.y-s}},e.ml.corner={tl:{x:e.ml.x-o,y:e.ml.y-s},tr:{x:e.ml.x+s,y:e.ml.y-o},bl:{x:e.ml.x-s,y:e.ml.y+o},br:{x:e.ml.x+o,y:e.ml.y+s}},e.mt.corner={tl:{x:e.mt.x-o,y:e.mt.y-s},tr:{x:e.mt.x+s,y:e.mt.y-o},bl:{x:e.mt.x-s,y:e.mt.y+o},br:{x:e.mt.x+o,y:e.mt.y+s}},e.mr.corner={tl:{x:e.mr.x-o,y:e.mr.y-s},tr:{x:e.mr.x+s,y:e.mr.y-o},bl:{x:e.mr.x-s,y:e.mr.y+o},br:{x:e.mr.x+o,y:e.mr.y+s}},e.mb.corner={tl:{x:e.mb.x-o,y:e.mb.y-s},tr:{x:e.mb.x+s,y:e.mb.y-o},bl:{x:e.mb.x-s,y:e.mb.y+o},br:{x:e.mb.x+o,y:e.mb.y+s}},e.mtr.corner={tl:{x:e.mtr.x-o+u*this.rotatingPointOffset,y:e.mtr.y-s-a*this.rotatingPointOffset},tr:{x:e.mtr.x+s+u*this.rotatingPointOffset,y:e.mtr.y-o-a*this.rotatingPointOffset},bl:{x:e.mtr.x-s+u*this.rotatingPointOffset,y:e.mtr.y+o-a*this.rotatingPointOffset},br:{x:e.mtr.x+o+u*this.rotatingPointOffset,y:e.mtr.y+s-a*this.rotatingPointOffset}}},drawBorders:function(e){if(!this.hasBorders)return;var t=this.padding,n=t*2,r=this.strokeWidth>1?this.strokeWidth:0;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),~~(u+n+r*this.scaleY));if(this.hasRotatingPoint&&!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},drawCorners:function(e){if(!this.hasControls)return;var t=this.cornerSize,n=t/2,r=this.strokeWidth/2,i=-(this.width/2),s=-(this.height/2),o,u,a=t/this.scaleX,f=t/this.scaleY,l=this.padding/this.scaleX,c=this.padding/this.scaleY,h=n/this.scaleY,p=n/this.scaleX,d=(n-t)/this.scaleX,v=(n-t)/this.scaleY,m=this.height,g=this.width,y=this.transparentCorners?"strokeRect":"fillRect",b=typeof G_vmlCanvasManager!="undefined";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,o=i-p-r-l,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g-p+r+l,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i-p-r-l,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g+d+r+l,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),this.get("lockUniScaling")||(o=i+g/2-p,u=s-h-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g/2-p,u=s+m+v+r+c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i+g+d+r+l,u=s+m/2-h,b||e.clearRect(o,u,a,f),e[y](o,u,a,f),o=i-p-r-l,u=s+m/2-h,b||e.clearRect(o,u,a,f),e[y](o,u,a,f)),this.hasRotatingPoint&&(o=i+g/2-p,u=this.flipY?s+m+this.rotatingPointOffset/this.scaleY-f/2+r+c:s-this.rotatingPointOffset/this.scaleY-f/2-r-c,b||e.clearRect(o,u,a,f),e[y](o,u,a,f)),e.restore(),this}})}(),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r={x1:1,x2:1,y1:1,y2:1};if(t.Line){t.warn("fabric.Line is already defined");return}t.Line=t.util.createClass(t.Object,{type:"line",initialize:function(e,t){t=t||{},e||(e=[0,0,0,0]),this.callSuper("initialize",t),this.set("x1",e[0]),this.set("y1",e[1]),this.set("x2",e[2]),this.set("y2",e[3]),this._setWidthHeight(t)},_setWidthHeight:function(e){e||(e={}),this.set("width",this.x2-this.x1||1),this.set("height",this.y2-this.y1||1),this.set("left","left"in e?e.left:this.x1+this.width/2),this.set("top","top"in e?e.top:this.y1+this.height/2)},_set:function(e,t){return this[e]=t,e in r&&this._setWidthHeight(),this},_render:function(e){e.beginPath(),this.group&&e.translate(-this.group.width/2+this.left,-this.group.height/2+this.top),e.moveTo(this.width===1?0:-this.width/2,this.height===1?0:-this.height/2),e.lineTo(this.width===1?0:this.width/2,this.height===1?0:this.height/2),e.lineWidth=this.strokeWidth;var t=e.strokeStyle;e.strokeStyle=e.fillStyle,e.stroke(),e.strokeStyle=t},complexity:function(){return 1},toObject:function(e){return n(this.callSuper("toObject",e),{x1:this.get("x1"),y1:this.get("y1"),x2:this.get("x2"),y2:this.get("y2")})},toSVG:function(){return[""].join("")}}),t.Line.ATTRIBUTE_NAMES="x1 y1 x2 y2 stroke stroke-width transform".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",initialize:function(e){e=e||{},this.set("radius",e.radius||0),this.callSuper("initialize",e);var t=this.get("radius")*2;this.set("width",t).set("height",t)},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius")})},toSVG:function(){return'"},_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.fill&&e.fill(),this._removeShadow(e),this.stroke&&e.stroke()},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="cx cy r fill fill-opacity opacity stroke stroke-width transform".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.fill&&e.fill(),this.stroke&&e.stroke()},complexity:function(){return 1},toSVG:function(){var e=this.width/2,t=this.height/2,n=[-e+" "+t,"0 "+ -t,e+" "+t].join(",");return'"}}),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",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(){return[""].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),this.stroke&&e.stroke(),this._removeShadow(e),this.fill&&e.fill(),e.restore()},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES="cx cy rx ry fill fill-opacity opacity stroke stroke-width transform".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 r(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}t.Rect=t.util.createClass(t.Object,{type:"rect",rx:0,ry:0,initialize:function(e){e=e||{},this._initStateProperties(),this.callSuper("initialize",e),this._initRxRy(),this.x=0,this.y=0},_initStateProperties:function(){this.stateProperties=this.stateProperties.concat(["rx","ry"])},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e){var t=this.rx||0,n=this.ry||0,r=-this.width/2,i=-this.height/2,s=this.width,o=this.height;e.beginPath(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&this.group&&e.translate(this.width/2+this.x,this.height/2+this.y),!this.transformMatrix&&this.group&&e.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y),e.moveTo(r+t,i),e.lineTo(r+s-t,i),e.quadraticCurveTo(r+s,i,r+s,i+n,r+s,i+n),e.lineTo(r+s,i+o-n),e.quadraticCurveTo(r+s,i+o,r+s-t,i+o,r+s-t,i+o),e.lineTo(r+t,i+o),e.quadraticCurveTo(r,i+o,r,i+o-n,r,i+o-n),e.lineTo(r,i+n),e.quadraticCurveTo(r,i,r+t,i,r+t,i),e.closePath(),this.fill&&e.fill(),this._removeShadow(e),this.strokeDashArray?this._renderDashedStroke(e):this.stroke&&e.stroke()},_renderDashedStroke:function(e){function u(u,a){var f=0,l=0,c=(a?i.height:i.width)+s*2;while(fc&&(l=f-c),u?n+=h*u-(l*u||0):r+=h*a-(l*a||0),e[1&t?"moveTo":"lineTo"](n,r),t>=o&&(t=0)}}1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray);var t=0,n=-this.width/2,r=-this.height/2,i=this,s=this.padding,o=this.strokeDashArray.length;e.save(),e.beginPath(),u(1,0),u(0,1),u(-1,0),u(0,-1),e.stroke(),e.closePath(),e.restore()},_normalizeLeftTopProperties:function(e){return e.left&&this.set("left",e.left+this.getWidth()/2),this.set("x",e.left||0),e.top&&this.set("top",e.top+this.getHeight()/2),this.set("y",e.top||0),this},complexity:function(){return 1},toObject:function(e){return n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0})},toSVG:function(){return'"}}),t.Rect.ATTRIBUTE_NAMES="x y width height rx ry fill fill-opacity opacity stroke stroke-width transform".split(" "),t.Rect.fromElement=function(e,i){if(!e)return null;var s=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);s=r(s);var o=new t.Rect(n(i?t.util.object.clone(i):{},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",initialize:function(e,t){t=t||{},this.set("points",e),this.callSuper("initialize",t),this._calcDimensions()},_calcDimensions:function(){return t.Polygon.prototype._calcDimensions.call(this)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(){var e=[];for(var t=0,r=this.points.length;t"].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"].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;n1&&(g=Math.sqrt(g),n*=g,i*=g);var y=d/n,b=p/n,w=-p/i,E=d/i,S=y*l+b*c,x=w*l+E*c,T=y*e+b*t,N=w*e+E*t,C=(T-S)*(T-S)+(N-x)*(N-x),k=1/C-.25;k<0&&(k=0);var L=Math.sqrt(k);a===u&&(L=-L);var A=.5*(S+T)-L*(N-x),O=.5*(x+N)+L*(T-S),M=Math.atan2(x-O,S-A),_=Math.atan2(N-O,T-A),D=_-M;D<0&&a===1?D+=2*Math.PI:D>0&&a===0&&(D-=2*Math.PI);var P=Math.ceil(Math.abs(D/(Math.PI*.5+.001))),H=[];for(var B=0;B"},toObject:function(e){var t=h(this.callSuper("toObject",e),{path:this.path});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(){var e=[];for(var t=0,n=this.path.length;t',"',""].join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],n,r,i;for(var s=0,o,u=this.path.length;sc)for(var h=1,p=o.length;h"];for(var n=0,r=e.length;n"),t.join("")},toString:function(){return"#"},isSameColor:function(){var e=this.getObjects()[0].get("fill");return this.getObjects().every(function(t){return t.get("fill")===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},toGrayscale:function(){var e=this.paths.length;while(e--)this.paths[e].toGrayscale();return this},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e){var n=u(e.paths);return new t.PathGroup(n,e)}}(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,o=t.util.removeFromArray;if(t.Group)return;var u={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,{type:"group",initialize:function(e,t){t=t||{},this.objects=e||[],this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this._setOpacityIfSame(),this.setCoords(!0),this.saveCoords()},_updateObjectsCoords:function(){var e=this.left,t=this.top;this.forEachObject(function(n){var r=n.get("left"),i=n.get("top");n.set("originalLeft",r),n.set("originalTop",i),n.set("left",r-e),n.set("top",i-t),n.setCoords(),n.hideCorners=!0},this)},toString:function(){return"#"},getObjects:function(){return this.objects},addWithUpdate:function(e){return this._restoreObjectsState(),this.objects.push(e),this._calcBounds(),this._updateObjectsCoords(),this},removeWithUpdate:function(e){return this._restoreObjectsState(),o(this.objects,e),e.setActive(!1),this._calcBounds(),this._updateObjectsCoords(),this},add:function(e){return this.objects.push(e),this},remove:function(e){return o(this.objects,e),this},size:function(){return this.getObjects().length},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,lineHeight:!0,textDecoration:!0,textShadow:!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},contains:function(e){return this.objects.indexOf(e)>-1},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this.objects,"toObject",e)})},render:function(e,t){e.save(),this.transform(e);var n=Math.max(this.scaleX,this.scaleY);for(var r=this.objects.length;r>0;r--){var i=this.objects[r-1],s=i.borderScaleFactor,o=i.hasRotatingPoint;i.borderScaleFactor=n,i.hasRotatingPoint=!1,i.render(e),i.borderScaleFactor=s,i.hasRotatingPoint=o}!t&&this.active&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore(),this.setCoords()},item:function(e){return this.getObjects()[e]},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=typeof t.complexity=="function"?t.complexity():0,e},0)},_restoreObjectsState:function(){return this.objects.forEach(this._restoreObjectState,this),this},_restoreObjectState:function(e){var t=this.get("left"),n=this.get("top"),r=this.getAngle()*(Math.PI/180),i=Math.cos(r)*e.get("top")+Math.sin(r)*e.get("left"),s=-Math.sin(r)*e.get("top")+Math.cos(r)*e.get("left");return e.setAngle(e.getAngle()+this.getAngle()),e.set("left",t+s*this.get("scaleX")),e.set("top",n+i*this.get("scaleY")),e.set("scaleX",e.get("scaleX")*this.get("scaleX")),e.set("scaleY",e.get("scaleY")*this.get("scaleY")),e.setCoords(),e.hideCorners=!1,e.setActive(!1),e.setCoords(),this},destroy:function(){return this._restoreObjectsState()},saveCoords:function(){return this._originalLeft=this.get("left"),this._originalTop=this.get("top"),this},hasMoved:function(){return this._originalLeft!==this.get("left")||this._originalTop!==this.get("top")},setObjectsCoords:function(){return this.forEachObject(function(e){e.setCoords()}),this},activateAllObjects:function(){return this.forEachObject(function(e){e.setActive()}),this},forEachObject:t.StaticCanvas.prototype.forEachObject,_setOpacityIfSame:function(){var e=this.getObjects(),t=e[0]?e[0].get("opacity"):1,n=e.every(function(e){return e.get("opacity")===t});n&&(this.opacity=t)},_calcBounds:function(){var e=[],t=[],n,s,o,u,a,f,l,c=0,h=this.objects.length;for(;ce.x&&i-ne.y},toGrayscale:function(){var e=this.objects.length;while(e--)this.objects[e].toGrayscale();return this},toSVG:function(){var e=[];for(var t=0,n=this.objects.length;t'+e.join("")+""},get:function(e){if(e in u){if(this[e])return this[e];for(var t=0,n=this.objects.length;t'+'"+""},getSrc:function(){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.filters.length===0){this.setElement(this._originalImage),e&&e();return}var t=typeof Buffer!="undefined"&&typeof window=="undefined",n=this._originalImage,r=fabric.util.createCanvasElement(),i=t?new(require("canvas").Image):fabric.document.createElement("img"),s=this;r.width=n.width,r.height=n.height,r.getContext("2d").drawImage(n,0,0,n.width,n.height),this.filters.forEach(function(e){e&&e.applyTo(r)}),i.onload=function(){s._element=i,e&&e(),i.onload=r=n=null},i.width=n.width,i.height=n.height;if(t){var o=r.toDataURL("image/png").substring(22);i.src=new Buffer(o,"base64"),s._element=i,e&&e()}else i.src=r.toDataURL("image/png");return this},_render:function(e){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)},_initFilters:function(e){e.filters&&e.filters.length&&(this.filters=e.filters.map(function(e){return e&&fabric.Image.filters[e.type].fromObject(e)}))},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement().width||0,this.height="height"in e?e.height:this.getElement().height||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){var n=fabric.document.createElement("img"),r=e.src;e.width&&(n.width=e.width),e.height&&(n.height=e.height),n.onload=function(){fabric.Image.prototype._initFilters.call(e,e);var r=new fabric.Image(n,e);t&&t(r),n=n.onload=n.onerror=null},n.onerror=function(){fabric.log("Error loading "+n.src),t&&t(null,!0),n=n.onload=n.onerror=null},n.src=r},fabric.Image.fromURL=function(e,t,n){var r=fabric.document.createElement("img");r.onload=function(){t&&t(new fabric.Image(r,n)),r=r.onload=null},r.src=e},fabric.Image.ATTRIBUTE_NAMES="x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href".split(" "),fabric.Image.fromElement=function(e,n,r){r||(r={});var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(i,r))},fabric.Image.async=!0}(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.setActive(!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.Grayscale=fabric.util.createClass({type:"Grayscale",applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=n.width,s=n.height,o,u,a,f;for(a=0;ao&&f>o&&l>o&&u(a-f)0&&(r[s]=a,r[s+1]=f,r[s+2]=l);t.putImageData(n,0,0)},toJSON:function(){return{type:this.type,color:this.color}}}),fabric.Image.filters.Tint.fromObject=function(e){return new fabric.Image.filters.Tint(e)},fabric.Image.filters.Convolute=fabric.util.createClass({type:"Convolute",initialize:function(e){e||(e={}),this.opaque=e.opaque,this.matrix=e.matrix||[0,0,0,0,1,0,0,0,0];var t=fabric.util.createCanvasElement();this.tmpCtx=t.getContext("2d")},_createImageData:function(e,t){return this.tmpCtx.createImageData(e,t)},applyTo:function(e){var t=this.matrix,n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=Math.round(Math.sqrt(t.length)),s=Math.floor(i/2),o=r.data,u=r.width,a=r.height,f=u,l=a,c=this._createImageData(f,l),h=c.data,p=this.opaque?1:0;for(var d=0;d=0&&N=0&&C'},_render:function(e){typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaCufon:function(e){var t=Cufon.textOptions||(Cufon.textOptions={});t.left=this.left,t.top=this.top,t.context=e,t.color=this.fill;var n=this._initDummyElementForCufon();this.transform(e),Cufon.replaceElement(n,{engine:"canvas",separate:"none",fontFamily:this.fontFamily,fontWeight:this.fontWeight,textDecoration:this.textDecoration,textShadow:this.textShadow,textAlign:this.textAlign,fontStyle:this.fontStyle,lineHeight:this.lineHeight,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor}),this.width=t.width,this.height=t.height,this._totalLineHeight=t.totalLineHeight,this._fontAscent=t.fontAscent,this._boundaries=t.boundaries,this._shadowOffsets=t.shadowOffsets,this._shadows=t.shadows||[],n=null,this.setCoords()},_renderViaNative:function(e){this.transform(e),this._setTextStyles(e);var t=this.text.split(/\r?\n/);this.width=this._getTextWidth(e,t),this.height=this._getTextHeight(e,t),this._renderTextBackground(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0)),this._setTextShadow(e),this._renderTextFill(e,t),this.textShadow&&e.restore(),this._renderTextStroke(e,t),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,t),this._setBoundaries(e,t),this._totalLineHeight=0,this.setCoords()},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_setTextShadow:function(e){if(this.textShadow){var t=/\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/,n=this.textShadow,r=t.exec(this.textShadow),i=n.replace(t,"");e.save(),e.shadowColor=i,e.shadowOffsetX=parseInt(r[1],10),e.shadowOffsetY=parseInt(r[2],10),e.shadowBlur=parseInt(r[3],10),this._shadows=[{blur:e.shadowBlur,color:e.shadowColor,offX:e.shadowOffsetX,offY:e.shadowOffsetY}],this._shadowOffsets=[[parseInt(e.shadowOffsetX,10),parseInt(e.shadowOffsetY,10)]]}},_drawTextLine:function(e,t,n,r,i){if(this.textAlign!=="justify"){t[e](n,r,i);return}var s=t.measureText(n).width,o=this.width;if(o>s){var u=n.split(/\s+/),a=t.measureText(n.replace(/\s+/g,"")).width,f=o-a,l=u.length-1,c=f/l,h=0;for(var p=0,d=u.length;p-1&&i(this.fontSize),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(0)},_getFontDeclaration:function(){return[this.fontStyle,this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},_initDummyElementForCufon:function(){var e=t.document.createElement("pre"),n=t.document.createElement("div");return n.appendChild(e),typeof G_vmlCanvasManager=="undefined"?e.innerHTML=this.text:e.innerText=this.text.replace(/\r?\n/gi,"\r"),e.style.fontSize=this.fontSize+"px",e.style.letterSpacing="normal",e},render:function(e,t){e.save(),this._render(e),!t&&this.active&&(this.drawBorders(e),this.hideCorners||this.drawCorners(e)),e.restore()},toObject:function(e){return 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,textShadow:this.textShadow,textAlign:this.textAlign,path:this.path,strokeStyle:this.strokeStyle,strokeWidth:this.strokeWidth,backgroundColor:this.backgroundColor,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative})},toSVG:function(){var e=this.text.split(/\r?\n/),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,s=this._getSVGTextAndBg(t,n,e),o=this._getSVGShadows(t,e);return r+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,['',s.textBgRects.join(""),"',o.join(""),s.textSpans.join(""),"",""].join("")},_getSVGShadows:function(e,n){var r=[],s,o,u,a,f=1;if(!this._shadows||!this._boundaries)return r;for(s=0,u=this._shadows.length;s",t.util.string.escapeXml(n[o]),""),f=1}else f++;return r},_getSVGTextAndBg:function(e,n,r){var s=[],o=[],u,a,f,l=1;this.backgroundColor&&this._boundaries&&o.push("');for(u=0,f=r.length;u",t.util.string.escapeXml(r[u]),""),l=1):l++;if(!this.textBackgroundColor||!this._boundaries)continue;o.push("')}return{textSpans:s,textBgRects:o}},_getFillAttributes:function(e){var n=e?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},setColor:function(e){return this.set("fill",e),this},setFontsize:function(e){return this.set("fontSize",e),this._initDimensions(),this.setCoords(),this},getText:function(){return this.text},setText:function(e){return this.set("text",e),this._initDimensions(),this.setCoords(),this},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t)}}),t.Text.ATTRIBUTE_NAMES="x y fill fill-opacity opacity stroke stroke-width transform font-family font-style font-weight font-size text-decoration".split(" "),t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},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);var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i}}(typeof exports!="undefined"?exports:this),function(){function request(e,t,n){var r=URL.parse(e),i=HTTP.createClient(r.port,r.hostname),s=i.request("GET",r.pathname,{host:r.hostname});i.addListener("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+i.host+":"+i.port):fabric.log(e.message)}),s.end(),s.on("response",function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=(new require("xmldom")).DOMParser,URL=require("url"),HTTP=require("http"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){var r=new Image;e&&e.indexOf("data")===0?(r.src=r._src=e,t&&t.call(n,r)):e&&request(e,"binary",function(i){r.src=new Buffer(i,"binary"),r._src=e,t&&t.call(n,r)})},fabric.loadSVGFromURL=function(e,t){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),request(e,"",function(e){fabric.loadSVGFromString(e,t)})},fabric.loadSVGFromString=function(e,t){var n=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(n.documentElement,function(e,n){t(e,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),t(r)})},fabric.createCanvasForNode=function(e,t){var n=fabric.document.createElement("canvas"),r=new Canvas(e||600,t||600);n.style={},n.width=r.width,n.height=r.height;var i=fabric.Canvas||fabric.StaticCanvas,s=new i(n);return s.contextContainer=r.getContext("2d"),s.nodeCanvas=r,s},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),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),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); +>>>>>>> master diff --git a/lib/aligning_guidelines.js b/lib/aligning_guidelines.js index a4dad1de..289240b9 100644 --- a/lib/aligning_guidelines.js +++ b/lib/aligning_guidelines.js @@ -6,7 +6,6 @@ function initAligningGuidelines(canvas) { var ctx = canvas.getSelectionContext(), - canvasHeight = canvas.getHeight(), aligningLineOffset = 5, aligningLineMargin = 4, aligningLineWidth = 1, @@ -57,11 +56,16 @@ function initAligningGuidelines(canvas) { var activeObject = e.target, canvasObjects = canvas.getObjects(), - activeObjectLeft = activeObject.get('left'), - activeObjectTop = activeObject.get('top'), - activeObjectHeight = activeObject.getHeight(), - activeObjectWidth = activeObject.getWidth(), - noneInTheRange = true; + activeObjectCenter = activeObject.getCenterPoint(), + activeObjectLeft = activeObjectCenter.x, + activeObjectTop = activeObjectCenter.y, + activeObjectHeight = activeObject.getBoundingRectHeight(), + activeObjectWidth = activeObject.getBoundingRectWidth(), + horizontalInTheRange = false, + verticalInTheRange = false, + transform = canvas._currentTransform; + + if (!transform) return; // It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions, // but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move @@ -70,14 +74,15 @@ function initAligningGuidelines(canvas) { if (canvasObjects[i] === activeObject) continue; - var objectLeft = canvasObjects[i].get('left'), - objectTop = canvasObjects[i].get('top'), - objectHeight = canvasObjects[i].getHeight(), - objectWidth = canvasObjects[i].getWidth(); + var objectCenter = canvasObjects[i].getCenterPoint(), + objectLeft = objectCenter.x, + objectTop = objectCenter.y, + objectHeight = canvasObjects[i].getBoundingRectHeight(), + objectWidth = canvasObjects[i].getBoundingRectWidth(); // snap by the horizontal center line if (isInRange(objectLeft, activeObjectLeft)) { - noneInTheRange = false; + verticalInTheRange = true; verticalLines.push({ x: objectLeft, y1: (objectTop < activeObjectTop) @@ -87,12 +92,12 @@ function initAligningGuidelines(canvas) { ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) }); - activeObject.set('left', objectLeft); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), transform.originX, transform.originY); } // snap by the left edge if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) { - noneInTheRange = false; + verticalInTheRange = true; verticalLines.push({ x: objectLeft - objectWidth / 2, y1: (objectTop < activeObjectTop) @@ -102,12 +107,12 @@ function initAligningGuidelines(canvas) { ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) }); - activeObject.set('left', objectLeft - objectWidth / 2 + activeObjectWidth / 2); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), transform.originX, transform.originY); } // snap by the right edge if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) { - noneInTheRange = false; + verticalInTheRange = true; verticalLines.push({ x: objectLeft + objectWidth / 2, y1: (objectTop < activeObjectTop) @@ -117,12 +122,12 @@ function initAligningGuidelines(canvas) { ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) }); - activeObject.set('left', objectLeft + objectWidth / 2 - activeObjectWidth / 2); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), transform.originX, transform.originY); } // snap by the vertical center line if (isInRange(objectTop, activeObjectTop)) { - noneInTheRange = false; + horizontalInTheRange = true; horizontalLines.push({ y: objectTop, x1: (objectLeft < activeObjectLeft) @@ -132,12 +137,12 @@ function initAligningGuidelines(canvas) { ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) }); - activeObject.set('top', objectTop); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), transform.originX, transform.originY); } // snap by the top edge if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) { - noneInTheRange = false; + horizontalInTheRange = true; horizontalLines.push({ y: objectTop - objectHeight / 2, x1: (objectLeft < activeObjectLeft) @@ -147,12 +152,12 @@ function initAligningGuidelines(canvas) { ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) }); - activeObject.set('top', objectTop - objectHeight / 2 + activeObjectHeight / 2); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), transform.originX, transform.originY); } // snap by the bottom edge if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) { - noneInTheRange = false; + horizontalInTheRange = true; horizontalLines.push({ y: objectTop + objectHeight / 2, x1: (objectLeft < activeObjectLeft) @@ -162,13 +167,21 @@ function initAligningGuidelines(canvas) { ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) }); - activeObject.set('top', objectTop + objectHeight / 2 - activeObjectHeight / 2); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), transform.originX, transform.originY); } } - if (noneInTheRange) { - verticalLines.length = horizontalLines.length = 0; + if (!horizontalInTheRange) { + horizontalLines.length = 0; } + + if (!verticalInTheRange) { + verticalLines.length = 0; + } + }); + + canvas.on('before:render', function() { + canvas.clearContext(canvas.contextTop); }); canvas.on('after:render', function() { @@ -184,4 +197,4 @@ function initAligningGuidelines(canvas) { verticalLines.length = horizontalLines.length = 0; canvas.renderAll(); }); -} \ No newline at end of file +} diff --git a/lib/centering_guidelines.js b/lib/centering_guidelines.js index c23f7bbe..91d6f965 100644 --- a/lib/centering_guidelines.js +++ b/lib/centering_guidelines.js @@ -48,19 +48,24 @@ function initCenteringGuidelines(canvas) { isInHorizontalCenter; canvas.on('object:moving', function(e) { - object = e.target; + var object = e.target, + objectCenter = object.getCenterPoint(), + transform = canvas._currentTransform; - isInVerticalCenter = object.get('left') in canvasWidthCenterMap, - isInHorizontalCenter = object.get('top') in canvasHeightCenterMap; + if (!transform) return; - if (isInHorizontalCenter) { - object.set('top', canvasHeightCenter); - } - if (isInVerticalCenter) { - object.set('left', canvasWidthCenter); + isInVerticalCenter = objectCenter.x in canvasWidthCenterMap, + isInHorizontalCenter = objectCenter.y in canvasHeightCenterMap; + + if (isInHorizontalCenter || isInVerticalCenter) { + object.setPositionByOrigin(new fabric.Point((isInVerticalCenter ? canvasWidthCenter : objectCenter.x), (isInHorizontalCenter ? canvasHeightCenter : objectCenter.y)), transform.originX, transform.originY); } }); + canvas.on('before:render', function() { + canvas.clearContext(canvas.contextTop); + }); + canvas.on('after:render', function() { if (isInVerticalCenter) { showVerticalCenterLine(); diff --git a/lib/yuicompressor-2.4.2.jar b/lib/yuicompressor-2.4.2.jar deleted file mode 100644 index c29470bd..00000000 Binary files a/lib/yuicompressor-2.4.2.jar and /dev/null differ diff --git a/package.json b/package.json index 0549acd8..80affc06 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fabric", "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", - "version": "1.0.0", + "version": "1.0.6", "author": "Juriy Zaytsev ", "keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"], "repository": "git://github.com/kangax/fabric.js", @@ -10,19 +10,19 @@ "url": "http://github.com/kangax/fabric.js/raw/master/LICENSE" }], "scripts": { - "build": "node build.js modules=ALL exclude=json,cufon", + "build": "node build.js modules=ALL exclude=json,cufon,gestures", "test": "node test.js" }, "dependencies": { - "canvas": "~0.13.0", + "canvas": "~1.0.0", "jsdom": ">=0.2.3", "xmldom": ">=0.1.7" }, "devDependencies": { "qunit": "0.5.x", "jshint": "0.9.x", - "uglify-js": "1.3.x" + "uglify-js": ">=2.0.0" }, - "engines": { "node": ">=0.4.0 && <0.9.0" }, + "engines": { "node": ">=0.4.0 && <1.0.0" }, "main": "./dist/all.js" } \ No newline at end of file diff --git a/src/canvas.class.js b/src/canvas.class.js index e7d1ab29..ef2dd065 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -2,22 +2,8 @@ var extend = fabric.util.object.extend, getPointer = fabric.util.getPointer, - addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, - cursorMap = { - 'tr': 'ne-resize', - 'br': 'se-resize', - 'bl': 'sw-resize', - 'tl': 'nw-resize', - 'ml': 'w-resize', - 'mt': 'n-resize', - 'mr': 'e-resize', - 'mb': 's-resize' - }, - - sqrt = Math.sqrt, atan2 = Math.atan2, abs = Math.abs, min = Math.min, @@ -178,418 +164,6 @@ this.calcOffset(); }, - /** - * Adds mouse listeners to canvas - * @method _initEvents - * @private - * See configuration documentation for more details. - */ - _initEvents: function () { - var _this = this; - - this._onMouseDown = function (e) { - _this.__onMouseDown(e); - - addListener(fabric.document, 'mouseup', _this._onMouseUp); - fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp); - - addListener(fabric.document, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove); - - removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); - }; - - this._onMouseUp = function (e) { - _this.__onMouseUp(e); - - removeListener(fabric.document, 'mouseup', _this._onMouseUp); - fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp); - - removeListener(fabric.document, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove); - - addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); - fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); - }; - - this._onMouseMove = function (e) { - e.preventDefault && e.preventDefault(); - _this.__onMouseMove(e); - }; - - this._onResize = function () { - _this.calcOffset(); - }; - - - addListener(fabric.window, 'resize', this._onResize); - - if (fabric.isTouchSupported) { - addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof Event !== 'undefined' && 'add' in Event) { - Event.add(this.upperCanvasEl, 'gesture', function(e, s) { - _this.__onTransformGesture(e, s); - }); - } - } - else { - addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - } - }, - - /** - * 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. - * @method __onMouseUp - * @param {Event} e Event object fired on mouseup - * - */ - __onMouseUp: function (e) { - - var target; - - if (this.isDrawingMode && this._isCurrentlyDrawing) { - this._isCurrentlyDrawing = false; - this.freeDrawingBrush.onMouseUp(); - this.fire('mouse:up', { e: e }); - return; - } - - if (this._currentTransform) { - - var transform = this._currentTransform; - - target = transform.target; - if (target._scaling) { - target._scaling = false; - } - - // determine the new coords everytime the image changes its position - var i = this._objects.length; - while (i--) { - this._objects[i].setCoords(); - } - - target.isMoving = false; - - // 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'); - } - - if (this._previousOriginX) { - this._adjustPosition(this._currentTransform.target, this._previousOriginX); - this._previousOriginX = null; - } - } - - this._currentTransform = null; - - if (this._groupSelector) { - // group selection was completed, determine its bounds - this._findSelectedObjects(e); - } - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.setObjectsCoords(); - activeGroup.set('isMoving', false); - this._setCursor(this.defaultCursor); - } - - // clear selection - this._groupSelector = null; - this.renderAll(); - - this._setCursorFromEvent(e, target); - - // fix for FF - this._setCursor(''); - - var _this = this; - setTimeout(function () { - _this._setCursorFromEvent(e, target); - }, 50); - - this.fire('mouse:up', { target: target, e: e }); - target && target.fire('mouseup', { 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. - * @method __onMouseDown - * @param e {Event} Event object fired on mousedown - * - */ - __onMouseDown: function (e) { - - var pointer; - - // accept only left clicks - var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; - if (!isLeftClick && !fabric.isTouchSupported) return; - - if (this.isDrawingMode) { - pointer = this.getPointer(e); - - this._isCurrentlyDrawing = true; - this.discardActiveObject().renderAll(); - - this.freeDrawingBrush.onMouseDown(pointer); - this.fire('mouse:down', { e: e }); - return; - } - - // ignore if some object is being transformed at this moment - if (this._currentTransform) return; - - var target = this.findTarget(e), corner; - pointer = this.getPointer(e); - - if (this._shouldClearSelection(e)) { - this._groupSelector = { - ex: pointer.x, - ey: pointer.y, - top: 0, - left: 0 - }; - this.deactivateAllWithDispatch(); - } - else { - // determine if it's a drag or rotate case - this.stateful && target.saveState(); - - if ((corner = target._findTargetCorner(e, this._offset))) { - this.onBeforeScaleRotate(target); - } - - if (this._shouldHandleGroupLogic(e, target)) { - this._handleGroupLogic(e, target); - target = this.getActiveGroup(); - } - else { - if (target !== this.getActiveGroup()) { - this.deactivateAll(); - } - this.setActiveObject(target, e); - } - - this._setupCurrentTransform(e, target); - } - // we must renderAll so that active image is placed on the top canvas - this.renderAll(); - - this.fire('mouse:down', { target: target, e: e }); - target && target.fire('mousedown', { e: e }); - - // center origin when rotating - if (corner === 'mtr') { - this._previousOriginX = this._currentTransform.target.originX; - this._adjustPosition(this._currentTransform.target, 'center'); - this._currentTransform.left = this._currentTransform.target.left; - this._currentTransform.top = this._currentTransform.target.top; - } - }, - - /** - * @method _shouldHandleGroupLogic - * @param e {Event} - * @param target {fabric.Object} - * @return {Boolean} - */ - _shouldHandleGroupLogic: function(e, target) { - var activeObject = this.getActiveObject(); - return e.shiftKey && - (this.getActiveGroup() || (activeObject && activeObject !== target)) - && this.selection; - }, - - /** - * 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. - * @method __onMouseMove - * @param e {Event} Event object fired on mousemove - * - */ - __onMouseMove: function (e) { - - var target, pointer; - - if (this.isDrawingMode) { - if (this._isCurrentlyDrawing) { - pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); - } - this.upperCanvasEl.style.cursor = this.freeDrawingCursor; - this.fire('mouse:move', { e: e }); - return; - } - - var groupSelector = this._groupSelector; - - // We initially clicked in an empty area, so we draw a box for multiple selection. - if (groupSelector !== null) { - pointer = getPointer(e); - - groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; - groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; - this.renderTop(); - } - else if (!this._currentTransform) { - - // alias style to elimintate unnecessary lookup - var style = this.upperCanvasEl.style; - - // Here we are hovering the canvas then we will determine - // what part of the pictures we are hovering to change the caret symbol. - // We won't do that while dragging or rotating in order to improve the - // performance. - target = this.findTarget(e); - - if (!target) { - // image/text was hovered-out from, we remove its borders - for (var i = this._objects.length; i--; ) { - if (this._objects[i] && !this._objects[i].active) { - this._objects[i].setActive(false); - } - } - style.cursor = this.defaultCursor; - } - else { - // set proper cursor - this._setCursorFromEvent(e, target); - } - } - else { - // object is being transformed (scaled/rotated/moved/etc.) - pointer = getPointer(e); - - var x = pointer.x, - y = pointer.y; - - this._currentTransform.target.isMoving = true; - - var t = this._currentTransform, reset = false; - if ( - (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') - && - ( - // Switch from a normal resize to center-based - (e.altKey && (t.originX !== 'center' || t.originY !== 'center')) - || - // Switch from center-based resize to normal one - (!e.altKey && t.originX === 'center' && t.originY === 'center') - ) - ) { - this._resetCurrentTransform(e); - reset = true; - } - - if (this._currentTransform.action === 'rotate') { - this._rotateObject(x, y); - - this.fire('object:rotating', { - target: this._currentTransform.target - }); - this._currentTransform.target.fire('rotating'); - } - else if (this._currentTransform.action === 'scale') { - // rotate object only if shift key is not pressed - // and if it is not a group we are transforming - - // TODO - /*if (!e.shiftKey) { - this._rotateObject(x, y); - - this.fire('object:rotating', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('rotating'); - }*/ - - // if (!this._currentTransform.target.hasRotatingPoint) { - // this._scaleObject(x, y); - // this.fire('object:scaling', { - // target: this._currentTransform.target - // }); - // this._currentTransform.target.fire('scaling'); - // } - - if (e.shiftKey || this.uniScaleTransform) { - this._currentTransform.currentAction = 'scale'; - this._scaleObject(x, y); - } - else { - if (!reset && t.currentAction === 'scale') { - // Switch from a normal resize to proportional - this._resetCurrentTransform(e); - } - - this._currentTransform.currentAction = 'scaleEqually'; - this._scaleObject(x, y, 'equally'); - } - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - } - // else if (this._currentTransform.action === 'scale') { - // this._scaleObject(x, y); - // this.fire('object:scaling', { - // target: this._currentTransform.target - // }); - // this._currentTransform.target.fire('scaling'); - // } - else if (this._currentTransform.action === 'scaleX') { - this._scaleObject(x, y, 'x'); - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('scaling', { e: e }); - } - else if (this._currentTransform.action === 'scaleY') { - this._scaleObject(x, y, 'y'); - - this.fire('object:scaling', { - target: this._currentTransform.target, - e: e - }); - this._currentTransform.target.fire('scaling', { e: e }); - } - else { - this._translateObject(x, y); - - this.fire('object:moving', { - target: this._currentTransform.target, - e: e - }); - - this._setCursor(this.moveCursor); - - this._currentTransform.target.fire('moving', { e: e }); - } - // only commit here. when we are actually moving the pictures - this.renderAll(); - } - this.fire('mouse:move', { target: target, e: e }); - target && target.fire('mousemove', { e: e }); - }, - /** * Resets the current transform to its original values and chooses the type of resizing based on the event * @method _resetCurrentTransform @@ -597,6 +171,7 @@ */ _resetCurrentTransform: function(e) { var t = this._currentTransform; + t.target.set('scaleX', t.original.scaleX); t.target.set('scaleY', t.original.scaleY); t.target.set('left', t.original.left); @@ -752,7 +327,7 @@ _setupCurrentTransform: function (e, target) { var action = 'drag', corner, - pointer = getPointer(e); + pointer = getPointer(e, target.canvas.upperCanvasEl); corner = target._findTargetCorner(e, this._offset); if (corner) { @@ -818,6 +393,19 @@ this._resetCurrentTransform(e); }, + /** + * @method _shouldHandleGroupLogic + * @param e {Event} + * @param target {fabric.Object} + * @return {Boolean} + */ + _shouldHandleGroupLogic: function(e, target) { + var activeObject = this.getActiveObject(); + return e.shiftKey && + (this.getActiveGroup() || (activeObject && activeObject !== target)) + && this.selection; + }, + /** * @private * @method _handleGroupLogic @@ -1019,43 +607,6 @@ target.setAngle(0); }, - /** - * Sets the cursor depending on where the canvas is being hovered. - * Note: very buggy in Opera - * @method _setCursorFromEvent - * @param e {Event} Event object - * @param target {Object} Object that the mouse is hovering, if so. - */ - _setCursorFromEvent: function (e, target) { - var s = this.upperCanvasEl.style; - if (!target) { - s.cursor = this.defaultCursor; - return false; - } - else { - var activeGroup = this.getActiveGroup(); - // only show proper corner when group selection is not active - var corner = !!target._findTargetCorner - && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(e, this._offset); - - if (!corner) { - s.cursor = this.hoverCursor; - } - else { - if (corner in cursorMap) { - s.cursor = cursorMap[corner]; - } else if (corner === 'mtr' && target.hasRotatingPoint) { - s.cursor = this.rotationCursor; - } else { - s.cursor = this.defaultCursor; - return false; - } - } - } - return true; - }, - /** * @method _drawSelection * @private @@ -1082,13 +633,17 @@ // selection border if (this.selectionDashArray.length > 1) { + var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft); var py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); + ctx.beginPath(); - this.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray); - this.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray); - this.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray); - this.drawDashedLine(ctx, px+aleft-1, py, px+aleft-1, py+atop, this.selectionDashArray); + + 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(); } @@ -1102,47 +657,6 @@ } }, - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @method drawDashedLine - * @param ctx {Canvas} context - * @param x {Number} start x coordinate - * @param y {Number} start y coordinate - * @param x2 {Number} end x coordinate - * @param y2 {Number} end y coordinate - * @param da {Array} 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(); - }, - /** * @private * @method _findSelectedObjects @@ -1199,7 +713,8 @@ if (this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && - this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay)) { + this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && + this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) { target = this.lastRenderedObjectWithControlsAboveOverlay; return target; } @@ -1247,7 +762,7 @@ * @return {Object} object with "x" and "y" number values */ getPointer: function (e) { - var pointer = getPointer(e); + var pointer = getPointer(e, this.upperCanvasEl); return { x: pointer.x - this._offset.left, y: pointer.y - this._offset.top @@ -1452,51 +967,6 @@ this.fire('selection:cleared'); } return this; - }, - - /** - * @private - * @method _adjustPosition - * @param obj - * @param {String} to One of left, center, right - */ - _adjustPosition: function(obj, to) { - - var angle = fabric.util.degreesToRadians(obj.angle); - - var hypotHalf = obj.getWidth() / 2; - var xHalf = Math.cos(angle) * hypotHalf; - var yHalf = Math.sin(angle) * hypotHalf; - - var hypotFull = obj.getWidth(); - var xFull = Math.cos(angle) * hypotFull; - var yFull = Math.sin(angle) * hypotFull; - - if (obj.originX === 'center' && to === 'left' || - obj.originX === 'right' && to === 'center') { - // move half left - obj.left -= xHalf; - obj.top -= yHalf; - } - else if (obj.originX === 'left' && to === 'center' || - obj.originX === 'center' && to === 'right') { - // move half right - obj.left += xHalf; - obj.top += yHalf; - } - else if (obj.originX === 'left' && to === 'right') { - // move full right - obj.left += xFull; - obj.top += yFull; - } - else if (obj.originX === 'right' && to === 'left') { - // move full left - obj.left -= xFull; - obj.top -= yFull; - } - - obj.setCoords(); - obj.originX = to; } }; @@ -1522,4 +992,4 @@ * @constructor */ fabric.Element = fabric.Canvas; -})(); \ No newline at end of file +})(); diff --git a/src/canvas.animation.js b/src/canvas_animation.mixin.js similarity index 96% rename from src/canvas.animation.js rename to src/canvas_animation.mixin.js index 7707ad11..e6a32247 100644 --- a/src/canvas.animation.js +++ b/src/canvas_animation.mixin.js @@ -1,4 +1,4 @@ -fabric.util.object.extend(fabric.StaticCanvas.prototype, { +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { /** * Animation duration (in ms) for fx* methods diff --git a/src/canvas_events.mixin.js b/src/canvas_events.mixin.js new file mode 100644 index 00000000..710d83b8 --- /dev/null +++ b/src/canvas_events.mixin.js @@ -0,0 +1,475 @@ +(function(){ + + var cursorMap = { + 'tr': 'ne-resize', + 'br': 'se-resize', + 'bl': 'sw-resize', + 'tl': 'nw-resize', + 'ml': 'w-resize', + 'mt': 'n-resize', + 'mr': 'e-resize', + 'mb': 's-resize' + }, + addListener = fabric.util.addListener, + removeListener = fabric.util.removeListener, + getPointer = fabric.util.getPointer; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ { + + /** + * Adds mouse listeners to canvas + * @method _initEvents + * @private + * See configuration documentation for more details. + */ + _initEvents: function () { + var _this = this; + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onResize = this._onResize.bind(this); + + addListener(fabric.window, 'resize', this._onResize); + + if (fabric.isTouchSupported) { + addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); + addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (typeof Event !== 'undefined' && 'add' in Event) { + Event.add(this.upperCanvasEl, 'gesture', function(e, s) { + _this.__onTransformGesture(e, s); + }); + } + } + else { + addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + } + }, + + /** + * @method _onMouseDown + * @private + */ + _onMouseDown: function (e) { + this.__onMouseDown(e); + + !fabric.isTouchSupported && addListener(fabric.document, 'mouseup', this._onMouseUp); + fabric.isTouchSupported && addListener(fabric.document, 'touchend', this._onMouseUp); + + !fabric.isTouchSupported && addListener(fabric.document, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && addListener(fabric.document, 'touchmove', this._onMouseMove); + + !fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + }, + + /** + * @method _onMouseUp + * @private + */ + _onMouseUp: function (e) { + this.__onMouseUp(e); + + !fabric.isTouchSupported && removeListener(fabric.document, 'mouseup', this._onMouseUp); + fabric.isTouchSupported && removeListener(fabric.document, 'touchend', this._onMouseUp); + + !fabric.isTouchSupported && removeListener(fabric.document, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', this._onMouseMove); + + !fabric.isTouchSupported && addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + fabric.isTouchSupported && addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + }, + + /** + * @method _onMouseMove + * @private + */ + _onMouseMove: function (e) { + e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + + /** + * @method _onResize + * @private + */ + _onResize: function () { + this.calcOffset(); + }, + + /** + * 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. + * @method __onMouseUp + * @param {Event} e Event object fired on mouseup + * + */ + __onMouseUp: function (e) { + + var target; + + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._isCurrentlyDrawing = false; + this.freeDrawingBrush.onMouseUp(); + this.fire('mouse:up', { e: e }); + return; + } + + if (this._currentTransform) { + + var transform = this._currentTransform; + + target = transform.target; + if (target._scaling) { + target._scaling = false; + } + + // determine the new coords everytime the image changes its position + var i = this._objects.length; + while (i--) { + this._objects[i].setCoords(); + } + + target.isMoving = false; + + // 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'); + } + + if (this._previousOriginX) { + this._currentTransform.target.adjustPosition(this._previousOriginX); + this._previousOriginX = null; + } + } + + this._currentTransform = null; + + if (this._groupSelector) { + // group selection was completed, determine its bounds + this._findSelectedObjects(e); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.setObjectsCoords(); + activeGroup.set('isMoving', false); + this._setCursor(this.defaultCursor); + } + + // clear selection + this._groupSelector = null; + this.renderAll(); + + this._setCursorFromEvent(e, target); + + // fix for FF + this._setCursor(''); + + var _this = this; + setTimeout(function () { + _this._setCursorFromEvent(e, target); + }, 50); + + this.fire('mouse:up', { target: target, e: e }); + target && target.fire('mouseup', { 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. + * @method __onMouseDown + * @param e {Event} Event object fired on mousedown + * + */ + __onMouseDown: function (e) { + + var pointer; + + // accept only left clicks + var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; + if (!isLeftClick && !fabric.isTouchSupported) return; + + if (this.isDrawingMode) { + pointer = this.getPointer(e); + this._isCurrentlyDrawing = true; + this.discardActiveObject().renderAll(); + this.freeDrawingBrush.onMouseDown(pointer); + this.fire('mouse:down', { e: e }); + return; + } + + // ignore if some object is being transformed at this moment + if (this._currentTransform) return; + + var target = this.findTarget(e), corner; + pointer = this.getPointer(e); + + if (this._shouldClearSelection(e)) { + this._groupSelector = { + ex: pointer.x, + ey: pointer.y, + top: 0, + left: 0 + }; + this.deactivateAllWithDispatch(); + } + else { + // determine if it's a drag or rotate case + this.stateful && target.saveState(); + + if ((corner = target._findTargetCorner(e, this._offset))) { + this.onBeforeScaleRotate(target); + } + + if (this._shouldHandleGroupLogic(e, target)) { + this._handleGroupLogic(e, target); + target = this.getActiveGroup(); + } + else { + if (target !== this.getActiveGroup()) { + this.deactivateAll(); + } + this.setActiveObject(target, e); + } + + this._setupCurrentTransform(e, target); + } + // we must renderAll so that active image is placed on the top canvas + this.renderAll(); + + this.fire('mouse:down', { target: target, e: e }); + target && target.fire('mousedown', { e: e }); + + // center origin when rotating + if (corner === 'mtr') { + this._previousOriginX = this._currentTransform.target.originX; + this._currentTransform.target.adjustPosition('center'); + this._currentTransform.left = this._currentTransform.target.left; + this._currentTransform.top = this._currentTransform.target.top; + } + }, + + /** + * 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. + * @method __onMouseMove + * @param e {Event} Event object fired on mousemove + * + */ + __onMouseMove: function (e) { + + var target, pointer; + + if (this.isDrawingMode) { + if (this._isCurrentlyDrawing) { + pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseMove(pointer); + } + this.upperCanvasEl.style.cursor = this.freeDrawingCursor; + this.fire('mouse:move', { e: e }); + return; + } + + var groupSelector = this._groupSelector; + + // We initially clicked in an empty area, so we draw a box for multiple selection. + if (groupSelector !== null) { + pointer = getPointer(e, this.upperCanvasEl); + + groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; + groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + this.renderTop(); + } + else if (!this._currentTransform) { + + // alias style to elimintate unnecessary lookup + var style = this.upperCanvasEl.style; + + // Here we are hovering the canvas then we will determine + // what part of the pictures we are hovering to change the caret symbol. + // We won't do that while dragging or rotating in order to improve the + // performance. + target = this.findTarget(e); + + if (!target) { + // image/text was hovered-out from, we remove its borders + for (var i = this._objects.length; i--; ) { + if (this._objects[i] && !this._objects[i].active) { + this._objects[i].setActive(false); + } + } + style.cursor = this.defaultCursor; + } + else { + // set proper cursor + this._setCursorFromEvent(e, target); + } + } + else { + // object is being transformed (scaled/rotated/moved/etc.) + pointer = getPointer(e, this.upperCanvasEl); + + var x = pointer.x, + y = pointer.y; + + this._currentTransform.target.isMoving = true; + + var t = this._currentTransform, reset = false; + if ( + (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') + && + ( + // Switch from a normal resize to center-based + (e.altKey && (t.originX !== 'center' || t.originY !== 'center')) + || + // Switch from center-based resize to normal one + (!e.altKey && t.originX === 'center' && t.originY === 'center') + ) + ) { + this._resetCurrentTransform(e); + reset = true; + } + + if (this._currentTransform.action === 'rotate') { + this._rotateObject(x, y); + + this.fire('object:rotating', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('rotating'); + } + else if (this._currentTransform.action === 'scale') { + // rotate object only if shift key is not pressed + // and if it is not a group we are transforming + + // TODO + /*if (!e.shiftKey) { + this._rotateObject(x, y); + + this.fire('object:rotating', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('rotating'); + }*/ + + // if (!this._currentTransform.target.hasRotatingPoint) { + // this._scaleObject(x, y); + // this.fire('object:scaling', { + // target: this._currentTransform.target + // }); + // this._currentTransform.target.fire('scaling'); + // } + + if (e.shiftKey || this.uniScaleTransform) { + this._currentTransform.currentAction = 'scale'; + this._scaleObject(x, y); + } + else { + if (!reset && t.currentAction === 'scale') { + // Switch from a normal resize to proportional + this._resetCurrentTransform(e); + } + + this._currentTransform.currentAction = 'scaleEqually'; + this._scaleObject(x, y, 'equally'); + } + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + } + // else if (this._currentTransform.action === 'scale') { + // this._scaleObject(x, y); + // this.fire('object:scaling', { + // target: this._currentTransform.target + // }); + // this._currentTransform.target.fire('scaling'); + // } + else if (this._currentTransform.action === 'scaleX') { + this._scaleObject(x, y, 'x'); + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('scaling', { e: e }); + } + else if (this._currentTransform.action === 'scaleY') { + this._scaleObject(x, y, 'y'); + + this.fire('object:scaling', { + target: this._currentTransform.target, + e: e + }); + this._currentTransform.target.fire('scaling', { e: e }); + } + else { + this._translateObject(x, y); + + this.fire('object:moving', { + target: this._currentTransform.target, + e: e + }); + + this._setCursor(this.moveCursor); + + this._currentTransform.target.fire('moving', { e: e }); + } + // only commit here. when we are actually moving the pictures + this.renderAll(); + } + this.fire('mouse:move', { target: target, e: e }); + target && target.fire('mousemove', { e: e }); + }, + /** + * Sets the cursor depending on where the canvas is being hovered. + * Note: very buggy in Opera + * @method _setCursorFromEvent + * @param e {Event} Event object + * @param target {Object} Object that the mouse is hovering, if so. + */ + _setCursorFromEvent: function (e, target) { + var s = this.upperCanvasEl.style; + if (!target) { + s.cursor = this.defaultCursor; + return false; + } + else { + var activeGroup = this.getActiveGroup(); + // only show proper corner when group selection is not active + var corner = target._findTargetCorner + && (!activeGroup || !activeGroup.contains(target)) + && target._findTargetCorner(e, this._offset); + + if (!corner) { + s.cursor = this.hoverCursor; + } + else { + if (corner in cursorMap) { + s.cursor = cursorMap[corner]; + } + else if (corner === 'mtr' && target.hasRotatingPoint) { + s.cursor = this.rotationCursor; + } + else { + s.cursor = this.defaultCursor; + return false; + } + } + } + return true; + } + }); +})(); diff --git a/src/canvas.gestures.js b/src/canvas_gestures.mixin.js similarity index 96% rename from src/canvas.gestures.js rename to src/canvas_gestures.mixin.js index a7728bdf..56c997f0 100644 --- a/src/canvas.gestures.js +++ b/src/canvas_gestures.mixin.js @@ -3,7 +3,7 @@ var degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees; - fabric.util.object.extend(fabric.Canvas.prototype, { + fabric.util.object.extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ { /** * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports diff --git a/src/canvas.serialization.js b/src/canvas_serialization.mixin.js similarity index 79% rename from src/canvas.serialization.js rename to src/canvas_serialization.mixin.js index d88ec78a..3f224c1b 100644 --- a/src/canvas.serialization.js +++ b/src/canvas_serialization.mixin.js @@ -1,4 +1,4 @@ -fabric.util.object.extend(fabric.StaticCanvas.prototype, { +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { /** * Populates canvas with data from the specified dataless JSON @@ -13,9 +13,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { */ loadFromDatalessJSON: function (json, callback) { - if (!json) { - return; - } + if (!json) return; // serialize if it wasn't already var serialized = (typeof json === 'string') @@ -26,9 +24,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { this.clear(); - // TODO: test this - this.backgroundColor = serialized.background; - this._enlivenDatalessObjects(serialized.objects, callback); + var _this = this; + this._enlivenDatalessObjects(serialized.objects, function() { + _this._setBgOverlayImages(serialized, callback); + }); }, /** @@ -37,6 +36,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { * @param {Function} callback */ _enlivenDatalessObjects: function (objects, callback) { + var _this = this, + numLoadedObjects = 0, + numTotalObjects = objects.length; /** @ignore */ function onObjectLoaded(object, index) { @@ -128,10 +130,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { } } - var _this = this, - numLoadedObjects = 0, - numTotalObjects = objects.length; - if (numTotalObjects === 0 && callback) { callback(); } @@ -140,7 +138,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { objects.forEach(loadObject, this); } catch(e) { - fabric.log(e.message); + fabric.log(e); } }, @@ -163,55 +161,72 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, { ? JSON.parse(json) : json; - if (!serialized || (serialized && !serialized.objects)) return; - - this.clear(); - var _this = this; this._enlivenObjects(serialized.objects, function () { - _this.backgroundColor = serialized.background; - var backgroundImageLoaded, overlayImageLoaded; - - if (serialized.backgroundImage) { - _this.setBackgroundImage(serialized.backgroundImage, function() { - - _this.backgroundImageOpacity = serialized.backgroundImageOpacity; - _this.backgroundImageStretch = serialized.backgroundImageStretch; - - _this.renderAll(); - backgroundImageLoaded = true; - - callback && overlayImageLoaded && callback(); - }); - } - else { - backgroundImageLoaded = true; - } - - if (serialized.overlayImage) { - _this.setOverlayImage(serialized.overlayImage, function() { - - _this.overlayImageLeft = serialized.overlayImageLeft || 0; - _this.overlayImageTop = serialized.overlayImageTop || 0; - - _this.renderAll(); - overlayImageLoaded = true; - - callback && backgroundImageLoaded && callback(); - }); - } - else { - overlayImageLoaded = true; - } - - if (!serialized.backgroundImage && !serialized.overlayImage) { - callback && callback(); - } + _this._setBgOverlayImages(serialized, callback); }); return this; }, + _setBgOverlayImages: function(serialized, callback) { + + var _this = this, + backgroundPatternLoaded, + backgroundImageLoaded, + overlayImageLoaded; + + if (serialized.backgroundImage) { + this.setBackgroundImage(serialized.backgroundImage, function() { + + _this.backgroundImageOpacity = serialized.backgroundImageOpacity; + _this.backgroundImageStretch = serialized.backgroundImageStretch; + + _this.renderAll(); + + backgroundImageLoaded = true; + + callback && overlayImageLoaded && backgroundPatternLoaded && callback(); + }); + } + else { + backgroundImageLoaded = true; + } + + if (serialized.overlayImage) { + this.setOverlayImage(serialized.overlayImage, function() { + + _this.overlayImageLeft = serialized.overlayImageLeft || 0; + _this.overlayImageTop = serialized.overlayImageTop || 0; + + _this.renderAll(); + overlayImageLoaded = true; + + callback && backgroundImageLoaded && backgroundPatternLoaded && callback(); + }); + } + else { + overlayImageLoaded = true; + } + + if (serialized.background) { + this.setBackgroundColor(serialized.background, function() { + + _this.renderAll(); + backgroundPatternLoaded = true; + + callback && overlayImageLoaded && backgroundImageLoaded && callback(); + }); + } + else { + backgroundPatternLoaded = true; + } + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background) { + callback && callback(); + } + }, + /** * @method _enlivenObjects * @param {Array} objects diff --git a/src/circle.class.js b/src/circle.class.js index 4b11e8e7..6de5fcc2 100644 --- a/src/circle.class.js +++ b/src/circle.class.js @@ -81,6 +81,7 @@ if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.stroke(); } diff --git a/src/ellipse.class.js b/src/ellipse.class.js index d1204613..8d1eda6f 100644 --- a/src/ellipse.class.js +++ b/src/ellipse.class.js @@ -101,6 +101,7 @@ if (this.stroke) { ctx.stroke(); } + this._removeShadow(ctx); if (this.fill) { ctx.fill(); } diff --git a/src/gradient.class.js b/src/gradient.class.js index 490070fc..9a4bf77f 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -65,11 +65,11 @@ /** * Returns an instance of CanvasGradient - * @method toLiveGradient + * @method toLive * @param ctx * @return {CanvasGradient} */ - toLiveGradient: function(ctx) { + toLive: function(ctx) { var gradient = ctx.createLinearGradient( this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2); diff --git a/src/image.class.js b/src/image.class.js index 092a1c0f..a905ee5e 100644 --- a/src/image.class.js +++ b/src/image.class.js @@ -102,7 +102,11 @@ if (!noTransform) { this.transform(ctx); } + + this._setShadow(ctx); this._render(ctx); + this._removeShadow(ctx); + if (this.active && !noTransform) { this.drawBorders(ctx); this.hideCorners || this.drawCorners(ctx); @@ -186,14 +190,10 @@ var isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined', imgEl = this._originalImage, - canvasEl = fabric.document.createElement('canvas'), + canvasEl = fabric.util.createCanvasElement(), replacement = isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'), _this = this; - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - canvasEl.width = imgEl.width; canvasEl.height = imgEl.height; diff --git a/src/image_filters.js b/src/image_filters.js index babf03fb..64bd7017 100644 --- a/src/image_filters.js +++ b/src/image_filters.js @@ -620,7 +620,8 @@ fabric.Image.filters.Convolute = fabric.util.createClass(/** @scope fabric.Image 0, 1, 0, 0, 0, 0 ]; - this.tmpCtx = fabric.document.createElement('canvas').getContext('2d'); + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext('2d'); }, /** diff --git a/src/node.js b/src/node.js index 9b94e0fd..0cfaa7c9 100644 --- a/src/node.js +++ b/src/node.js @@ -121,6 +121,10 @@ 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); @@ -141,4 +145,4 @@ fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; } -})(); \ No newline at end of file +})(); diff --git a/src/object.class.js b/src/object.class.js index dc65f95a..8e334d73 100644 --- a/src/object.class.js +++ b/src/object.class.js @@ -6,7 +6,6 @@ extend = fabric.util.object.extend, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, - getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians; if (fabric.Object) { @@ -32,11 +31,11 @@ fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ { /** - * Type of an object (rect, circle, path, etc) + * Type of an object (rect, circle, path, etc.) * @property * @type String */ - type: 'object', + type: 'object', /** * Horizontal origin of transformation of an object (one of "left", "right", "center") @@ -199,6 +198,13 @@ */ strokeDashArray: null, + /** + * Shadow object representing shadow of this shape + * @property + * @type fabric.Shadow + */ + shadow: null, + /** * Border opacity when object is active and moving * @property @@ -286,7 +292,7 @@ 'top left width height scaleX scaleY flipX flipY ' + 'angle opacity cornerSize fill overlayFill originX originY ' + 'stroke strokeWidth strokeDashArray fillRule ' + - 'borderScaleFactor transformMatrix selectable' + 'borderScaleFactor transformMatrix selectable shadow' ).split(' '), /** @@ -305,11 +311,34 @@ * @method _initGradient */ _initGradient: function(options) { - if (options.fill && typeof options.fill === 'object' && !(options.fill instanceof fabric.Gradient)) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { this.set('fill', new fabric.Gradient(options.fill)); } }, + /** + * @private + * @method _initPattern + */ + _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 + * @method _initShadow + */ + _initShadow: function(options) { + if (options.shadow && !(options.shadow instanceof fabric.Shadow)) { + this.setShadow(options.shadow); + } + }, + /** * Sets object's properties from options * @method setOptions @@ -320,6 +349,8 @@ this.set(prop, options[prop]); } this._initGradient(options); + this._initPattern(options); + this._initShadow(options); }, /** @@ -350,30 +381,31 @@ var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; var 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, - overlayFill: this.overlayFill, - stroke: this.stroke, - strokeWidth: this.strokeWidth, - strokeDashArray: this.strokeDashArray, - 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), - selectable: this.selectable, - hasControls: this.hasControls, - hasBorders: this.hasBorders, - hasRotatingPoint: this.hasRotatingPoint, + 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, + overlayFill: this.overlayFill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: this.strokeWidth, + strokeDashArray: this.strokeDashArray, + 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), + selectable: this.selectable, + hasControls: this.hasControls, + hasBorders: this.hasBorders, + hasRotatingPoint: this.hasRotatingPoint, transparentCorners: this.transparentCorners, - perPixelTargetFind: this.perPixelTargetFind + perPixelTargetFind: this.perPixelTargetFind, + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow }; if (!this.includeDefaultValues) { @@ -489,21 +521,13 @@ }, /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @method _constrainScale - * @param {Number} value - * @return {Number} + * Basic getter + * @method get + * @param {String} property + * @return {Any} value of a property */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) - return -this.minScaleLimit; - else - return this.minScaleLimit; - } - - return value; + get: function(property) { + return this[property]; }, /** @@ -587,16 +611,6 @@ return this; }, - /** - * Basic getter - * @method get - * @param {String} property - * @return {Any} value of a property - */ - get: function(property) { - return this[property]; - }, - /** * Renders an object on a specified context * @method render @@ -621,15 +635,20 @@ if (this.stroke || this.strokeDashArray) { ctx.lineWidth = this.strokeWidth; - ctx.strokeStyle = this.stroke; + if (this.stroke && this.stroke.toLive) { + ctx.strokeStyle = this.stroke.toLive(ctx); + } + else { + ctx.strokeStyle = this.stroke; + } } if (this.overlayFill) { ctx.fillStyle = this.overlayFill; } else if (this.fill) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; } @@ -638,7 +657,9 @@ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } + this._setShadow(ctx); this._render(ctx, noTransform); + this._removeShadow(ctx); if (this.active && !noTransform) { this.drawBorders(ctx); @@ -648,576 +669,25 @@ }, /** - * Returns width of an object - * @method getWidth - * @return {Number} width value + * @private + * @method _setShadow */ - getWidth: function() { - return this.width * this.scaleX; - }, + _setShadow: function(ctx) { + if (!this.shadow) return; - /** - * Returns height of an object - * @method getHeight - * @return {Number} height value - */ - getHeight: function() { - return this.height * this.scaleY; - }, - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @method translateToCenterPoint - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {string} enum('left', 'center', 'right') Horizontal origin - * @param {string} enum('top', 'center', 'bottom') Vertical origin - * @return {fabric.Point} - */ - translateToCenterPoint: function(point, originX, originY) { - var cx = point.x, cy = point.y; - - if ( originX === "left" ) { - cx = point.x + this.getWidth() / 2; - } - else if ( originX === "right" ) { - cx = point.x - this.getWidth() / 2; - } - - if ( originY === "top" ) { - cy = point.y + this.getHeight() / 2; - } - else if ( originY === "bottom" ) { - cy = point.y - this.getHeight() / 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) - * @method translateToOriginPoint - * @param {fabric.Point} point The point which corresponds to center of the object - * @param {string} enum('left', 'center', 'right') Horizontal origin - * @param {string} enum('top', 'center', 'bottom') Vertical origin - * @return {fabric.Point} - */ - translateToOriginPoint: function(center, originX, originY) { - var x = center.x, y = center.y; - - // Get the point coordinates - if ( originX === "left" ) { - x = center.x - this.getWidth() / 2; - } - else if ( originX === "right" ) { - x = center.x + this.getWidth() / 2; - } - if ( originY === "top" ) { - y = center.y - this.getHeight() / 2; - } - else if ( originY === "bottom" ) { - y = center.y + this.getHeight() / 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 - * @method getCenterPoint - * @return {fabric.Point} - */ - getCenterPoint: function() { - return this.translateToCenterPoint( - new fabric.Point(this.left, this.top), this.originX, this.originY); - }, - - /** - * Returns the coordinates of the object based on center coordinates - * @method getOriginPoint - * @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 - * @method getPointByOrigin - * @param {string} enum('left', 'center', 'right') Horizontal origin - * @param {string} enum('top', 'center', 'bottom') Vertical origin - * @return {fabric.Point} - */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); - - return this.translateToOriginPoint(center, originX, originY); - }, - - /** - * Returns the point in local coordinates - * @method toLocalPoint - * @param {fabric.Point} The point relative to the global coordinate system - * @return {fabric.Point} - */ - toLocalPoint: function(point, originX, originY) { - var center = this.getCenterPoint(); - - var x, y; - if (originX !== undefined && originY !== undefined) { - if ( originX === "left" ) { - x = center.x - this.getWidth() / 2; - } - else if ( originX === "right" ) { - x = center.x + this.getWidth() / 2; - } - else { - x = center.x; - } - - if ( originY === "top" ) { - y = center.y - this.getHeight() / 2; - } - else if ( originY === "bottom" ) { - y = center.y + this.getHeight() / 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 - * @method toGlobalPoint - * @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 - * @method setPositionByOrigin - * @param {fabric.Point} point The new position of the object - * @param {string} enum('left', 'center', 'right') Horizontal origin - * @param {string} enum('top', 'center', 'bottom') Vertical origin - * @return {void} - */ - setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY); - var position = this.translateToOriginPoint(center, this.originX, this.originY); - - this.set('left', position.x); - this.set('top', position.y); - }, - - /** - * Scales an object (equally by x and y) - * @method scale - * @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) - * @method scaleToWidth - * @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) - * @method scaleToHeight - * @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 - * @method setCoords - * @return {fabric.Object} thisArg - * @chainable - */ - setCoords: function() { - - var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, - padding = this.padding, - theta = degreesToRadians(this.angle); - - this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; - this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; - - //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)); - - var _angle = Math.atan(this.currentHeight / this.currentWidth); - - // offset added for rotate and scale actions - 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(); - 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: tl.x + (this.currentWidth/2 * cosTh), - y: tl.y + (this.currentWidth/2 * sinTh) - }; - - // 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); - - // clockwise - this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb, mtr: mtr }; - - // set coordinates of the draggable boxes in the corners used to scale/rotate the image - this._setCornerCoords(); - - return this; - }, - - /** - * Returns width of an object's bounding rectangle - * @method getBoundingRectWidth - * @return {Number} width value - */ - getBoundingRectWidth: function() { - this.oCoords || this.setCoords(); - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; - var minX = fabric.util.array.min(xCoords); - var maxX = fabric.util.array.max(xCoords); - return Math.abs(minX - maxX); - }, - - /** - * Returns height of an object's bounding rectangle - * @method getBoundingRectHeight - * @return {Number} height value - */ - getBoundingRectHeight: function() { - this.oCoords || this.setCoords(); - var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; - var minY = fabric.util.array.min(yCoords); - var maxY = fabric.util.array.max(yCoords); - return Math.abs(minY - maxY); - }, - - /** - * Draws borders of an object's bounding box. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @method drawBorders - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawBorders: function(ctx) { - if (!this.hasBorders) return; - - var padding = this.padding, - padding2 = padding * 2, - strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0; - - 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; - - ctx.scale(scaleX, scaleY); - - var w = this.getWidth(), - h = this.getHeight(); - - 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), - ~~(h + padding2 + strokeWidth * this.scaleY) - ); - - if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { - - var rotateHeight = ( - this.flipY - ? h + (strokeWidth * this.scaleY) + (padding * 2) - : -h - (strokeWidth * this.scaleY) - (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; + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; }, /** * @private - * @method _renderDashedStroke + * @method _removeShadow */ - _renderDashedStroke: function(ctx) { - - if (1 & this.strokeDashArray.length /* if odd number of items */) { - /* duplicate items */ - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - - var i = 0, - x = -this.width/2, y = -this.height/2, - _this = this, - padding = this.padding, - dashedArrayLength = this.strokeDashArray.length; - - ctx.save(); - ctx.beginPath(); - - /** @ignore */ - function renderSide(xMultiplier, yMultiplier) { - - var lineLength = 0, - lengthDiff = 0, - sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2; - - while (lineLength < sideLength) { - - var lengthOfSubPath = _this.strokeDashArray[i++]; - lineLength += lengthOfSubPath; - - if (lineLength > sideLength) { - lengthDiff = lineLength - sideLength; - } - - // track coords - if (xMultiplier) { - x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0); - } - else { - y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0); - } - - ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y); - if (i >= dashedArrayLength) { - i = 0; - } - } - } - - renderSide(1, 0); - renderSide(0, 1); - renderSide(-1, 0); - renderSide(0, -1); - - ctx.stroke(); - ctx.closePath(); - ctx.restore(); - }, - - /** - * Draws corners of an object's bounding box. - * Requires public properties: width, height, scaleX, scaleY - * Requires public options: cornerSize, padding - * @method drawCorners - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawCorners: function(ctx) { - if (!this.hasControls) return; - - var size = this.cornerSize, - size2 = size / 2, - strokeWidth2 = this.strokeWidth / 2, - left = -(this.width / 2), - top = -(this.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, - methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', - isVML = typeof G_vmlCanvasManager !== 'undefined'; - - ctx.save(); - - ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); - - 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; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // top-right - _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // bottom-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // bottom-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - if (!this.get('lockUniScaling')) { - // middle-top - _left = left + width/2 - scaleOffsetX; - _top = top - scaleOffsetY - strokeWidth2 - paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-bottom - _left = left + width/2 - scaleOffsetX; - _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-right - _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; - _top = top + height/2 - scaleOffsetY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - - // middle-left - _left = left - scaleOffsetX - strokeWidth2 - paddingX; - _top = top + height/2 - scaleOffsetY; - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - } - - // middle-top-rotate - if (this.hasRotatingPoint) { - - _left = left + width/2 - scaleOffsetX; - _top = this.flipY ? - (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) - : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); - - isVML || ctx.clearRect(_left, _top, sizeX, sizeY); - ctx[methodName](_left, _top, sizeX, sizeY); - } - - ctx.restore(); - - return this; + _removeShadow: function(ctx) { + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, /** @@ -1274,10 +744,7 @@ * @param callback {Function} callback that recieves resulting data-url string */ toDataURL: function(callback) { - var el = fabric.document.createElement('canvas'); - if (!el.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(el); - } + var el = fabric.util.createCanvasElement(); el.width = this.getBoundingRectWidth(); el.height = this.getBoundingRectHeight(); @@ -1344,84 +811,6 @@ this.saveState(); }, - /** - * Returns true if object intersects with an area formed by 2 points - * @method intersectsWithRect - * @param {Object} selectionTL - * @param {Object} selectionBR - * @return {Boolean} - */ - intersectsWithRect: function(selectionTL, selectionBR) { - 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); - - var intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - selectionTL, - selectionBR - ); - return (intersection.status === 'Intersection'); - }, - - /** - * Returns true if object intersects with another object - * @method intersectsWithObject - * @param {Object} other Object to test - * @return {Boolean} - */ - 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); - - var intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); - - return (intersection.status === 'Intersection'); - }, - - /** - * Returns true if object is fully contained within area of another object - * @method isContainedWithinObject - * @param {Object} other Object to test - * @return {Boolean} - */ - isContainedWithinObject: function(other) { - return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br); - }, - - /** - * Returns true if object is fully contained within area formed by 2 points - * @method isContainedWithinRect - * @param {Object} selectionTL - * @param {Object} selectionBR - * @return {Boolean} - */ - isContainedWithinRect: function(selectionTL, selectionBR) { - 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); - - return tl.x > selectionTL.x - && tr.x < selectionBR.x - && tl.y > selectionTL.y - && bl.y < selectionBR.y; - }, - /** * Returns true if specified type is identical to the type of an instance * @method isType @@ -1432,324 +821,6 @@ return this.type === type; }, - /** - * Determines which one of the four corners has been clicked - * @method _findTargetCorner - * @private - * @param e {Event} event object - * @param offset {Object} canvas offset - * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found - */ - _findTargetCorner: function(e, offset) { - if (!this.hasControls || !this.active) return false; - - var pointer = getPointer(e), - ex = pointer.x - offset.left, - ey = pointer.y - offset.top, - xpoints, - lines; - - for (var i in this.oCoords) { - - 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, i); - - // 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(ex, ey, lines); - if (xpoints % 2 === 1 && xpoints !== 0) { - this.__corner = i; - return i; - } - } - return false; - }, - - /** - * Helper method to determine how many cross points are between the 4 image edges - * and the horizontal line determined by the position of our mouse when clicked on canvas - * @method _findCrossPoints - * @private - * @param ex {Number} x coordinate of the mouse - * @param ey {Number} y coordinate of the mouse - * @param oCoords {Object} Coordinates of the image being evaluated - */ - _findCrossPoints: function(ex, ey, oCoords) { - var b1, b2, a1, a2, xi, yi, - xcount = 0, - iLine; - - for (var lineKey in oCoords) { - iLine = oCoords[lineKey]; - // optimisation 1: line below dot. no cross - if ((iLine.o.y < ey) && (iLine.d.y < ey)) { - continue; - } - // optimisation 2: line above dot. no cross - if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) { - continue; - } - // optimisation 3: vertical line case - if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= ex)) { - xi = iLine.o.x; - yi = ey; - } - // calculate the intersection point - else { - b1 = 0; - b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x); - a1 = ey-b1*ex; - a2 = iLine.o.y-b2*iLine.o.x; - - xi = - (a1-a2)/(b1-b2); - yi = a1+b1*xi; - } - // dont count xi < ex cases - if (xi >= ex) { - xcount += 1; - } - // optimisation 4: specific for square images - if (xcount === 2) { - break; - } - } - return xcount; - }, - - /** - * Method that returns an object with the image lines in it given the coordinates of the corners - * @method _getImageLines - * @private - * @param oCoords {Object} coordinates of the image 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 - } - }; - }, - - /** - * Sets the coordinates of the draggable boxes in the corners of - * the image used to scale/rotate it. - * @method _setCornerCoords - * @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) - } - }; - }, - /** * Makes object's color grayscale * @method toGrayscale @@ -1791,6 +862,22 @@ this.set('fill', fabric.Gradient.forObject(this, options)); }, + /** + * Sets pattern fill of an object + * @method setPatternFill + */ + setPatternFill: function(options) { + this.set('fill', new fabric.Pattern(options)); + }, + + /** + * Sets shadow of an object + * @method setShadow + */ + setShadow: function(options) { + this.set('shadow', new fabric.Shadow(options)); + }, + /** * Animates object's properties * @method animate @@ -1823,7 +910,7 @@ * @method _animate */ _animate: function(property, to, options) { - var obj = this; + var obj = this, propPair; to = to.toString(); @@ -1834,12 +921,20 @@ 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 = this.get(property); + options.from = currentValue; } if (~to.indexOf('=')) { - to = this.get(property) + parseFloat(to.replace('=', '')); + to = currentValue + parseFloat(to.replace('=', '')); } else { to = parseFloat(to); @@ -1852,7 +947,12 @@ easing: options.easing, duration: options.duration, onChange: function(value) { - obj.set(property, value); + if (propPair) { + obj[propPair[0]][propPair[1]] = value; + } + else { + obj.set(property, value); + } options.onChange && options.onChange(); }, onComplete: function() { @@ -1977,14 +1077,11 @@ extend(fabric.Object.prototype, fabric.Observable); - extend(fabric.Object, { - - /** - * @static - * @constant - * @type Number - */ - NUM_FRACTION_DIGITS: 2 - }); + /** + * @static + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; })(typeof exports !== 'undefined' ? exports : this); diff --git a/src/object_geometry.mixin.js b/src/object_geometry.mixin.js new file mode 100644 index 00000000..25222b2e --- /dev/null +++ b/src/object_geometry.mixin.js @@ -0,0 +1,308 @@ +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { + + /** + * Returns true if object intersects with an area formed by 2 points + * @method intersectsWithRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + intersectsWithRect: function(selectionTL, selectionBR) { + 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); + + var intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + selectionTL, + selectionBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Returns true if object intersects with another object + * @method intersectsWithObject + * @param {Object} other Object to test + * @return {Boolean} + */ + 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); + + var intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); + + return intersection.status === 'Intersection'; + }, + + /** + * Returns true if object is fully contained within area of another object + * @method isContainedWithinObject + * @param {Object} other Object to test + * @return {Boolean} + */ + isContainedWithinObject: function(other) { + return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br); + }, + + /** + * Returns true if object is fully contained within area formed by 2 points + * @method isContainedWithinRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + isContainedWithinRect: function(selectionTL, selectionBR) { + 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); + + return tl.x > selectionTL.x + && tr.x < selectionBR.x + && tl.y > selectionTL.y + && bl.y < selectionBR.y; + }, + + /** + * Returns width of an object's bounding rectangle + * @deprecated since 1.0.4 + * @method getBoundingRectWidth + * @return {Number} width value + */ + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + + /** + * Returns height of an object's bounding rectangle + * @deprecated since 1.0.4 + * @method getBoundingRectHeight + * @return {Number} height value + */ + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * @method getBoundingRect + * @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]; + var minX = fabric.util.array.min(xCoords); + var maxX = fabric.util.array.max(xCoords); + var width = Math.abs(minX - maxX); + + var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; + var minY = fabric.util.array.min(yCoords); + var maxY = fabric.util.array.max(yCoords); + var height = Math.abs(minY - maxY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns width of an object + * @method getWidth + * @return {Number} width value + */ + getWidth: function() { + return this.width * this.scaleX; + }, + + /** + * Returns height of an object + * @method getHeight + * @return {Number} height value + */ + getHeight: function() { + return this.height * this.scaleY; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @method _constrainScale + * @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) + * @method scale + * @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) + * @method scaleToWidth + * @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) + * @method scaleToHeight + * @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 + * @method setCoords + * @return {fabric.Object} thisArg + * @chainable + */ + setCoords: function() { + + var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, + padding = this.padding, + theta = degreesToRadians(this.angle); + + this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; + this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; + + // 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)); + + var _angle = Math.atan(this.currentHeight / this.currentWidth); + + // offset added for rotate and scale actions + 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(); + 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: tl.x + (this.currentWidth/2 * cosTh), + y: tl.y + (this.currentWidth/2 * sinTh) + }; + + // 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); + + // clockwise + this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb, mtr: mtr }; + + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this._setCornerCoords(); + + return this; + } + }); +})(); \ No newline at end of file diff --git a/src/object_interactivity.mixin.js b/src/object_interactivity.mixin.js new file mode 100644 index 00000000..f76467b7 --- /dev/null +++ b/src/object_interactivity.mixin.js @@ -0,0 +1,496 @@ +(function(){ + + var getPointer = fabric.util.getPointer, + degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { + + /** + * Determines which one of the four corners has been clicked + * @method _findTargetCorner + * @private + * @param e {Event} event object + * @param offset {Object} canvas offset + * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found + */ + _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, + xpoints, + lines; + + for (var i in this.oCoords) { + + 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, i); + + // 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(ex, ey, lines); + if (xpoints % 2 === 1 && xpoints !== 0) { + this.__corner = i; + return i; + } + } + return false; + }, + + /** + * Helper method to determine how many cross points are between the 4 image edges + * and the horizontal line determined by the position of our mouse when clicked on canvas + * @method _findCrossPoints + * @private + * @param ex {Number} x coordinate of the mouse + * @param ey {Number} y coordinate of the mouse + * @param oCoords {Object} Coordinates of the image being evaluated + */ + _findCrossPoints: function(ex, ey, oCoords) { + var b1, b2, a1, a2, xi, yi, + xcount = 0, + iLine; + + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + // optimisation 1: line below dot. no cross + if ((iLine.o.y < ey) && (iLine.d.y < ey)) { + continue; + } + // optimisation 2: line above dot. no cross + if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= ex)) { + xi = iLine.o.x; + yi = ey; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x); + a1 = ey-b1*ex; + a2 = iLine.o.y-b2*iLine.o.x; + + xi = - (a1-a2)/(b1-b2); + yi = a1+b1*xi; + } + // dont count xi < ex cases + if (xi >= ex) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount === 2) { + break; + } + } + return xcount; + }, + + /** + * Method that returns an object with the image lines in it given the coordinates of the corners + * @method _getImageLines + * @private + * @param oCoords {Object} coordinates of the image 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 + } + }; + }, + + /** + * Sets the coordinates of the draggable boxes in the corners of + * the image used to scale/rotate it. + * @method _setCornerCoords + * @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 + * @method drawBorders + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawBorders: function(ctx) { + if (!this.hasBorders) return; + + var padding = this.padding, + padding2 = padding * 2, + strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0; + + 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; + + ctx.scale(scaleX, scaleY); + + var w = this.getWidth(), + h = this.getHeight(); + + 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), + ~~(h + padding2 + strokeWidth * this.scaleY) + ); + + if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) { + + var rotateHeight = ( + this.flipY + ? h + (strokeWidth * this.scaleY) + (padding * 2) + : -h - (strokeWidth * this.scaleY) - (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; + }, + + /** + * Draws corners of an object's bounding box. + * Requires public properties: width, height, scaleX, scaleY + * Requires public options: cornerSize, padding + * @method drawCorners + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawCorners: function(ctx) { + if (!this.hasControls) return; + + var size = this.cornerSize, + size2 = size / 2, + strokeWidth2 = this.strokeWidth / 2, + left = -(this.width / 2), + top = -(this.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, + methodName = this.transparentCorners ? 'strokeRect' : 'fillRect', + isVML = typeof G_vmlCanvasManager !== 'undefined'; + + ctx.save(); + + ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); + + 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; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // top-right + _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + if (!this.get('lockUniScaling')) { + // middle-top + _left = left + width/2 - scaleOffsetX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-bottom + _left = left + width/2 - scaleOffsetX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height/2 - scaleOffsetY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height/2 - scaleOffsetY; + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + // middle-top-rotate + if (this.hasRotatingPoint) { + + _left = left + width/2 - scaleOffsetX; + _top = this.flipY ? + (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) + : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + + isVML || ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + ctx.restore(); + + return this; + } + }); +})(); \ No newline at end of file diff --git a/src/object_origin.mixin.js b/src/object_origin.mixin.js new file mode 100644 index 00000000..00981731 --- /dev/null +++ b/src/object_origin.mixin.js @@ -0,0 +1,207 @@ +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { + + /** + * Translates the coordinates from origin to center coordinates (based on the object's dimensions) + * @method translateToCenterPoint + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {string} enum('left', 'center', 'right') Horizontal origin + * @param {string} enum('top', 'center', 'bottom') Vertical origin + * @return {fabric.Point} + */ + translateToCenterPoint: function(point, originX, originY) { + var cx = point.x, cy = point.y; + + if ( originX === "left" ) { + cx = point.x + this.getWidth() / 2; + } + else if ( originX === "right" ) { + cx = point.x - this.getWidth() / 2; + } + + if ( originY === "top" ) { + cy = point.y + this.getHeight() / 2; + } + else if ( originY === "bottom" ) { + cy = point.y - this.getHeight() / 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) + * @method translateToOriginPoint + * @param {fabric.Point} point The point which corresponds to center of the object + * @param {string} enum('left', 'center', 'right') Horizontal origin + * @param {string} enum('top', 'center', 'bottom') Vertical origin + * @return {fabric.Point} + */ + translateToOriginPoint: function(center, originX, originY) { + var x = center.x, y = center.y; + + // Get the point coordinates + if ( originX === "left" ) { + x = center.x - this.getWidth() / 2; + } + else if ( originX === "right" ) { + x = center.x + this.getWidth() / 2; + } + if ( originY === "top" ) { + y = center.y - this.getHeight() / 2; + } + else if ( originY === "bottom" ) { + y = center.y + this.getHeight() / 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 + * @method getCenterPoint + * @return {fabric.Point} + */ + getCenterPoint: function() { + return this.translateToCenterPoint( + new fabric.Point(this.left, this.top), this.originX, this.originY); + }, + + /** + * Returns the coordinates of the object based on center coordinates + * @method getOriginPoint + * @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 + * @method getPointByOrigin + * @param {string} enum('left', 'center', 'right') Horizontal origin + * @param {string} enum('top', 'center', 'bottom') Vertical origin + * @return {fabric.Point} + */ + // getPointByOrigin: function(originX, originY) { + // var center = this.getCenterPoint(); + + // return this.translateToOriginPoint(center, originX, originY); + // }, + + /** + * Returns the point in local coordinates + * @method toLocalPoint + * @param {fabric.Point} The point relative to the global coordinate system + * @return {fabric.Point} + */ + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(); + + var x, y; + if (originX !== undefined && originY !== undefined) { + if ( originX === "left" ) { + x = center.x - this.getWidth() / 2; + } + else if ( originX === "right" ) { + x = center.x + this.getWidth() / 2; + } + else { + x = center.x; + } + + if ( originY === "top" ) { + y = center.y - this.getHeight() / 2; + } + else if ( originY === "bottom" ) { + y = center.y + this.getHeight() / 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 + * @method toGlobalPoint + * @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 + * @method setPositionByOrigin + * @param {fabric.Point} point The new position of the object + * @param {string} enum('left', 'center', 'right') Horizontal origin + * @param {string} enum('top', 'center', 'bottom') Vertical origin + * @return {void} + */ + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY); + var position = this.translateToOriginPoint(center, this.originX, this.originY); + + this.set('left', position.x); + this.set('top', position.y); + }, + + /** + * @method adjustPosition + * @param {String} to One of left, center, right + */ + adjustPosition: function(to) { + + var angle = degreesToRadians(this.angle); + + var hypotHalf = this.getWidth() / 2; + var xHalf = Math.cos(angle) * hypotHalf; + var yHalf = Math.sin(angle) * hypotHalf; + + var hypotFull = this.getWidth(); + var xFull = Math.cos(angle) * hypotFull; + var 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; + } + }); + +})(); \ No newline at end of file diff --git a/src/object_straightening.js b/src/object_straightening.mixin.js similarity index 96% rename from src/object_straightening.js rename to src/object_straightening.mixin.js index bac388a5..b88aa4ea 100644 --- a/src/object_straightening.js +++ b/src/object_straightening.mixin.js @@ -1,4 +1,4 @@ -fabric.util.object.extend(fabric.Object.prototype, { +fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ { /** * @private diff --git a/src/observable.js b/src/observable.mixin.js similarity index 100% rename from src/observable.js rename to src/observable.mixin.js diff --git a/src/parser.js b/src/parser.js index dd5e60dd..bc5c4ca1 100644 --- a/src/parser.js +++ b/src/parser.js @@ -365,8 +365,8 @@ checkIfDone(); } } - catch(e) { - fabric.log(e.message || e); + catch(err) { + fabric.log(err); } } else { diff --git a/src/path.class.js b/src/path.class.js index d327e880..3605cd09 100644 --- a/src/path.class.js +++ b/src/path.class.js @@ -543,21 +543,26 @@ ctx.fillStyle = this.overlayFill; } else if (this.fill) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; } if (this.stroke) { - ctx.strokeStyle = this.stroke; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; } ctx.beginPath(); + this._setShadow(ctx); this._render(ctx); if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); + if (this.stroke) { ctx.strokeStyle = this.stroke; ctx.lineWidth = this.strokeWidth; diff --git a/src/path_group.class.js b/src/path_group.class.js index 229d91aa..f912f92f 100644 --- a/src/path_group.class.js +++ b/src/path_group.class.js @@ -73,9 +73,13 @@ } this.transform(ctx); + + this._setShadow(ctx); for (var i = 0, l = this.paths.length; i < l; ++i) { this.paths[i].render(ctx, true); } + this._removeShadow(ctx); + if (this.active) { this.drawBorders(ctx); this.hideCorners || this.drawCorners(ctx); diff --git a/src/pattern.class.js b/src/pattern.class.js new file mode 100644 index 00000000..da00bfc8 --- /dev/null +++ b/src/pattern.class.js @@ -0,0 +1,69 @@ +/** + * Pattern class + * @class Pattern + * @memberOf fabric + */ +fabric.Pattern = fabric.util.createClass(/** @scope fabric.Pattern.prototype */ { + + /** + * Repeat property of a pattern (one of repeat, repeat-x, repeat-y) + * @property + * @type String + */ + repeat: 'repeat', + + /** + * Constructor + * @method initialize + * @param {Object} [options] + * @return {fabric.Pattern} thisArg + */ + initialize: function(options) { + options || (options = { }); + + if (options.source) { + this.source = typeof options.source === 'string' + ? new Function(options.source) + : options.source; + } + if (options.repeat) { + this.repeat = options.repeat; + } + }, + + /** + * Returns object representation of a pattern + * @method toObject + * @return {Object} + */ + toObject: function() { + + var source; + + // callback + if (typeof this.source === 'function') { + source = String(this.source) + .match(/function\s+\w*\s*\(.*\)\s+\{([\s\S]*)\}/)[1]; + } + // element + else if (typeof this.source.src === 'string') { + source = this.source.src; + } + + return { + source: source, + repeat: this.repeat + }; + }, + + /** + * Returns an instance of CanvasPattern + * @method toLive + * @param ctx + * @return {CanvasPattern} + */ + toLive: function(ctx) { + var source = typeof this.source === 'function' ? this.source() : this.source; + return ctx.createPattern(source, this.repeat); + } +}); \ No newline at end of file diff --git a/src/pencil_brush.class.js b/src/pencil_brush.class.js index 61d50e40..84f896ea 100644 --- a/src/pencil_brush.class.js +++ b/src/pencil_brush.class.js @@ -208,6 +208,20 @@ return path; }, + /** + * Creates fabric.Path object to add on canvas + * @method createPath + * @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.canvas.freeDrawingColor; + path.strokeWidth = this.canvas.freeDrawingLineWidth; + return path; + }, + /** * On mouseup after drawing the path on contextTop canvas * we use the points captured to create an new fabric path object @@ -219,10 +233,8 @@ var ctx = this.canvas.contextTop; ctx.closePath(); - var path = this._getSVGPathData(); - path = path.join(''); - - if (path === "M 0 0 Q 0 0 0 0 L 0 0") { + 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, @@ -231,27 +243,23 @@ return; } - var p = new fabric.Path(path); - p.fill = null; - p.stroke = this.color; - p.strokeWidth = this.width; - this.canvas.add(p); - // set path origin coordinates based on our bounding box var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2; var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2; this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); - p.set({ left: originLeft, top: originTop }); + var path = this.createPath(pathData); + path.set({ left: originLeft, top: originTop }); - // does not change position - p.setCoords(); + this.canvas.add(path); + path.setCoords(); + this.canvas.contextTop && this.canvas.clearContext(this.canvas.contextTop); this.canvas.renderAll(); // fire event 'path' created - this.canvas.fire('path:created', { path: p }); + this.canvas.fire('path:created', { path: path }); } }); -})(); \ No newline at end of file +})(); diff --git a/src/polygon.class.js b/src/polygon.class.js index 1af1e67a..e3b30379 100644 --- a/src/polygon.class.js +++ b/src/polygon.class.js @@ -56,6 +56,15 @@ this.width = (maxX - minX) || 1; this.height = (maxY - minY) || 1; + // var halfWidth = this.width / 2, + // halfHeight = this.height / 2; + + // change points to offset polygon into a bounding box + // this.points.forEach(function(p) { + // p.x -= halfWidth; + // p.y -= halfHeight; + // }, this); + this.minX = minX; this.minY = minY; }, @@ -108,6 +117,7 @@ if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.closePath(); ctx.stroke(); diff --git a/src/polyline.class.js b/src/polyline.class.js index 21255d83..b725491f 100644 --- a/src/polyline.class.js +++ b/src/polyline.class.js @@ -92,6 +92,7 @@ if (this.fill) { ctx.fill(); } + this._removeShadow(ctx); if (this.stroke) { ctx.stroke(); } diff --git a/src/rect.class.js b/src/rect.class.js index f5585ccc..0a7d6bde 100644 --- a/src/rect.class.js +++ b/src/rect.class.js @@ -121,6 +121,8 @@ ctx.fill(); } + this._removeShadow(ctx); + if (this.strokeDashArray) { this._renderDashedStroke(ctx); } @@ -129,6 +131,67 @@ } }, + /** + * @private + * @method _renderDashedStroke + */ + _renderDashedStroke: function(ctx) { + + if (1 & this.strokeDashArray.length /* if odd number of items */) { + /* duplicate items */ + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + + var i = 0, + x = -this.width/2, y = -this.height/2, + _this = this, + padding = this.padding, + dashedArrayLength = this.strokeDashArray.length; + + ctx.save(); + ctx.beginPath(); + + /** @ignore */ + function renderSide(xMultiplier, yMultiplier) { + + var lineLength = 0, + lengthDiff = 0, + sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2; + + while (lineLength < sideLength) { + + var lengthOfSubPath = _this.strokeDashArray[i++]; + lineLength += lengthOfSubPath; + + if (lineLength > sideLength) { + lengthDiff = lineLength - sideLength; + } + + // track coords + if (xMultiplier) { + x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0); + } + else { + y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0); + } + + ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y); + if (i >= dashedArrayLength) { + i = 0; + } + } + } + + renderSide(1, 0); + renderSide(0, 1); + renderSide(-1, 0); + renderSide(0, -1); + + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + }, + /** * @method _normalizeLeftTopProperties * @private diff --git a/src/shadow.class.js b/src/shadow.class.js new file mode 100644 index 00000000..259c9b88 --- /dev/null +++ b/src/shadow.class.js @@ -0,0 +1,70 @@ +/** + * Shadow class + * @class Shadow + * @memberOf fabric + */ +fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ { + + /** + * Shadow color + * @property + * @type String + */ + color: 'rgb(0,0,0)', + + /** + * Shadow blur + * @property + * @type Number + */ + blur: 0, + + /** + * Shadow horizontal offset + * @property + * @type Number + */ + offsetX: 0, + + /** + * Shadow vertical offset + * @property + * @type Number + */ + offsetY: 0, + + /** + * Constructor + * @method initialize + * @param [options] Options object with any of color, blur, offsetX, offsetX properties + * @return {fabric.Shadow} thisArg + */ + initialize: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Returns object representation of a shadow + * @method toObject + * @return {Object} + */ + toObject: function() { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + }, + + /** + * Returns SVG representation of a shadow + * @method toSVG + * @return {String} + */ + toSVG: function() { + + } +}); \ No newline at end of file diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 95e25b7f..e43acd3e 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -148,6 +148,9 @@ if (options.backgroundImage) { this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); + } this.calcOffset(); }, @@ -211,6 +214,33 @@ return this; }, + /** + * Sets background color for this canvas + * @method setBackgroundColor + * @param {String|fabric.Pattern} Color of pattern to set background color to + * @param {Function} callback callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + */ + setBackgroundColor: function(backgroundColor, callback) { + if (backgroundColor.source) { + var _this = this; + fabric.util.loadImage(backgroundColor.source, function(img) { + _this.backgroundColor = new fabric.Pattern({ + source: img, + pattern: backgroundColor.pattern + }); + callback && callback(); + }); + } + else { + this.backgroundColor = backgroundColor; + callback && callback(); + } + + return this; + }, + /** * @private * @method _createCanvasElement @@ -232,12 +262,8 @@ * @param {HTMLElement} element */ _initCanvasElement: function(element) { - if (typeof element.getContext === 'undefined' && - typeof G_vmlCanvasManager !== 'undefined' && - G_vmlCanvasManager.initElement) { + fabric.util.createCanvasElement(element); - G_vmlCanvasManager.initElement(element); - } if (typeof element.getContext === 'undefined') { throw CANVAS_INIT_ERROR; } @@ -510,6 +536,7 @@ if (this.contextTop) { this.clearContext(this.contextTop); } + this.fire('canvas:cleared'); this.renderAll(); return this; }, @@ -533,12 +560,17 @@ this.clearContext(canvasToDrawOn); } + this.fire('before:render'); + if (this.clipTo) { this._clipCanvas(canvasToDrawOn); } if (this.backgroundColor) { - canvasToDrawOn.fillStyle = this.backgroundColor; + canvasToDrawOn.fillStyle = this.backgroundColor.toLive + ? this.backgroundColor.toLive(canvasToDrawOn) + : this.backgroundColor; + canvasToDrawOn.fillRect(0, 0, this.width, this.height); } @@ -546,8 +578,6 @@ this._drawBackroundImage(canvasToDrawOn); } - this.fire('before:render'); - var activeGroup = this.getActiveGroup(); for (var i = 0, length = this._objects.length; i < length; ++i) { if (!activeGroup || @@ -687,6 +717,8 @@ var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) ? canvasEl.toDataURL('image/' + format, quality) : canvasEl.toDataURL('image/' + format); + + this.contextTop && this.clearContext(this.contextTop); this.renderAll(); return data; }, @@ -740,6 +772,7 @@ this.setActiveObject(activeObject); } + this.contextTop && this.clearContext(this.contextTop); this.renderAll(); return dataURL; @@ -874,7 +907,9 @@ } return object; }, this), - background: this.backgroundColor + background: (this.backgroundColor && this.backgroundColor.toObject) + ? this.backgroundColor.toObject() + : this.backgroundColor }; if (this.backgroundImage) { data.backgroundImage = this.backgroundImage.src; @@ -894,13 +929,22 @@ * Returns SVG representation of canvas * @function * @method toSVG + * @param {Object} [options] Options for SVG output ("suppressPreamble: true" + * will start the svg output directly at "', - '', + toSVG: function(options) { + options || (options = { }); + var markup = []; + + if (!options.suppressPreamble) { + markup.push( + '', + '' + ); + } + markup.push( '', 'Created with Fabric.js ', fabric.version, '', fabric.createSVGFontFacesMarkup(this.getObjects()) - ]; + ); if (this.backgroundImage) { markup.push( @@ -959,14 +1003,22 @@ * @return {Object} removed object */ remove: function (object) { - removeFromArray(this._objects, object); + // removing active object should fire "selection:cleared" events if (this.getActiveObject() === object) { - - // removing active object should fire "selection:cleared" events this.fire('before:selection:cleared', { target: object }); this.discardActiveObject(); this.fire('selection:cleared'); } + + var objects = this._objects; + var index = objects.indexOf(object); + + // removing any object should fire "objct:removed" events + if (index !== -1) { + objects.splice(index,1); + this.fire('object:removed', { target: object }); + } + this.renderAll(); return object; }, @@ -1191,11 +1243,8 @@ * `null` if canvas element or context can not be initialized */ supports: function (methodName) { - var el = fabric.document.createElement('canvas'); + var el = fabric.util.createCanvasElement(); - if (typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(el); - } if (!el || !el.getContext) { return null; } diff --git a/src/text.class.js b/src/text.class.js index 4ecfe159..b012a34a 100644 --- a/src/text.class.js +++ b/src/text.class.js @@ -20,7 +20,7 @@ fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ { /** - * Font size + * Font size (in pixels) * @property * @type Number */ @@ -147,11 +147,7 @@ * @method _initDimensions */ _initDimensions: function() { - var canvasEl = fabric.document.createElement('canvas'); - - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } + var canvasEl = fabric.util.createCanvasElement(); this._render(canvasEl.getContext('2d')); }, @@ -322,8 +318,8 @@ * @method _setTextStyles */ _setTextStyles: function(ctx) { - ctx.fillStyle = this.fill.toLiveGradient - ? this.fill.toLiveGradient(ctx) + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) : this.fill; ctx.strokeStyle = this.strokeStyle; ctx.lineWidth = this.strokeWidth; diff --git a/src/util/dom_event.js b/src/util/dom_event.js index b34068dc..cda8ff07 100644 --- a/src/util/dom_event.js +++ b/src/util/dom_event.js @@ -172,27 +172,41 @@ * @method getPointer * @memberOf fabric.util * @param {Event} event + * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn */ - function getPointer(event) { + function getPointer(event, upperCanvasEl) { event || (event = fabric.window.event); var element = event.target || (typeof event.srcElement !== 'unknown' ? event.srcElement : null), + body = fabric.document.body || {scrollLeft: 0, scrollTop: 0}, + docElement = fabric.document.documentElement, + orgElement = element, scrollLeft = 0, scrollTop = 0, firstFixedAncestor; while (element && element.parentNode && !firstFixedAncestor) { - element = element.parentNode; + element = element.parentNode; - if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element; + if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element; + if (element !== fabric.document && orgElement !== upperCanvasEl && fabric.util.getElementPosition(element) === 'absolute') { + scrollLeft = 0; + scrollTop = 0; + } + else if (element === fabric.document && orgElement !== upperCanvasEl) { + scrollLeft = body.scrollLeft || docElement.scrollLeft || 0; + scrollTop = body.scrollTop || docElement.scrollTop || 0; + } + else { scrollLeft += element.scrollLeft || 0; scrollTop += element.scrollTop || 0; + } } return { - x: pointerX(event) + scrollLeft, - y: pointerY(event) + scrollTop + x: pointerX(event) + scrollLeft, + y: pointerY(event) + scrollTop }; } @@ -209,10 +223,10 @@ if (fabric.isTouchSupported) { pointerX = function(event) { - return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX; + return (event.touches && event.touches[0] ? (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX); }; pointerY = function(event) { - return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY; + return (event.touches && event.touches[0] ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY); }; } diff --git a/src/util/misc.js b/src/util/misc.js index 5e714d7c..9fb23f2d 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -1,5 +1,8 @@ (function() { + var sqrt = Math.sqrt, + atan2 = Math.atan2; + /** * @namespace */ @@ -295,6 +298,55 @@ } } + /** + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @method drawDashedLine + * @param ctx {Canvas} context + * @param x {Number} start x coordinate + * @param y {Number} start y coordinate + * @param x2 {Number} end x coordinate + * @param y2 {Number} end y coordinate + * @param da {Array} dash array pattern + */ + function drawDashedLine(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(); + } + + function createCanvasElement() { + var canvasEl = fabric.document.createElement('canvas'); + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); + } + return canvasEl; + } + fabric.util.removeFromArray = removeFromArray; fabric.util.degreesToRadians = degreesToRadians; fabric.util.radiansToDegrees = radiansToDegrees; @@ -308,4 +360,7 @@ fabric.util.enlivenObjects = enlivenObjects; fabric.util.groupSVGElements = groupSVGElements; fabric.util.populateWithProperties = populateWithProperties; + fabric.util.drawDashedLine = drawDashedLine; + fabric.util.createCanvasElement = createCanvasElement; + })(); \ No newline at end of file diff --git a/test.js b/test.js index c8cbda5b..bf93a19f 100644 --- a/test.js +++ b/test.js @@ -25,7 +25,9 @@ testrunner.run({ './test/unit/parser.js', './test/unit/canvas.js', './test/unit/canvas_static.js', - './test/unit/gradient.js' + './test/unit/gradient.js', + './test/unit/pattern.js', + './test/unit/shadow.js' ] }, function(err, report) { if(report.failed > 0){ diff --git a/test/fixtures/greyfloral.png b/test/fixtures/greyfloral.png new file mode 100644 index 00000000..91525ccd Binary files /dev/null and b/test/fixtures/greyfloral.png differ diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 68a7e0c7..b210e028 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -23,12 +23,12 @@ var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+ '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+ - '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,'+ + '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+ '"path":"http://example.com/"}],"background":""}'; var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+ '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+ - '"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],'+ + '"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+ '"background":"#ff5555"}'; var el = fabric.document.createElement('canvas'); diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index af6b5f03..1c6611d4 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -21,17 +21,17 @@ var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+ '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+ - '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,'+ + '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+ '"path":"http://example.com/"}],"background":""}'; var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+ '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+ - '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],'+ + '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+ '"background":"#ff5555"}'; var RECT_JSON_WITH_PADDING = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)","overlayFill":null,'+ '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+ - '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"padding":123,"foo":"bar","rx":0,"ry":0}],'+ + '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"padding":123,"foo":"bar","rx":0,"ry":0}],'+ '"background":""}'; // force creation of static canvas @@ -193,6 +193,15 @@ equal(rect.getAngle(), 90, 'angle should be coerced to 90 (from 100)'); }); + test('toSVG without preamble', function() { + ok(typeof canvas.toSVG == 'function'); + var withPreamble = canvas.toSVG(); + var withoutPreamble = canvas.toSVG({suppressPreamble: true}); + ok(withPreamble != withoutPreamble); + equal(withoutPreamble.slice(0, 4), ' "src" + if (img.src) { + equal(object.source, '../fixtures/greyfloral.png'); + } + equal(object.repeat, 'repeat'); + + var sourceExecuted; + var patternWithGetSource = new fabric.Pattern({ + source: function() {return fabric.document.createElement("canvas")} + }); + + var object2 = patternWithGetSource.toObject(); + equal(object2.source, 'return fabric.document.createElement("canvas")'); + equal(object2.repeat, 'repeat'); + }); + + test('toLive', function() { + var pattern = createPattern(); + + ok(typeof pattern.toLive == 'function'); + }); + +})(); \ No newline at end of file diff --git a/test/unit/polygon.js b/test/unit/polygon.js index ac4937da..4761e98b 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -32,7 +32,8 @@ 'hasBorders': true, 'hasRotatingPoint': true, 'transparentCorners': true, - 'perPixelTargetFind': false + 'perPixelTargetFind': false, + 'shadow': null }; QUnit.module('fabric.Polygon'); @@ -46,7 +47,7 @@ ok(polygon instanceof fabric.Object); equal(polygon.type, 'polygon'); - deepEqual(getPoints(), polygon.get('points')); + deepEqual([ { x: 5, y: 7 }, { x: 15, y: 17 } ], polygon.get('points')); }); test('complexity', function() { @@ -58,7 +59,11 @@ var polygon = new fabric.Polygon(getPoints()); ok(typeof polygon.toObject == 'function'); - deepEqual(REFERENCE_OBJECT, polygon.toObject()); + var objectWithOriginalPoints = fabric.util.object.extend(polygon.toObject(), { + points: getPoints() + }); + + deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); }); test('fromObject', function() { @@ -89,7 +94,12 @@ elPolygonWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)'); var polygonWithAttrs = fabric.Polygon.fromElement(elPolygonWithAttrs); - var expectedPoints = [{x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 10, y: 10}]; + var expectedPoints = [ + { x: 0, y: 0 }, + { x: 10, y: 10 }, + { x: 20, y: 20 }, + { x: 0, y: 0 } + ]; deepEqual(fabric.util.object.extend(REFERENCE_OBJECT, { 'width': 20, diff --git a/test/unit/polyline.js b/test/unit/polyline.js index ab4c23ad..6aa2aa0a 100644 --- a/test/unit/polyline.js +++ b/test/unit/polyline.js @@ -32,7 +32,8 @@ 'hasBorders': true, 'hasRotatingPoint': true, 'transparentCorners': true, - 'perPixelTargetFind': false + 'perPixelTargetFind': false, + 'shadow': null }; QUnit.module('fabric.Polyline'); @@ -46,7 +47,7 @@ ok(polyline instanceof fabric.Object); equal(polyline.type, 'polyline'); - deepEqual(polyline.get('points'), getPoints()); + deepEqual(polyline.get('points'), [ { x: 5, y: 7 }, { x: 15, y: 17 } ]); }); test('complexity', function() { @@ -57,7 +58,11 @@ test('toObject', function() { var polyline = new fabric.Polyline(getPoints()); ok(typeof polyline.toObject == 'function'); - deepEqual(polyline.toObject(), REFERENCE_OBJECT); + var objectWithOriginalPoints = fabric.util.object.extend(polyline.toObject(), { + points: getPoints() + }); + + deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); }); test('fromObject', function() { diff --git a/test/unit/rect.js b/test/unit/rect.js index 7a36578a..d43dba66 100644 --- a/test/unit/rect.js +++ b/test/unit/rect.js @@ -25,6 +25,7 @@ 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, + 'shadow': null, 'rx': 0, 'ry': 0 }; diff --git a/test/unit/shadow.js b/test/unit/shadow.js new file mode 100644 index 00000000..17532997 --- /dev/null +++ b/test/unit/shadow.js @@ -0,0 +1,34 @@ +(function() { + + QUnit.module('fabric.Shadow'); + + test('constructor', function() { + ok(fabric.Shadow); + + var shadow = new fabric.Shadow(); + ok(shadow instanceof fabric.Shadow, 'should inherit from fabric.Shadow'); + }); + + test('properties', function() { + var shadow = new fabric.Shadow(); + + equal(shadow.blur, 0); + equal(shadow.color, 'rgb(0,0,0)'); + equal(shadow.offsetX, 0); + equal(shadow.offsetY, 0); + }); + + test('toObject', function() { + var shadow = new fabric.Shadow(); + ok(typeof shadow.toObject == 'function'); + + var object = shadow.toObject(); + equal(JSON.stringify(object), '{"color":"rgb(0,0,0)","blur":0,"offsetX":0,"offsetY":0}'); + }); + + // TODO: implement and test this + // test('toSVG', function() { + // + // }); + +})(); \ No newline at end of file diff --git a/test/unit/text.js b/test/unit/text.js index 8e8efca5..c85dd2d5 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -31,6 +31,7 @@ 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, + 'shadow': null, 'text': 'x', 'fontSize': 40, 'fontWeight': 400,