diff --git a/.jshintignore b/.jshintignore index 11dc50f34..ae60d4ba0 100644 --- a/.jshintignore +++ b/.jshintignore @@ -4,7 +4,6 @@ www/code/codemirror* www/common/chainpad.js storage/kad.js www/common/otaml.js -www/whiteboard/fabric.js server.js NetFluxWebsocketSrv.js diff --git a/www/whiteboard/fabric.js b/www/whiteboard/fabric.js deleted file mode 100644 index 4c885f95f..000000000 --- a/www/whiteboard/fabric.js +++ /dev/null @@ -1,26125 +0,0 @@ -/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ - /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ - -var fabric = fabric || { version: "1.6.7" }; -if (typeof exports !== 'undefined') { - exports.fabric = fabric; -} - -if (typeof document !== 'undefined' && typeof window !== 'undefined') { - fabric.document = document; - fabric.window = window; - // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) - window.fabric = fabric; -} -else { - // assume we're running under node.js when document/window are not present - fabric.document = require("jsdom") - .jsdom(""); - - if (fabric.document.createWindow) { - fabric.window = fabric.document.createWindow(); - } else { - fabric.window = fabric.document.parentWindow; - } -} - -/** - * True when in environment that supports touch events - * @type boolean - */ -fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; - -/** - * True when in environment that's probably Node.js - * @type boolean - */ -fabric.isLikelyNode = typeof Buffer !== 'undefined' && - typeof window === 'undefined'; - -/* _FROM_SVG_START_ */ -/** - * Attributes parsed from all SVG elements - * @type array - */ -fabric.SHARED_ATTRIBUTES = [ - "display", - "transform", - "fill", "fill-opacity", "fill-rule", - "opacity", - "stroke", "stroke-dasharray", "stroke-linecap", - "stroke-linejoin", "stroke-miterlimit", - "stroke-opacity", "stroke-width", - "id" -]; -/* _FROM_SVG_END_ */ - -/** - * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. - */ -fabric.DPI = 96; -fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; -fabric.fontPaths = { }; - -/** - * Cache Object for widths of chars in text rendering. - */ -fabric.charWidthsCache = { }; - -/** - * Device Pixel Ratio - * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html - */ -fabric.devicePixelRatio = fabric.window.devicePixelRatio || - fabric.window.webkitDevicePixelRatio || - fabric.window.mozDevicePixelRatio || - 1; - - -(function() { - - /** - * @private - * @param {String} eventName - * @param {Function} handler - */ - function _removeEventListener(eventName, handler) { - if (!this.__eventListeners[eventName]) { - return; - } - var eventListener = this.__eventListeners[eventName]; - if (handler) { - eventListener[eventListener.indexOf(handler)] = false; - } - else { - fabric.util.array.fill(eventListener, false); - } - } - - /** - * Observes specified event - * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) - * @memberOf fabric.Observable - * @alias on - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function that receives a notification when an event of the specified type occurs - * @return {Self} thisArg - * @chainable - */ - function observe(eventName, handler) { - if (!this.__eventListeners) { - this.__eventListeners = { }; - } - // one object with key/value pairs was passed - if (arguments.length === 1) { - for (var prop in eventName) { - this.on(prop, eventName[prop]); - } - } - else { - if (!this.__eventListeners[eventName]) { - this.__eventListeners[eventName] = []; - } - this.__eventListeners[eventName].push(handler); - } - return this; - } - - /** - * Stops event observing for a particular event handler. Calling this method - * without arguments removes all handlers for all events - * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) - * @memberOf fabric.Observable - * @alias off - * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) - * @param {Function} handler Function to be deleted from EventListeners - * @return {Self} thisArg - * @chainable - */ - function stopObserving(eventName, handler) { - if (!this.__eventListeners) { - return; - } - - // remove all key/value pairs (event name -> event handler) - if (arguments.length === 0) { - for (eventName in this.__eventListeners) { - _removeEventListener.call(this, eventName); - } - } - // one object with key/value pairs was passed - else if (arguments.length === 1 && typeof arguments[0] === 'object') { - for (var prop in eventName) { - _removeEventListener.call(this, prop, eventName[prop]); - } - } - else { - _removeEventListener.call(this, eventName, handler); - } - return this; - } - - /** - * Fires event with an optional options object - * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) - * @memberOf fabric.Observable - * @alias trigger - * @param {String} eventName Event name to fire - * @param {Object} [options] Options object - * @return {Self} thisArg - * @chainable - */ - function fire(eventName, options) { - if (!this.__eventListeners) { - return; - } - - var listenersForEvent = this.__eventListeners[eventName]; - if (!listenersForEvent) { - return; - } - - for (var i = 0, len = listenersForEvent.length; i < len; i++) { - listenersForEvent[i] && listenersForEvent[i].call(this, options || { }); - } - this.__eventListeners[eventName] = listenersForEvent.filter(function(value) { - return value !== false; - }); - return this; - } - - /** - * @namespace fabric.Observable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} - * @see {@link http://fabricjs.com/events|Events demo} - */ - fabric.Observable = { - observe: observe, - stopObserving: stopObserving, - fire: fire, - - on: observe, - off: stopObserving, - trigger: fire - }; -})(); - - -/** - * @namespace fabric.Collection - */ -fabric.Collection = { - - _objects: [], - - /** - * Adds objects to collection, Canvas or Group, then renders canvas - * (if `renderOnAddRemove` is not `false`). - * in case of Group no changes to bounding box are made. - * Objects should be instances of (or inherit from) fabric.Object - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - * @chainable - */ - add: function () { - this._objects.push.apply(this._objects, arguments); - if (this._onObjectAdded) { - for (var i = 0, length = arguments.length; i < length; i++) { - this._onObjectAdded(arguments[i]); - } - } - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) - * An object should be an instance of (or inherit from) fabric.Object - * @param {Object} object Object to insert - * @param {Number} index Index to insert object at - * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs - * @return {Self} thisArg - * @chainable - */ - insertAt: function (object, index, nonSplicing) { - var objects = this.getObjects(); - if (nonSplicing) { - objects[index] = object; - } - else { - objects.splice(index, 0, object); - } - this._onObjectAdded && this._onObjectAdded(object); - this.renderOnAddRemove && this.renderAll(); - return this; - }, - - /** - * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {...fabric.Object} object Zero or more fabric instances - * @return {Self} thisArg - * @chainable - */ - remove: function() { - var objects = this.getObjects(), - index, somethingRemoved = false; - - for (var i = 0, length = arguments.length; i < length; i++) { - index = objects.indexOf(arguments[i]); - - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - somethingRemoved = true; - objects.splice(index, 1); - this._onObjectRemoved && this._onObjectRemoved(arguments[i]); - } - } - - this.renderOnAddRemove && somethingRemoved && this.renderAll(); - return this; - }, - - /** - * Executes given function for each object in this group - * @param {Function} callback - * Callback invoked with current object as first argument, - * index - as second and an array of all objects - as third. - * Callback is invoked in a context of Global Object (e.g. `window`) - * when no `context` argument is given - * - * @param {Object} context Context (aka thisObject) - * @return {Self} thisArg - * @chainable - */ - forEachObject: function(callback, context) { - var objects = this.getObjects(); - for (var i = 0, len = objects.length; i < len; i++) { - callback.call(context, objects[i], i, objects); - } - return this; - }, - - /** - * Returns an array of children objects of this instance - * Type parameter introduced in 1.3.10 - * @param {String} [type] When specified, only objects of this type are returned - * @return {Array} - */ - getObjects: function(type) { - if (typeof type === 'undefined') { - return this._objects; - } - return this._objects.filter(function(o) { - return o.type === type; - }); - }, - - /** - * Returns object at specified index - * @param {Number} index - * @return {Self} thisArg - */ - item: function (index) { - return this.getObjects()[index]; - }, - - /** - * Returns true if collection contains no objects - * @return {Boolean} true if collection is empty - */ - isEmpty: function () { - return this.getObjects().length === 0; - }, - - /** - * Returns a size of a collection (i.e: length of an array containing its objects) - * @return {Number} Collection size - */ - size: function() { - return this.getObjects().length; - }, - - /** - * Returns true if collection contains an object - * @param {Object} object Object to check against - * @return {Boolean} `true` if collection contains an object - */ - contains: function(object) { - return this.getObjects().indexOf(object) > -1; - }, - - /** - * Returns number representation of a collection complexity - * @return {Number} complexity - */ - complexity: function () { - return this.getObjects().reduce(function (memo, current) { - memo += current.complexity ? current.complexity() : 0; - return memo; - }, 0); - } -}; - - -(function(global) { - - var sqrt = Math.sqrt, - atan2 = Math.atan2, - pow = Math.pow, - abs = Math.abs, - PiBy180 = Math.PI / 180; - - /** - * @namespace fabric.util - */ - fabric.util = { - - /** - * Removes value from an array. - * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` - * @static - * @memberOf fabric.util - * @param {Array} array - * @param {*} value - * @return {Array} original array - */ - removeFromArray: function(array, value) { - var idx = array.indexOf(value); - if (idx !== -1) { - array.splice(idx, 1); - } - return array; - }, - - /** - * Returns random number between 2 specified ones. - * @static - * @memberOf fabric.util - * @param {Number} min lower limit - * @param {Number} max upper limit - * @return {Number} random value (between min and max) - */ - getRandomInt: function(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }, - - /** - * Transforms degrees to radians. - * @static - * @memberOf fabric.util - * @param {Number} degrees value in degrees - * @return {Number} value in radians - */ - degreesToRadians: function(degrees) { - return degrees * PiBy180; - }, - - /** - * Transforms radians to degrees. - * @static - * @memberOf fabric.util - * @param {Number} radians value in radians - * @return {Number} value in degrees - */ - radiansToDegrees: function(radians) { - return radians / PiBy180; - }, - - /** - * Rotates `point` around `origin` with `radians` - * @static - * @memberOf fabric.util - * @param {fabric.Point} point The point to rotate - * @param {fabric.Point} origin The origin of the rotation - * @param {Number} radians The radians of the angle for the rotation - * @return {fabric.Point} The new rotated point - */ - rotatePoint: function(point, origin, radians) { - point.subtractEquals(origin); - var v = fabric.util.rotateVector(point, radians); - return new fabric.Point(v.x, v.y).addEquals(origin); - }, - - /** - * Rotates `vector` with `radians` - * @static - * @memberOf fabric.util - * @param {Object} vector The vector to rotate (x and y) - * @param {Number} radians The radians of the angle for the rotation - * @return {Object} The new rotated point - */ - rotateVector: function(vector, radians) { - var sin = Math.sin(radians), - cos = Math.cos(radians), - rx = vector.x * cos - vector.y * sin, - ry = vector.x * sin + vector.y * cos; - return { - x: rx, - y: ry - }; - }, - - /** - * Apply transform t to point p - * @static - * @memberOf fabric.util - * @param {fabric.Point} p The point to transform - * @param {Array} t The transform - * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied - * @return {fabric.Point} The transformed point - */ - transformPoint: function(p, t, ignoreOffset) { - if (ignoreOffset) { - return new fabric.Point( - t[0] * p.x + t[2] * p.y, - t[1] * p.x + t[3] * p.y - ); - } - return new fabric.Point( - t[0] * p.x + t[2] * p.y + t[4], - t[1] * p.x + t[3] * p.y + t[5] - ); - }, - - /** - * Returns coordinates of points's bounding rectangle (left, top, width, height) - * @param {Array} points 4 points array - * @return {Object} Object with left, top, width, height properties - */ - makeBoundingBoxFromPoints: function(points) { - var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], - minX = fabric.util.array.min(xPoints), - maxX = fabric.util.array.max(xPoints), - width = Math.abs(minX - maxX), - yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], - minY = fabric.util.array.min(yPoints), - maxY = fabric.util.array.max(yPoints), - height = Math.abs(minY - maxY); - - return { - left: minX, - top: minY, - width: width, - height: height - }; - }, - - /** - * Invert transformation t - * @static - * @memberOf fabric.util - * @param {Array} t The transform - * @return {Array} The inverted transform - */ - invertTransform: function(t) { - var a = 1 / (t[0] * t[3] - t[1] * t[2]), - r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], - o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); - r[4] = -o.x; - r[5] = -o.y; - return r; - }, - - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static - * @memberOf fabric.util - * @param {Number|String} number number to operate on - * @param {Number} fractionDigits number of fraction digits to "leave" - * @return {Number} - */ - toFixed: function(number, fractionDigits) { - return parseFloat(Number(number).toFixed(fractionDigits)); - }, - - /** - * Converts from attribute value to pixel value if applicable. - * Returns converted pixels or original value not converted. - * @param {Number|String} value number to operate on - * @param {Number} fontSize - * @return {Number|String} - */ - parseUnit: function(value, fontSize) { - var unit = /\D{0,2}$/.exec(value), - number = parseFloat(value); - if (!fontSize) { - fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - switch (unit[0]) { - case 'mm': - return number * fabric.DPI / 25.4; - - case 'cm': - return number * fabric.DPI / 2.54; - - case 'in': - return number * fabric.DPI; - - case 'pt': - return number * fabric.DPI / 72; // or * 4 / 3 - - case 'pc': - return number * fabric.DPI / 72 * 12; // or * 16 - - case 'em': - return number * fontSize; - - default: - return number; - } - }, - - /** - * Function which always returns `false`. - * @static - * @memberOf fabric.util - * @return {Boolean} - */ - falseFunction: function() { - return false; - }, - - /** - * Returns klass "Class" object of given namespace - * @memberOf fabric.util - * @param {String} type Type of object (eg. 'circle') - * @param {String} namespace Namespace to get klass "Class" object from - * @return {Object} klass "Class" - */ - getKlass: function(type, namespace) { - // capitalize first letter only - type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); - return fabric.util.resolveNamespace(namespace)[type]; - }, - - /** - * Returns object of given namespace - * @memberOf fabric.util - * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' - * @return {Object} Object for given namespace (default fabric) - */ - resolveNamespace: function(namespace) { - if (!namespace) { - return fabric; - } - - var parts = namespace.split('.'), - len = parts.length, i, - obj = global || fabric.window; - - for (i = 0; i < len; ++i) { - obj = obj[parts[i]]; - } - - return obj; - }, - - /** - * Loads image element from given url and passes it to a callback - * @memberOf fabric.util - * @param {String} url URL representing an image - * @param {Function} callback Callback; invoked with loaded image - * @param {*} [context] Context to invoke callback in - * @param {Object} [crossOrigin] crossOrigin value to set image element to - */ - loadImage: function(url, callback, context, crossOrigin) { - if (!url) { - callback && callback.call(context, url); - return; - } - - var img = fabric.util.createImage(); - - /** @ignore */ - img.onload = function () { - callback && callback.call(context, img); - img = img.onload = img.onerror = null; - }; - - /** @ignore */ - img.onerror = function() { - fabric.log('Error loading ' + img.src); - callback && callback.call(context, null, true); - img = img.onload = img.onerror = null; - }; - - // data-urls appear to be buggy with crossOrigin - // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 - // see https://code.google.com/p/chromium/issues/detail?id=315152 - // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 - if (url.indexOf('data') !== 0 && crossOrigin) { - img.crossOrigin = crossOrigin; - } - - img.src = url; - }, - - /** - * Creates corresponding fabric instances from their object representations - * @static - * @memberOf fabric.util - * @param {Array} objects Objects to enliven - * @param {Function} callback Callback to invoke when all objects are created - * @param {String} namespace Namespace to get klass "Class" object from - * @param {Function} reviver Method for further parsing of object elements, - * called after each fabric object created. - */ - enlivenObjects: function(objects, callback, namespace, reviver) { - objects = objects || []; - - function onLoaded() { - if (++numLoadedObjects === numTotalObjects) { - callback && callback(enlivenedObjects); - } - } - - var enlivenedObjects = [], - numLoadedObjects = 0, - numTotalObjects = objects.length; - - if (!numTotalObjects) { - callback && callback(enlivenedObjects); - return; - } - - objects.forEach(function (o, index) { - // if sparse array - if (!o || !o.type) { - onLoaded(); - return; - } - var klass = fabric.util.getKlass(o.type, namespace); - if (klass.async) { - klass.fromObject(o, function (obj, error) { - if (!error) { - enlivenedObjects[index] = obj; - reviver && reviver(o, enlivenedObjects[index]); - } - onLoaded(); - }); - } - else { - enlivenedObjects[index] = klass.fromObject(o); - reviver && reviver(o, enlivenedObjects[index]); - onLoaded(); - } - }); - }, - - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @param {Object} [options] Options object - * @param {String} path Value to set sourcePath to - * @return {fabric.Object|fabric.PathGroup} - */ - groupSVGElements: function(elements, options, path) { - var object; - - object = new fabric.PathGroup(elements, options); - - if (typeof path !== 'undefined') { - object.setSourcePath(path); - } - return object; - }, - - /** - * Populates an object with properties of another object - * @static - * @memberOf fabric.util - * @param {Object} source Source object - * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include - */ - populateWithProperties: function(source, destination, properties) { - if (properties && Object.prototype.toString.call(properties) === '[object Array]') { - for (var i = 0, len = properties.length; i < len; i++) { - if (properties[i] in source) { - destination[properties[i]] = source[properties[i]]; - } - } - } - }, - - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x start x coordinate - * @param {Number} y start y coordinate - * @param {Number} x2 end x coordinate - * @param {Number} y2 end y coordinate - * @param {Array} da dash array pattern - */ - drawDashedLine: function(ctx, x, y, x2, y2, da) { - var dx = x2 - x, - dy = y2 - y, - len = sqrt(dx * dx + dy * dy), - rot = atan2(dy, dx), - dc = da.length, - di = 0, - draw = true; - - ctx.save(); - ctx.translate(x, y); - ctx.moveTo(0, 0); - ctx.rotate(rot); - - x = 0; - while (len > x) { - x += da[di++ % dc]; - if (x > len) { - x = len; - } - ctx[draw ? 'lineTo' : 'moveTo'](x, 0); - draw = !draw; - } - - ctx.restore(); - }, - - /** - * Creates canvas element and initializes it via excanvas if necessary - * @static - * @memberOf fabric.util - * @param {CanvasElement} [canvasEl] optional canvas element to initialize; - * when not given, element is created implicitly - * @return {CanvasElement} initialized canvas element - */ - createCanvasElement: function(canvasEl) { - canvasEl || (canvasEl = fabric.document.createElement('canvas')); - /* eslint-disable camelcase */ - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - /* eslint-enable camelcase */ - return canvasEl; - }, - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - createImage: function() { - return fabric.isLikelyNode - ? new (require('canvas').Image)() - : fabric.document.createElement('img'); - }, - - /** - * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array - * @static - * @memberOf fabric.util - * @param {Object} klass "Class" to create accessors for - */ - createAccessors: function(klass) { - var proto = klass.prototype, i, propName, - capitalizedPropName, setterName, getterName; - - for (i = proto.stateProperties.length; i--; ) { - - 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) { - var that = this; - return function () { this.get(property); }; //return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - var that = this; - return function () { return this.get(property); }; // new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } - }, - - /** - * @static - * @memberOf fabric.util - * @param {fabric.Object} receiver Object implementing `clipTo` method - * @param {CanvasRenderingContext2D} ctx Context to clip - */ - clipContext: function(receiver, ctx) { - ctx.save(); - ctx.beginPath(); - receiver.clipTo(ctx); - ctx.clip(); - }, - - /** - * Multiply matrix A by matrix B to nest transformations - * @static - * @memberOf fabric.util - * @param {Array} a First transformMatrix - * @param {Array} b Second transformMatrix - * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices - * @return {Array} The product of the two transform matrices - */ - multiplyTransformMatrices: function(a, b, is2x2) { - // Matrix multiply a * b - return [ - a[0] * b[0] + a[2] * b[1], - a[1] * b[0] + a[3] * b[1], - a[0] * b[2] + a[2] * b[3], - a[1] * b[2] + a[3] * b[3], - is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], - is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5] - ]; - }, - - /** - * Decomposes standard 2x2 matrix into transform componentes - * @static - * @memberOf fabric.util - * @param {Array} a transformMatrix - * @return {Object} Components of transform - */ - qrDecompose: function(a) { - var angle = atan2(a[1], a[0]), - denom = pow(a[0], 2) + pow(a[1], 2), - scaleX = sqrt(denom), - scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX, - skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); - return { - angle: angle / PiBy180, - scaleX: scaleX, - scaleY: scaleY, - skewX: skewX / PiBy180, - skewY: 0, - translateX: a[4], - translateY: a[5] - }; - }, - - customTransformMatrix: function(scaleX, scaleY, skewX) { - var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], - scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; - return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true); - }, - - resetObjectTransform: function (target) { - target.scaleX = 1; - target.scaleY = 1; - target.skewX = 0; - target.skewY = 0; - target.flipX = false; - target.flipY = false; - target.setAngle(0); - }, - - /** - * Returns string representation of function body - * @param {Function} fn Function to get body of - * @return {String} Function body - */ - getFunctionBody: function(fn) { - return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; - }, - - /** - * Returns true if context has transparent pixel - * at specified location (taking tolerance into account) - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x x coordinate - * @param {Number} y y coordinate - * @param {Number} tolerance Tolerance - */ - isTransparent: function(ctx, x, y, tolerance) { - - // If tolerance is > 0 adjust start coords to take into account. - // If moves off Canvas fix to 0 - if (tolerance > 0) { - if (x > tolerance) { - x -= tolerance; - } - else { - x = 0; - } - if (y > tolerance) { - y -= tolerance; - } - else { - y = 0; - } - } - - var _isTransparent = true, i, temp, - imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1), - l = imageData.data.length; - - // Split image data - for tolerance > 1, pixelDataSize = 4; - for (i = 3; i < l; i += 4) { - temp = imageData.data[i]; - _isTransparent = temp <= 0; - if (_isTransparent === false) { - break; // Stop if colour found - } - } - - imageData = null; - - return _isTransparent; - }, - - /** - * Parse preserveAspectRatio attribute from element - * @param {string} attribute to be parsed - * @return {Object} an object containing align and meetOrSlice attribute - */ - parsePreserveAspectRatioAttribute: function(attribute) { - var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', - aspectRatioAttrs = attribute.split(' '), align; - - if (aspectRatioAttrs && aspectRatioAttrs.length) { - meetOrSlice = aspectRatioAttrs.pop(); - if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { - align = meetOrSlice; - meetOrSlice = 'meet'; - } - else if (aspectRatioAttrs.length) { - align = aspectRatioAttrs.pop(); - } - } - //divide align in alignX and alignY - alignX = align !== 'none' ? align.slice(1, 4) : 'none'; - alignY = align !== 'none' ? align.slice(5, 8) : 'none'; - return { - meetOrSlice: meetOrSlice, - alignX: alignX, - alignY: alignY - }; - }, - - /** - * Clear char widths cache for a font family. - * @memberOf fabric.util - * @param {String} [fontFamily] font family to clear - */ - clearFabricFontCache: function(fontFamily) { - if (!fontFamily) { - fabric.charWidthsCache = { }; - } - else if (fabric.charWidthsCache[fontFamily]) { - delete fabric.charWidthsCache[fontFamily]; - } - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var arcToSegmentsCache = { }, - segmentToBezierCache = { }, - boundsOfCurveCache = { }, - _join = Array.prototype.join; - - /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp - * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here - * http://mozilla.org/MPL/2.0/ - */ - function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { - var argsString = _join.call(arguments); - if (arcToSegmentsCache[argsString]) { - return arcToSegmentsCache[argsString]; - } - - var PI = Math.PI, th = rotateX * PI / 180, - sinTh = Math.sin(th), - cosTh = Math.cos(th), - fromX = 0, fromY = 0; - - rx = Math.abs(rx); - ry = Math.abs(ry); - - var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, - py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, - rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, - pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, - root = 0; - - if (pl < 0) { - var s = Math.sqrt(1 - pl / (rx2 * ry2)); - rx *= s; - ry *= s; - } - else { - root = (large === sweep ? -1.0 : 1.0) * - Math.sqrt( pl / (rx2 * py2 + ry2 * px2)); - } - - var cx = root * rx * py / ry, - cy = -root * ry * px / rx, - cx1 = cosTh * cx - sinTh * cy + toX * 0.5, - cy1 = sinTh * cx + cosTh * cy + toY * 0.5, - mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), - dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); - - if (sweep === 0 && dtheta > 0) { - dtheta -= 2 * PI; - } - else if (sweep === 1 && dtheta < 0) { - dtheta += 2 * PI; - } - - // Convert into cubic bezier segments <= 90deg - var segments = Math.ceil(Math.abs(dtheta / PI * 2)), - result = [], mDelta = dtheta / segments, - mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), - th3 = mTheta + mDelta; - - for (var i = 0; i < segments; i++) { - result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); - fromX = result[i][4]; - fromY = result[i][5]; - mTheta = th3; - th3 += mDelta; - } - arcToSegmentsCache[argsString] = result; - return result; - } - - function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { - var argsString2 = _join.call(arguments); - if (segmentToBezierCache[argsString2]) { - return segmentToBezierCache[argsString2]; - } - - var costh2 = Math.cos(th2), - sinth2 = Math.sin(th2), - costh3 = Math.cos(th3), - sinth3 = Math.sin(th3), - toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, - toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, - cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2), - cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2), - cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), - cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); - - segmentToBezierCache[argsString2] = [ - cp1X, cp1Y, - cp2X, cp2Y, - toX, toY - ]; - return segmentToBezierCache[argsString2]; - } - - /* - * Private - */ - function calcVectorAngle(ux, uy, vx, vy) { - var ta = Math.atan2(uy, ux), - tb = Math.atan2(vy, vx); - if (tb >= ta) { - return tb - ta; - } - else { - return 2 * Math.PI - (ta - tb); - } - } - - /** - * Draws arc - * @param {CanvasRenderingContext2D} ctx - * @param {Number} fx - * @param {Number} fy - * @param {Array} coords - */ - fabric.util.drawArc = function(ctx, fx, fy, coords) { - var rx = coords[0], - ry = coords[1], - rot = coords[2], - large = coords[3], - sweep = coords[4], - tx = coords[5], - ty = coords[6], - segs = [[], [], [], []], - segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); - - for (var i = 0, len = segsNorm.length; i < len; i++) { - segs[i][0] = segsNorm[i][0] + fx; - segs[i][1] = segsNorm[i][1] + fy; - segs[i][2] = segsNorm[i][2] + fx; - segs[i][3] = segsNorm[i][3] + fy; - segs[i][4] = segsNorm[i][4] + fx; - segs[i][5] = segsNorm[i][5] + fy; - ctx.bezierCurveTo.apply(ctx, segs[i]); - } - }; - - /** - * Calculate bounding box of a elliptic-arc - * @param {Number} fx start point of arc - * @param {Number} fy - * @param {Number} rx horizontal radius - * @param {Number} ry vertical radius - * @param {Number} rot angle of horizontal axe - * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points - * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction - * @param {Number} tx end point of arc - * @param {Number} ty - */ - fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { - - var fromX = 0, fromY = 0, bound, bounds = [], - segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); - - for (var i = 0, len = segs.length; i < len; i++) { - bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); - bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); - bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); - fromX = segs[i][4]; - fromY = segs[i][5]; - } - return bounds; - }; - - /** - * Calculate bounding box of a beziercurve - * @param {Number} x0 starting point - * @param {Number} y0 - * @param {Number} x1 first control point - * @param {Number} y1 - * @param {Number} x2 secondo control point - * @param {Number} y2 - * @param {Number} x3 end of beizer - * @param {Number} y3 - */ - // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. - function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { - var argsString = _join.call(arguments); - if (boundsOfCurveCache[argsString]) { - return boundsOfCurveCache[argsString]; - } - - var sqrt = Math.sqrt, - min = Math.min, max = Math.max, - abs = Math.abs, tvalues = [], - bounds = [[], []], - a, b, c, t, t1, t2, b2ac, sqrtb2ac; - - b = 6 * x0 - 12 * x1 + 6 * x2; - a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; - c = 3 * x1 - 3 * x0; - - for (var i = 0; i < 2; ++i) { - if (i > 0) { - b = 6 * y0 - 12 * y1 + 6 * y2; - a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; - c = 3 * y1 - 3 * y0; - } - - if (abs(a) < 1e-12) { - if (abs(b) < 1e-12) { - continue; - } - t = -c / b; - if (0 < t && t < 1) { - tvalues.push(t); - } - continue; - } - b2ac = b * b - 4 * c * a; - if (b2ac < 0) { - continue; - } - sqrtb2ac = sqrt(b2ac); - t1 = (-b + sqrtb2ac) / (2 * a); - if (0 < t1 && t1 < 1) { - tvalues.push(t1); - } - t2 = (-b - sqrtb2ac) / (2 * a); - if (0 < t2 && t2 < 1) { - tvalues.push(t2); - } - } - - var x, y, j = tvalues.length, jlen = j, mt; - while (j--) { - t = tvalues[j]; - mt = 1 - t; - x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); - bounds[0][j] = x; - - y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); - bounds[1][j] = y; - } - - bounds[0][jlen] = x0; - bounds[1][jlen] = y0; - bounds[0][jlen + 1] = x3; - bounds[1][jlen + 1] = y3; - var result = [ - { - x: min.apply(null, bounds[0]), - y: min.apply(null, bounds[1]) - }, - { - x: max.apply(null, bounds[0]), - y: max.apply(null, bounds[1]) - } - ]; - boundsOfCurveCache[argsString] = result; - return result; - } - - fabric.util.getBoundsOfCurve = getBoundsOfCurve; - -})(); - - -(function() { - - var slice = Array.prototype.slice; - - /* _ES5_COMPAT_START_ */ - - if (!Array.prototype.indexOf) { - /** - * Finds index of an element in an array - * @param {*} searchElement - * @return {Number} - */ - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - if (this === void 0 || this === null) { - throw new TypeError(); - } - var t = Object(this), len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n !== n) { // shortcut for verifying if it's NaN - n = 0; - } - else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - }; - } - - if (!Array.prototype.forEach) { - /** - * Iterates an array, invoking callback for each element - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.forEach = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - fn.call(context, this[i], i, this); - } - } - }; - } - - if (!Array.prototype.map) { - /** - * Returns a result of iterating over an array, invoking callback for each element - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.map = function(fn, context) { - var result = []; - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - result[i] = fn.call(context, this[i], i, this); - } - } - return result; - }; - } - - if (!Array.prototype.every) { - /** - * Returns true if a callback returns truthy value for all elements in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Boolean} - */ - Array.prototype.every = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this && !fn.call(context, this[i], i, this)) { - return false; - } - } - return true; - }; - } - - if (!Array.prototype.some) { - /** - * Returns true if a callback returns truthy value for at least one element in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Boolean} - */ - Array.prototype.some = function(fn, context) { - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this && fn.call(context, this[i], i, this)) { - return true; - } - } - return false; - }; - } - - if (!Array.prototype.filter) { - /** - * Returns the result of iterating over elements in an array - * @param {Function} fn Callback to invoke for each element - * @param {Object} [context] Context to invoke callback in - * @return {Array} - */ - Array.prototype.filter = function(fn, context) { - var result = [], val; - for (var i = 0, len = this.length >>> 0; i < len; i++) { - if (i in this) { - val = this[i]; // in case fn mutates this - if (fn.call(context, val, i, this)) { - result.push(val); - } - } - } - return result; - }; - } - - if (!Array.prototype.reduce) { - /** - * Returns "folded" (reduced) result of iterating over elements in an array - * @param {Function} fn Callback to invoke for each element - * @return {*} - */ - Array.prototype.reduce = function(fn /*, initial*/) { - var len = this.length >>> 0, - i = 0, - rv; - - if (arguments.length > 1) { - rv = arguments[1]; - } - else { - do { - if (i in this) { - rv = this[i++]; - break; - } - // if array contains no values, no initial value to return - if (++i >= len) { - throw new TypeError(); - } - } - while (true); - } - for (; i < len; i++) { - if (i in this) { - rv = fn.call(null, rv, this[i], i, this); - } - } - return rv; - }; - } - - /* _ES5_COMPAT_END_ */ - - /** - * Invokes method on all items in a given array - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} method Name of a method to invoke - * @return {Array} - */ - function invoke(array, method) { - var args = slice.call(arguments, 2), result = []; - for (var i = 0, len = array.length; i < len; i++) { - result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); - } - return result; - } - - /** - * Finds maximum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {*} - */ - function max(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 >= value2; - }); - } - - /** - * Finds minimum value in array (not necessarily "first" one) - * @memberOf fabric.util.array - * @param {Array} array Array to iterate over - * @param {String} byProperty - * @return {*} - */ - function min(array, byProperty) { - return find(array, byProperty, function(value1, value2) { - return value1 < value2; - }); - } - - /** - * @private - */ - function fill(array, value) { - var k = array.length; - while (k--) { - array[k] = value; - } - return array; - } - - /** - * @private - */ - function find(array, byProperty, condition) { - if (!array || array.length === 0) { - return; - } - - var i = array.length - 1, - result = byProperty ? array[i][byProperty] : array[i]; - if (byProperty) { - while (i--) { - if (condition(array[i][byProperty], result)) { - result = array[i][byProperty]; - } - } - } - else { - while (i--) { - if (condition(array[i], result)) { - result = array[i]; - } - } - } - return result; - } - - /** - * @namespace fabric.util.array - */ - fabric.util.array = { - fill: fill, - invoke: invoke, - min: min, - max: max - }; - -})(); - - -(function() { - /** - * Copies all enumerable properties of one object to another - * @memberOf fabric.util.object - * @param {Object} destination Where to copy to - * @param {Object} source Where to copy from - * @return {Object} - */ - - function extend(destination, source, deep) { - // JScript DontEnum bug is not taken care of - // the deep clone is for internal use, is not meant to avoid - // javascript traps or cloning html element or self referenced objects. - if (deep) { - if (!fabric.isLikelyNode && source instanceof Element) { - // avoid cloning deep images, canvases, - destination = source; - } - else if (source instanceof Array) { - destination = source.map(function(v) { - return clone(v, deep) - }) - } - else if (source instanceof Object) { - for (var property in source) { - destination[property] = clone(source[property], deep) - } - } - else { - // this sounds odd for an extend but is ok for recursive use - destination = source; - } - } - else { - for (var property in source) { - destination[property] = source[property]; - } - } - return destination; - } - - /** - * Creates an empty object and copies all enumerable properties of another object to it - * @memberOf fabric.util.object - * @param {Object} object Object to clone - * @return {Object} - */ - function clone(object, deep) { - return extend({ }, object, deep); - } - - /** @namespace fabric.util.object */ - fabric.util.object = { - extend: extend, - clone: clone - }; - -})(); - - -(function() { - - /* _ES5_COMPAT_START_ */ - if (!String.prototype.trim) { - /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see String#trim on MDN - */ - String.prototype.trim = function () { - // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now - return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); - }; - } - /* _ES5_COMPAT_END_ */ - - /** - * Camelizes a string - * @memberOf fabric.util.string - * @param {String} string String to camelize - * @return {String} Camelized version of a string - */ - function camelize(string) { - return string.replace(/-+(.)?/g, function(match, character) { - return character ? character.toUpperCase() : ''; - }); - } - - /** - * Capitalizes a string - * @memberOf fabric.util.string - * @param {String} string String to capitalize - * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized - * and other letters stay untouched, if false first letter is capitalized - * and other letters are converted to lowercase. - * @return {String} Capitalized version of a string - */ - function capitalize(string, firstLetterOnly) { - return string.charAt(0).toUpperCase() + - (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); - } - - /** - * Escapes XML in a string - * @memberOf fabric.util.string - * @param {String} string String to escape - * @return {String} Escaped version of a string - */ - function escapeXml(string) { - return string.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>'); - } - - /** - * String utilities - * @namespace fabric.util.string - */ - fabric.util.string = { - camelize: camelize, - capitalize: capitalize, - escapeXml: escapeXml - }; -})(); - - -/* _ES5_COMPAT_START_ */ -(function() { - - var slice = Array.prototype.slice, - apply = Function.prototype.apply, - Dummy = function() { }; - - if (!Function.prototype.bind) { - /** - * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) - * @see Function#bind on MDN - * @param {Object} thisArg Object to bind function to - * @param {Any[]} Values to pass to a bound function - * @return {Function} - */ - Function.prototype.bind = function(thisArg) { - var _this = this, args = slice.call(arguments, 1), bound; - if (args.length) { - bound = function() { - return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); - }; - } - else { - /** @ignore */ - bound = function() { - return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); - }; - } - Dummy.prototype = this.prototype; - bound.prototype = new Dummy(); - - return bound; - }; - } - -})(); -/* _ES5_COMPAT_END_ */ - - -(function() { - - var slice = Array.prototype.slice, emptyFunction = function() { }, - - IS_DONTENUM_BUGGY = (function() { - for (var p in { toString: 1 }) { - if (p === 'toString') { - return false; - } - } - return true; - })(), - - /** @ignore */ - addMethods = function(klass, source, parent) { - for (var property in source) { - - if (property in klass.prototype && - typeof klass.prototype[property] === 'function' && - (source[property] + '').indexOf('callSuper') > -1) { - - klass.prototype[property] = (function(property) { - return function() { - - var superclass = this.constructor.superclass; - this.constructor.superclass = parent; - var returnValue = source[property].apply(this, arguments); - this.constructor.superclass = superclass; - - if (property !== 'initialize') { - return returnValue; - } - }; - })(property); - } - else { - klass.prototype[property] = source[property]; - } - - if (IS_DONTENUM_BUGGY) { - if (source.toString !== Object.prototype.toString) { - klass.prototype.toString = source.toString; - } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; - - function Subclass() { } - - function callSuper(methodName) { - var fn = this.constructor.superclass.prototype[methodName]; - return (arguments.length > 1) - ? fn.apply(this, slice.call(arguments, 1)) - : fn.call(this); - } - - /** - * Helper for creation of "classes". - * @memberOf fabric.util - * @param {Function} [parent] optional "Class" to inherit from - * @param {Object} [properties] Properties shared by all instances of this class - * (be careful modifying objects defined here as this would affect all instances) - */ - function createClass() { - var parent = null, - properties = slice.call(arguments, 0); - - if (typeof properties[0] === 'function') { - parent = properties.shift(); - } - function klass() { - this.initialize.apply(this, arguments); - } - - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - Subclass.prototype = parent.prototype; - klass.prototype = new Subclass(); - parent.subclasses.push(klass); - } - for (var i = 0, length = properties.length; i < length; i++) { - addMethods(klass, properties[i], parent); - } - if (!klass.prototype.initialize) { - klass.prototype.initialize = emptyFunction; - } - klass.prototype.constructor = klass; - klass.prototype.callSuper = callSuper; - return klass; - } - - fabric.util.createClass = createClass; -})(); - - -(function () { - - var unknown = 'unknown'; - - /* EVENT HANDLING */ - - function areHostMethods(object) { - var methodNames = Array.prototype.slice.call(arguments, 1), - t, i, len = methodNames.length; - for (i = 0; i < len; i++) { - t = typeof object[methodNames[i]]; - if (!(/^(?:function|object|unknown)$/).test(t)) { - return false; - } - } - return true; - } - - /** @ignore */ - var getElement, - setElement, - getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); - - (function () { - var elements = { }; - /** @ignore */ - getElement = function (uid) { - return elements[uid]; - }; - /** @ignore */ - setElement = function (uid, element) { - elements[uid] = element; - }; - })(); - - function createListener(uid, handler) { - return { - handler: handler, - wrappedHandler: createWrappedHandler(uid, handler) - }; - } - - function createWrappedHandler(uid, handler) { - return function (e) { - handler.call(getElement(uid), e || fabric.window.event); - }; - } - - function createDispatcher(uid, eventName) { - return function (e) { - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - handlersForEvent[i].call(this, e || fabric.window.event); - } - } - }; - } - - var shouldUseAddListenerRemoveListener = ( - areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && - areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), - - shouldUseAttachEventDetachEvent = ( - areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && - areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), - - // IE branch - listeners = { }, - - // DOM L0 branch - handlers = { }, - - addListener, removeListener; - - if (shouldUseAddListenerRemoveListener) { - /** @ignore */ - addListener = function (element, eventName, handler) { - element.addEventListener(eventName, handler, false); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - element.removeEventListener(eventName, handler, false); - }; - } - - else if (shouldUseAttachEventDetachEvent) { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - setElement(uid, element); - if (!listeners[uid]) { - listeners[uid] = { }; - } - if (!listeners[uid][eventName]) { - listeners[uid][eventName] = []; - - } - var listener = createListener(uid, handler); - listeners[uid][eventName].push(listener); - element.attachEvent('on' + eventName, listener.wrappedHandler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element), listener; - if (listeners[uid] && listeners[uid][eventName]) { - for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { - listener = listeners[uid][eventName][i]; - if (listener && listener.handler === handler) { - element.detachEvent('on' + eventName, listener.wrappedHandler); - listeners[uid][eventName][i] = null; - } - } - } - }; - } - else { - /** @ignore */ - addListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (!handlers[uid]) { - handlers[uid] = { }; - } - if (!handlers[uid][eventName]) { - handlers[uid][eventName] = []; - var existingHandler = element['on' + eventName]; - if (existingHandler) { - handlers[uid][eventName].push(existingHandler); - } - element['on' + eventName] = createDispatcher(uid, eventName); - } - handlers[uid][eventName].push(handler); - }; - /** @ignore */ - removeListener = function (element, eventName, handler) { - var uid = getUniqueId(element); - if (handlers[uid] && handlers[uid][eventName]) { - var handlersForEvent = handlers[uid][eventName]; - for (var i = 0, len = handlersForEvent.length; i < len; i++) { - if (handlersForEvent[i] === handler) { - handlersForEvent.splice(i, 1); - } - } - } - }; - } - - /** - * Adds an event listener to an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.addListener = addListener; - - /** - * Removes an event listener from an element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {String} eventName - * @param {Function} handler - */ - fabric.util.removeListener = removeListener; - - /** - * Cross-browser wrapper for getting event's coordinates - * @memberOf fabric.util - * @param {Event} event Event object - */ - function getPointer(event) { - event || (event = fabric.window.event); - - var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null), - - scroll = fabric.util.getScrollLeftTop(element); - - return { - x: pointerX(event) + scroll.left, - y: pointerY(event) + scroll.top - }; - } - - var pointerX = function(event) { - // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) - // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] - // need to investigate later - return (typeof event.clientX !== unknown ? event.clientX : 0); - }, - - pointerY = function(event) { - return (typeof event.clientY !== unknown ? event.clientY : 0); - }; - - function _getPointer(event, pageProp, clientProp) { - var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; - - return (event[touchProp] && event[touchProp][0] - ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) - || event[clientProp] - : event[clientProp]); - } - - if (fabric.isTouchSupported) { - pointerX = function(event) { - return _getPointer(event, 'pageX', 'clientX'); - }; - pointerY = function(event) { - return _getPointer(event, 'pageY', 'clientY'); - }; - } - - fabric.util.getPointer = getPointer; - - fabric.util.object.extend(fabric.util, fabric.Observable); - -})(); - - -(function () { - - /** - * Cross-browser wrapper for setting element's style - * @memberOf fabric.util - * @param {HTMLElement} element - * @param {Object} styles - * @return {HTMLElement} Element that was passed as a first argument - */ - function setStyle(element, styles) { - var elementStyle = element.style; - if (!elementStyle) { - return element; - } - if (typeof styles === 'string') { - element.style.cssText += ';' + styles; - return styles.indexOf('opacity') > -1 - ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) - : element; - } - for (var property in styles) { - if (property === 'opacity') { - setOpacity(element, styles[property]); - } - else { - var normalizedProperty = (property === 'float' || property === 'cssFloat') - ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') - : property; - elementStyle[normalizedProperty] = styles[property]; - } - } - return element; - } - - var parseEl = fabric.document.createElement('div'), - supportsOpacity = typeof parseEl.style.opacity === 'string', - supportsFilters = typeof parseEl.style.filter === 'string', - reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, - - /** @ignore */ - setOpacity = function (element) { return element; }; - - if (supportsOpacity) { - /** @ignore */ - setOpacity = function(element, value) { - element.style.opacity = value; - return element; - }; - } - else if (supportsFilters) { - /** @ignore */ - setOpacity = function(element, value) { - var es = element.style; - if (element.currentStyle && !element.currentStyle.hasLayout) { - es.zoom = 1; - } - if (reOpacity.test(es.filter)) { - value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); - es.filter = es.filter.replace(reOpacity, value); - } - else { - es.filter += ' alpha(opacity=' + (value * 100) + ')'; - } - return element; - }; - } - - fabric.util.setStyle = setStyle; - -})(); - - -(function() { - - var _slice = Array.prototype.slice; - - /** - * Takes id and returns an element with that id (if one exists in a document) - * @memberOf fabric.util - * @param {String|HTMLElement} id - * @return {HTMLElement|null} - */ - function getById(id) { - return typeof id === 'string' ? fabric.document.getElementById(id) : id; - } - - var sliceCanConvertNodelists, - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - toArray = function(arrayLike) { - return _slice.call(arrayLike, 0); - }; - - try { - sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; - } - catch (err) { } - - if (!sliceCanConvertNodelists) { - toArray = function(arrayLike) { - var arr = new Array(arrayLike.length), i = arrayLike.length; - while (i--) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - /** - * Creates specified element with specified attributes - * @memberOf fabric.util - * @param {String} tagName Type of an element to create - * @param {Object} [attributes] Attributes to set on an element - * @return {HTMLElement} Newly created element - */ - function makeElement(tagName, attributes) { - var el = fabric.document.createElement(tagName); - for (var prop in attributes) { - if (prop === 'class') { - el.className = attributes[prop]; - } - else if (prop === 'for') { - el.htmlFor = attributes[prop]; - } - else { - el.setAttribute(prop, attributes[prop]); - } - } - return el; - } - - /** - * Adds class to an element - * @memberOf fabric.util - * @param {HTMLElement} element Element to add class to - * @param {String} className Class to add to an element - */ - function addClass(element, className) { - if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { - element.className += (element.className ? ' ' : '') + className; - } - } - - /** - * Wraps element with another element - * @memberOf fabric.util - * @param {HTMLElement} element Element to wrap - * @param {HTMLElement|String} wrapper Element to wrap with - * @param {Object} [attributes] Attributes to set on a wrapper - * @return {HTMLElement} wrapper - */ - function wrapElement(element, wrapper, attributes) { - if (typeof wrapper === 'string') { - wrapper = makeElement(wrapper, attributes); - } - if (element.parentNode) { - element.parentNode.replaceChild(wrapper, element); - } - wrapper.appendChild(element); - return wrapper; - } - - /** - * Returns element scroll offsets - * @memberOf fabric.util - * @param {HTMLElement} element Element to operate on - * @return {Object} Object with left/top values - */ - function getScrollLeftTop(element) { - - var left = 0, - top = 0, - docElement = fabric.document.documentElement, - body = fabric.document.body || { - scrollLeft: 0, scrollTop: 0 - }; - - // While loop checks (and then sets element to) .parentNode OR .host - // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, - // but the .parentNode of a root ShadowDOM node will always be null, instead - // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 - while (element && (element.parentNode || element.host)) { - - // Set element to element parent, or 'host' in case of ShadowDOM - element = element.parentNode || element.host; - - if (element === fabric.document) { - left = body.scrollLeft || docElement.scrollLeft || 0; - top = body.scrollTop || docElement.scrollTop || 0; - } - else { - left += element.scrollLeft || 0; - top += element.scrollTop || 0; - } - - if (element.nodeType === 1 && - fabric.util.getElementStyle(element, 'position') === 'fixed') { - break; - } - } - - return { left: left, top: top }; - } - - /** - * Returns offset for a given element - * @function - * @memberOf fabric.util - * @param {HTMLElement} element Element to get offset for - * @return {Object} Object with "left" and "top" properties - */ - function getElementOffset(element) { - var docElem, - doc = element && element.ownerDocument, - box = { left: 0, top: 0 }, - offset = { left: 0, top: 0 }, - scrollLeftTop, - offsetAttributes = { - borderLeftWidth: 'left', - borderTopWidth: 'top', - paddingLeft: 'left', - paddingTop: 'top' - }; - - if (!doc) { - return offset; - } - - for (var attr in offsetAttributes) { - offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; - } - - docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== 'undefined' ) { - box = element.getBoundingClientRect(); - } - - scrollLeftTop = getScrollLeftTop(element); - - return { - left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, - top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top - }; - } - - /** - * Returns style attribute value of a given element - * @memberOf fabric.util - * @param {HTMLElement} element Element to get style attribute for - * @param {String} attr Style attribute to get for element - * @return {String} Style attribute value of the given element. - */ - var getElementStyle; - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { - getElementStyle = function(element, attr) { - var style = fabric.document.defaultView.getComputedStyle(element, null); - return style ? style[attr] : undefined; - }; - } - else { - getElementStyle = function(element, attr) { - var value = element.style[attr]; - if (!value && element.currentStyle) { - value = element.currentStyle[attr]; - } - return value; - }; - } - - (function () { - var style = fabric.document.documentElement.style, - selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; - - /** - * Makes element unselectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make unselectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementUnselectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = fabric.util.falseFunction; - } - if (selectProp) { - element.style[selectProp] = 'none'; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = 'on'; - } - return element; - } - - /** - * Makes element selectable - * @memberOf fabric.util - * @param {HTMLElement} element Element to make selectable - * @return {HTMLElement} Element that was passed in - */ - function makeElementSelectable(element) { - if (typeof element.onselectstart !== 'undefined') { - element.onselectstart = null; - } - if (selectProp) { - element.style[selectProp] = ''; - } - else if (typeof element.unselectable === 'string') { - element.unselectable = ''; - } - return element; - } - - fabric.util.makeElementUnselectable = makeElementUnselectable; - fabric.util.makeElementSelectable = makeElementSelectable; - })(); - - (function() { - - /** - * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading - * @memberOf fabric.util - * @param {String} url URL of a script to load - * @param {Function} callback Callback to execute when script is finished loading - */ - function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName('head')[0], - scriptEl = fabric.document.createElement('script'), - loading = true; - - /** @ignore */ - scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { - if (loading) { - if (typeof this.readyState === 'string' && - this.readyState !== 'loaded' && - this.readyState !== 'complete') { - return; - } - loading = false; - callback(e || fabric.window.event); - scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; - } - }; - scriptEl.src = url; - headEl.appendChild(scriptEl); - // causes issue in Opera - // headEl.removeChild(scriptEl); - } - - fabric.util.getScript = getScript; - })(); - - fabric.util.getById = getById; - fabric.util.toArray = toArray; - fabric.util.makeElement = makeElement; - fabric.util.addClass = addClass; - fabric.util.wrapElement = wrapElement; - fabric.util.getScrollLeftTop = getScrollLeftTop; - fabric.util.getElementOffset = getElementOffset; - fabric.util.getElementStyle = getElementStyle; - -})(); - - -(function() { - - function addParamToUrl(url, param) { - return url + (/\?/.test(url) ? '&' : '?') + param; - } - - var makeXHR = (function() { - var factories = [ - function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, - function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, - function() { return new XMLHttpRequest(); } - ]; - for (var i = factories.length; i--; ) { - try { - var req = factories[i](); - if (req) { - return factories[i]; - } - } - catch (err) { } - } - })(); - - function emptyFn() { } - - /** - * Cross-browser abstraction for sending XMLHttpRequest - * @memberOf fabric.util - * @param {String} url URL to send XMLHttpRequest to - * @param {Object} [options] Options object - * @param {String} [options.method="GET"] - * @param {String} [options.parameters] parameters to append to url in GET or in body - * @param {String} [options.body] body to send with POST or PUT request - * @param {Function} options.onComplete Callback to invoke when request is completed - * @return {XMLHttpRequest} request - */ - function request(url, options) { - - options || (options = { }); - - var method = options.method ? options.method.toUpperCase() : 'GET', - onComplete = options.onComplete || function() { }, - xhr = makeXHR(), - body = options.body || options.parameters; - - /** @ignore */ - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - onComplete(xhr); - xhr.onreadystatechange = emptyFn; - } - }; - - if (method === 'GET') { - body = null; - if (typeof options.parameters === 'string') { - url = addParamToUrl(url, options.parameters); - } - } - - xhr.open(method, url, true); - - if (method === 'POST' || method === 'PUT') { - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - } - - xhr.send(body); - return xhr; - } - - fabric.util.request = request; -})(); - - -/** - * Wrapper around `console.log` (when available) - * @param {*} [values] Values to log - */ -fabric.log = function() { }; - -/** - * Wrapper around `console.warn` (when available) - * @param {*} [values] Values to log as a warning - */ -fabric.warn = function() { }; - -/* eslint-disable */ -if (typeof console !== 'undefined') { - - ['log', 'warn'].forEach(function(methodName) { - - if (typeof console[methodName] !== 'undefined' && - typeof console[methodName].apply === 'function') { - - fabric[methodName] = function() { - return console[methodName].apply(console, arguments); - }; - } - }); -} -/* eslint-enable */ - - -(function() { - - /** - * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. - * @memberOf fabric.util - * @param {Object} [options] Animation options - * @param {Function} [options.onChange] Callback; invoked on every value change - * @param {Function} [options.onComplete] Callback; invoked when value change is completed - * @param {Number} [options.startValue=0] Starting value - * @param {Number} [options.endValue=100] Ending value - * @param {Number} [options.byValue=100] Value to modify the property by - * @param {Function} [options.easing] Easing function - * @param {Number} [options.duration=500] Duration of change (in ms) - */ - function animate(options) { - - requestAnimFrame(function(timestamp) { - options || (options = { }); - - var start = timestamp || +new Date(), - duration = options.duration || 500, - finish = start + duration, time, - onChange = options.onChange || function() { }, - abort = options.abort || function() { return false; }, - easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, - startValue = 'startValue' in options ? options.startValue : 0, - endValue = 'endValue' in options ? options.endValue : 100, - byValue = options.byValue || endValue - startValue; - - options.onStart && options.onStart(); - - (function tick(ticktime) { - time = ticktime || +new Date(); - var currentTime = time > finish ? duration : (time - start); - if (abort()) { - options.onComplete && options.onComplete(); - return; - } - onChange(easing(currentTime, startValue, byValue, duration)); - if (time > finish) { - options.onComplete && options.onComplete(); - return; - } - requestAnimFrame(tick); - })(start); - }); - - } - - var _requestAnimFrame = fabric.window.requestAnimationFrame || - fabric.window.webkitRequestAnimationFrame || - fabric.window.mozRequestAnimationFrame || - fabric.window.oRequestAnimationFrame || - fabric.window.msRequestAnimationFrame || - function(callback) { - fabric.window.setTimeout(callback, 1000 / 60); - }; - - /** - * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method - * @memberOf fabric.util - * @param {Function} callback Callback to invoke - * @param {DOMElement} element optional Element to associate with animation - */ - function requestAnimFrame() { - return _requestAnimFrame.apply(fabric.window, arguments); - } - - fabric.util.animate = animate; - fabric.util.requestAnimFrame = requestAnimFrame; - -})(); - - -(function() { - - function normalize(a, c, p, s) { - if (a < Math.abs(c)) { - a = c; - s = p / 4; - } - else { - //handle the 0/0 case: - if (c === 0 && a === 0) { - s = p / (2 * Math.PI) * Math.asin(1); - } - else { - s = p / (2 * Math.PI) * Math.asin(c / a); - } - } - return { a: a, c: c, p: p, s: s }; - } - - function elastic(opts, t, d) { - return opts.a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); - } - - /** - * Cubic easing out - * @memberOf fabric.util.ease - */ - function easeOutCubic(t, b, c, d) { - return c * ((t = t / d - 1) * t * t + 1) + b; - } - - /** - * Cubic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutCubic(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t + b; - } - return c / 2 * ((t -= 2) * t * t + 2) + b; - } - - /** - * Quartic easing in - * @memberOf fabric.util.ease - */ - function easeInQuart(t, b, c, d) { - return c * (t /= d) * t * t * t + b; - } - - /** - * Quartic easing out - * @memberOf fabric.util.ease - */ - function easeOutQuart(t, b, c, d) { - return -c * ((t = t / d - 1) * t * t * t - 1) + b; - } - - /** - * Quartic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutQuart(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t * t + b; - } - return -c / 2 * ((t -= 2) * t * t * t - 2) + b; - } - - /** - * Quintic easing in - * @memberOf fabric.util.ease - */ - function easeInQuint(t, b, c, d) { - return c * (t /= d) * t * t * t * t + b; - } - - /** - * Quintic easing out - * @memberOf fabric.util.ease - */ - function easeOutQuint(t, b, c, d) { - return c * ((t = t / d - 1) * t * t * t * t + 1) + b; - } - - /** - * Quintic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutQuint(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t * t * t + b; - } - return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; - } - - /** - * Sinusoidal easing in - * @memberOf fabric.util.ease - */ - function easeInSine(t, b, c, d) { - return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - } - - /** - * Sinusoidal easing out - * @memberOf fabric.util.ease - */ - function easeOutSine(t, b, c, d) { - return c * Math.sin(t / d * (Math.PI / 2)) + b; - } - - /** - * Sinusoidal easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutSine(t, b, c, d) { - return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; - } - - /** - * Exponential easing in - * @memberOf fabric.util.ease - */ - function easeInExpo(t, b, c, d) { - return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; - } - - /** - * Exponential easing out - * @memberOf fabric.util.ease - */ - function easeOutExpo(t, b, c, d) { - return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; - } - - /** - * Exponential easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutExpo(t, b, c, d) { - if (t === 0) { - return b; - } - if (t === d) { - return b + c; - } - t /= d / 2; - if (t < 1) { - return c / 2 * Math.pow(2, 10 * (t - 1)) + b; - } - return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; - } - - /** - * Circular easing in - * @memberOf fabric.util.ease - */ - function easeInCirc(t, b, c, d) { - return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; - } - - /** - * Circular easing out - * @memberOf fabric.util.ease - */ - function easeOutCirc(t, b, c, d) { - return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; - } - - /** - * Circular easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutCirc(t, b, c, d) { - t /= d / 2; - if (t < 1) { - return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; - } - return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; - } - - /** - * Elastic easing in - * @memberOf fabric.util.ease - */ - function easeInElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; - } - t /= d; - if (t === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - var opts = normalize(a, c, p, s); - return -elastic(opts, t, d) + b; - } - - /** - * Elastic easing out - * @memberOf fabric.util.ease - */ - function easeOutElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; - } - t /= d; - if (t === 1) { - return b + c; - } - if (!p) { - p = d * 0.3; - } - var opts = normalize(a, c, p, s); - return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; - } - - /** - * Elastic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutElastic(t, b, c, d) { - var s = 1.70158, p = 0, a = c; - if (t === 0) { - return b; - } - t /= d / 2; - if (t === 2) { - return b + c; - } - if (!p) { - p = d * (0.3 * 1.5); - } - var opts = normalize(a, c, p, s); - if (t < 1) { - return -0.5 * elastic(opts, t, d) + b; - } - return opts.a * Math.pow(2, -10 * (t -= 1)) * - Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; - } - - /** - * Backwards easing in - * @memberOf fabric.util.ease - */ - function easeInBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; - } - return c * (t /= d) * t * ((s + 1) * t - s) + b; - } - - /** - * Backwards easing out - * @memberOf fabric.util.ease - */ - function easeOutBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; - } - return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; - } - - /** - * Backwards easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutBack(t, b, c, d, s) { - if (s === undefined) { - s = 1.70158; - } - t /= d / 2; - if (t < 1) { - return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; - } - return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; - } - - /** - * Bouncing easing in - * @memberOf fabric.util.ease - */ - function easeInBounce(t, b, c, d) { - return c - easeOutBounce (d - t, 0, c, d) + b; - } - - /** - * Bouncing easing out - * @memberOf fabric.util.ease - */ - function easeOutBounce(t, b, c, d) { - if ((t /= d) < (1 / 2.75)) { - return c * (7.5625 * t * t) + b; - } - else if (t < (2 / 2.75)) { - return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; - } - else if (t < (2.5 / 2.75)) { - return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; - } - else { - return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; - } - } - - /** - * Bouncing easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutBounce(t, b, c, d) { - if (t < d / 2) { - return easeInBounce (t * 2, 0, c, d) * 0.5 + b; - } - return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - - /** - * Easing functions - * See Easing Equations by Robert Penner - * @namespace fabric.util.ease - */ - fabric.util.ease = { - - /** - * Quadratic easing in - * @memberOf fabric.util.ease - */ - easeInQuad: function(t, b, c, d) { - return c * (t /= d) * t + b; - }, - - /** - * Quadratic easing out - * @memberOf fabric.util.ease - */ - easeOutQuad: function(t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - }, - - /** - * Quadratic easing in and out - * @memberOf fabric.util.ease - */ - easeInOutQuad: function(t, b, c, d) { - t /= (d / 2); - if (t < 1) { - return c / 2 * t * t + b; - } - return -c / 2 * ((--t) * (t - 2) - 1) + b; - }, - - /** - * Cubic easing in - * @memberOf fabric.util.ease - */ - easeInCubic: function(t, b, c, d) { - return c * (t /= d) * t * t + b; - }, - - easeOutCubic: easeOutCubic, - easeInOutCubic: easeInOutCubic, - easeInQuart: easeInQuart, - easeOutQuart: easeOutQuart, - easeInOutQuart: easeInOutQuart, - easeInQuint: easeInQuint, - easeOutQuint: easeOutQuint, - easeInOutQuint: easeInOutQuint, - easeInSine: easeInSine, - easeOutSine: easeOutSine, - easeInOutSine: easeInOutSine, - easeInExpo: easeInExpo, - easeOutExpo: easeOutExpo, - easeInOutExpo: easeInOutExpo, - easeInCirc: easeInCirc, - easeOutCirc: easeOutCirc, - easeInOutCirc: easeInOutCirc, - easeInElastic: easeInElastic, - easeOutElastic: easeOutElastic, - easeInOutElastic: easeInOutElastic, - easeInBack: easeInBack, - easeOutBack: easeOutBack, - easeInOutBack: easeInOutBack, - easeInBounce: easeInBounce, - easeOutBounce: easeOutBounce, - easeInOutBounce: easeInOutBounce - }; - -})(); - - -(function(global) { - - 'use strict'; - - /** - * @name fabric - * @namespace - */ - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - capitalize = fabric.util.string.capitalize, - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - parseUnit = fabric.util.parseUnit, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - - reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i, - reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i, - reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata)$/i, - reAllowedParents = /^(symbol|g|a|svg)$/i, - - attributesMap = { - cx: 'left', - x: 'left', - r: 'radius', - cy: 'top', - y: 'top', - display: 'visible', - visibility: 'visible', - transform: 'transformMatrix', - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit': 'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'text-anchor': 'originX' - }, - - colorAttributes = { - stroke: 'strokeOpacity', - fill: 'fillOpacity' - }; - - fabric.cssRules = { }; - fabric.gradientDefs = { }; - - function normalizeAttr(attr) { - // transform attribute names - if (attr in attributesMap) { - return attributesMap[attr]; - } - return attr; - } - - function normalizeValue(attr, value, parentAttributes, fontSize) { - var isArray = Object.prototype.toString.call(value) === '[object Array]', - parsed; - - if ((attr === 'fill' || attr === 'stroke') && value === 'none') { - value = ''; - } - else if (attr === 'strokeDashArray') { - value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { - return parseFloat(n); - }); - } - else if (attr === 'transformMatrix') { - if (parentAttributes && parentAttributes.transformMatrix) { - value = multiplyTransformMatrices( - parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); - } - else { - value = fabric.parseTransformAttribute(value); - } - } - else if (attr === 'visible') { - value = (value === 'none' || value === 'hidden') ? false : true; - // display=none on parent element always takes precedence over child element - if (parentAttributes && parentAttributes.visible === false) { - value = false; - } - } - else if (attr === 'originX' /* text-anchor */) { - value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; - } - else { - parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); - } - - return (!isArray && isNaN(parsed) ? value : parsed); - } - - /** - * @private - * @param {Object} attributes Array of attributes to parse - */ - function _setStrokeFillOpacity(attributes) { - for (var attr in colorAttributes) { - - if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') { - continue; - } - - if (typeof attributes[attr] === 'undefined') { - if (!fabric.Object.prototype[attr]) { - continue; - } - attributes[attr] = fabric.Object.prototype[attr]; - } - - if (attributes[attr].indexOf('url(') === 0) { - continue; - } - - var color = new fabric.Color(attributes[attr]); - attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); - } - return attributes; - } - - /** - * @private - */ - function _getMultipleNodes(doc, nodeNames) { - var nodeName, nodeArray = [], nodeList; - for (var i = 0; i < nodeNames.length; i++) { - nodeName = nodeNames[i]; - nodeList = doc.getElementsByTagName(nodeName); - nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); - } - return nodeArray; - } - - /** - * Parses "transform" attribute, returning an array of values - * @static - * @function - * @memberOf fabric - * @param {String} attributeValue String containing attribute value - * @return {Array} Array of 6 elements representing transformation matrix - */ - fabric.parseTransformAttribute = (function() { - function rotateMatrix(matrix, args) { - var angle = args[0], - x = (args.length === 3) ? args[1] : 0, - y = (args.length === 3) ? args[2] : 0; - - matrix[0] = Math.cos(angle); - matrix[1] = Math.sin(angle); - matrix[2] = -Math.sin(angle); - matrix[3] = Math.cos(angle); - matrix[4] = x - (matrix[0] * x + matrix[2] * y); - matrix[5] = y - (matrix[1] * x + matrix[3] * y); - } - - function scaleMatrix(matrix, args) { - var multiplierX = args[0], - multiplierY = (args.length === 2) ? args[1] : args[0]; - - matrix[0] = multiplierX; - matrix[3] = multiplierY; - } - - function skewXMatrix(matrix, args) { - matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); - } - - function skewYMatrix(matrix, args) { - matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); - } - - function translateMatrix(matrix, args) { - matrix[4] = args[0]; - if (args.length === 2) { - matrix[5] = args[1]; - } - } - - // identity matrix - var iMatrix = [ - 1, // a - 0, // b - 0, // c - 1, // d - 0, // e - 0 // f - ], - - // == begin transform regexp - number = fabric.reNum, - - commaWsp = '(?:\\s+,?\\s*|,\\s*)', - - skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', - - skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', - - rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + ')' + - commaWsp + '(' + number + '))?\\s*\\))', - - scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - commaWsp + '(' + number + '))?\\s*\\))', - - matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + commaWsp + - '(' + number + ')' + - '\\s*\\))', - - transform = '(?:' + - matrix + '|' + - translate + '|' + - scale + '|' + - rotate + '|' + - skewX + '|' + - skewY + - ')', - - transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')', - - transformList = '^\\s*(?:' + transforms + '?)\\s*$', - - // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transformList), - // == end transform regexp - - reTransform = new RegExp(transform, 'g'); - - return function(attributeValue) { - - // start with identity matrix - var matrix = iMatrix.concat(), - matrices = []; - - // return if no argument was given or - // an argument does not match transform attribute regexp - if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { - return matrix; - } - - attributeValue.replace(reTransform, function(match) { - - var m = new RegExp(transform).exec(match).filter(function (match) { - // match !== '' && match != null - return (!!match); - }), - operation = m[1], - args = m.slice(2).map(parseFloat); - - switch (operation) { - case 'translate': - translateMatrix(matrix, args); - break; - case 'rotate': - args[0] = fabric.util.degreesToRadians(args[0]); - rotateMatrix(matrix, args); - break; - case 'scale': - scaleMatrix(matrix, args); - break; - case 'skewX': - skewXMatrix(matrix, args); - break; - case 'skewY': - skewYMatrix(matrix, args); - break; - case 'matrix': - matrix = args; - break; - } - - // snapshot current matrix into matrices array - matrices.push(matrix.concat()); - // reset - matrix = iMatrix.concat(); - }); - - var combinedMatrix = matrices[0]; - while (matrices.length > 1) { - matrices.shift(); - combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); - } - return combinedMatrix; - }; - })(); - - /** - * @private - */ - function parseStyleString(style, oStyle) { - var attr, value; - style.replace(/;\s*$/, '').split(';').forEach(function (chunk) { - var pair = chunk.split(':'); - - attr = normalizeAttr(pair[0].trim().toLowerCase()); - value = normalizeValue(attr, pair[1].trim()); - - oStyle[attr] = value; - }); - } - - /** - * @private - */ - function parseStyleObject(style, oStyle) { - var attr, value; - for (var prop in style) { - if (typeof style[prop] === 'undefined') { - continue; - } - - attr = normalizeAttr(prop.toLowerCase()); - value = normalizeValue(attr, style[prop]); - - oStyle[attr] = value; - } - } - - /** - * @private - */ - function getGlobalStylesForElement(element, svgUid) { - var styles = { }; - for (var rule in fabric.cssRules[svgUid]) { - if (elementMatchesRule(element, rule.split(' '))) { - for (var property in fabric.cssRules[svgUid][rule]) { - styles[property] = fabric.cssRules[svgUid][rule][property]; - } - } - } - return styles; - } - - /** - * @private - */ - function elementMatchesRule(element, selectors) { - var firstMatching, parentMatching = true; - //start from rightmost selector. - firstMatching = selectorMatches(element, selectors.pop()); - if (firstMatching && selectors.length) { - parentMatching = doesSomeParentMatch(element, selectors); - } - return firstMatching && parentMatching && (selectors.length === 0); - } - - function doesSomeParentMatch(element, selectors) { - var selector, parentMatching = true; - while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { - if (parentMatching) { - selector = selectors.pop(); - } - element = element.parentNode; - parentMatching = selectorMatches(element, selector); - } - return selectors.length === 0; - } - - /** - * @private - */ - function selectorMatches(element, selector) { - var nodeName = element.nodeName, - classNames = element.getAttribute('class'), - id = element.getAttribute('id'), matcher; - // i check if a selector matches slicing away part from it. - // if i get empty string i should match - matcher = new RegExp('^' + nodeName, 'i'); - selector = selector.replace(matcher, ''); - if (id && selector.length) { - matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - if (classNames && selector.length) { - classNames = classNames.split(' '); - for (var i = classNames.length; i--;) { - matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); - selector = selector.replace(matcher, ''); - } - } - return selector.length === 0; - } - - /** - * @private - * to support IE8 missing getElementById on SVGdocument - */ - function elementById(doc, id) { - var el; - doc.getElementById && (el = doc.getElementById(id)); - if (el) { - return el; - } - var node, i, nodelist = doc.getElementsByTagName('*'); - for (i = 0; i < nodelist.length; i++) { - node = nodelist[i]; - if (id === node.getAttribute('id')) { - return node; - } - } - } - - /** - * @private - */ - function parseUseDirectives(doc) { - var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0; - - while (nodelist.length && i < nodelist.length) { - var el = nodelist[i], - xlink = el.getAttribute('xlink:href').substr(1), - x = el.getAttribute('x') || 0, - y = el.getAttribute('y') || 0, - el2 = elementById(doc, xlink).cloneNode(true), - currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', - parentNode, oldLength = nodelist.length, attr, j, attrs, l; - - applyViewboxTransform(el2); - if (/^svg$/i.test(el2.nodeName)) { - var el3 = el2.ownerDocument.createElement('g'); - for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) { - attr = attrs.item(j); - el3.setAttribute(attr.nodeName, attr.nodeValue); - } - // el2.firstChild != null - while (el2.firstChild) { - el3.appendChild(el2.firstChild); - } - el2 = el3; - } - - for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { - attr = attrs.item(j); - if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { - continue; - } - - if (attr.nodeName === 'transform') { - currentTrans = attr.nodeValue + ' ' + currentTrans; - } - else { - el2.setAttribute(attr.nodeName, attr.nodeValue); - } - } - - el2.setAttribute('transform', currentTrans); - el2.setAttribute('instantiated_by_use', '1'); - el2.removeAttribute('id'); - parentNode = el.parentNode; - parentNode.replaceChild(el2, el); - // some browsers do not shorten nodelist after replaceChild (IE8) - if (nodelist.length === oldLength) { - i++; - } - } - } - - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // matches, e.g.: +14.56e-12, etc. - var reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*,?' + - '\\s*(' + fabric.reNum + '+)\\s*' + - '$' - ); - - /** - * Add a element that envelop all child elements and makes the viewbox transformMatrix descend on all elements - */ - function applyViewboxTransform(element) { - - var viewBoxAttr = element.getAttribute('viewBox'), - scaleX = 1, - scaleY = 1, - minX = 0, - minY = 0, - viewBoxWidth, viewBoxHeight, matrix, el, - widthAttr = element.getAttribute('width'), - heightAttr = element.getAttribute('height'), - x = element.getAttribute('x') || 0, - y = element.getAttribute('y') || 0, - preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', - missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName) - || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), - missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), - toBeParsed = missingViewBox && missingDimAttr, - parsedDim = { }, translateMatrix = ''; - - parsedDim.width = 0; - parsedDim.height = 0; - parsedDim.toBeParsed = toBeParsed; - - if (toBeParsed) { - return parsedDim; - } - - if (missingViewBox) { - parsedDim.width = parseUnit(widthAttr); - parsedDim.height = parseUnit(heightAttr); - return parsedDim; - } - - minX = -parseFloat(viewBoxAttr[1]); - minY = -parseFloat(viewBoxAttr[2]); - viewBoxWidth = parseFloat(viewBoxAttr[3]); - viewBoxHeight = parseFloat(viewBoxAttr[4]); - - if (!missingDimAttr) { - parsedDim.width = parseUnit(widthAttr); - parsedDim.height = parseUnit(heightAttr); - scaleX = parsedDim.width / viewBoxWidth; - scaleY = parsedDim.height / viewBoxHeight; - } - else { - parsedDim.width = viewBoxWidth; - parsedDim.height = viewBoxHeight; - } - - // default is to preserve aspect ratio - preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); - if (preserveAspectRatio.alignX !== 'none') { - //translate all container for the effect of Mid, Min, Max - scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); - } - - if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) { - return parsedDim; - } - - if (x || y) { - translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; - } - - matrix = translateMatrix + ' matrix(' + scaleX + - ' 0' + - ' 0 ' + - scaleY + ' ' + - (minX * scaleX) + ' ' + - (minY * scaleY) + ') '; - - if (element.nodeName === 'svg') { - el = element.ownerDocument.createElement('g'); - // element.firstChild != null - while (element.firstChild) { - el.appendChild(element.firstChild); - } - element.appendChild(el); - } - else { - el = element; - matrix = el.getAttribute('transform') + matrix; - } - - el.setAttribute('transform', matrix); - return parsedDim; - } - - /** - * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @param {Function} callback Callback to call when parsing is finished; - * It's being passed an array of elements (parsed from a document). - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - fabric.parseSVGDocument = (function() { - - function hasAncestorWithNodeName(element, nodeName) { - while (element && (element = element.parentNode)) { - if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', '')) - && !element.getAttribute('instantiated_by_use')) { - return true; - } - } - return false; - } - - return function(doc, callback, reviver) { - if (!doc) { - return; - } - - parseUseDirectives(doc); - - var startTime = new Date(), - svgUid = fabric.Object.__uid++, - options = applyViewboxTransform(doc), - descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - - options.svgUid = svgUid; - - if (descendants.length === 0 && fabric.isLikelyNode) { - // we're likely in node, where "o3-xml" library fails to gEBTN("*") - // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes('//*[name(.)!="svg"]'); - var arr = []; - for (var i = 0, len = descendants.length; i < len; i++) { - arr[i] = descendants[i]; - } - descendants = arr; - } - - var elements = descendants.filter(function(el) { - applyViewboxTransform(el); - return reAllowedSVGTagNames.test(el.nodeName.replace('svg:', '')) && - !hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement - }); - - if (!elements || (elements && !elements.length)) { - callback && callback([], {}); - return; - } - - fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); - fabric.cssRules[svgUid] = fabric.getCSSRules(doc); - // Precedence of rules: style > class > attribute - fabric.parseElements(elements, function(instances) { - fabric.documentParsingTime = new Date() - startTime; - if (callback) { - callback(instances, options); - } - }, clone(options), reviver); - }; - })(); - - /** - * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) - * @namespace - */ - var svgCache = { - - /** - * @param {String} name - * @param {Function} callback - */ - has: function (name, callback) { - callback(false); - }, - - get: function () { - /* NOOP */ - }, - - set: function () { - /* NOOP */ - } - }; - - /** - * @private - */ - function _enlivenCachedObject(cachedObject) { - - var objects = cachedObject.objects, - options = cachedObject.options; - - objects = objects.map(function (o) { - return fabric[capitalize(o.type)].fromObject(o); - }); - - return ({ objects: objects, options: options }); - } - - /** - * @private - */ - function _createSVGPattern(markup, canvas, property) { - if (canvas[property] && canvas[property].toSVG) { - markup.push( - '\t\n', - '\t\t\n\t\n' - ); - } - } - - var reFontDeclaration = new RegExp( - '(normal|italic)?\\s*(normal|small-caps)?\\s*' + - '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + - fabric.reNum + - '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); - - extend(fabric, { - /** - * Parses a short font declaration, building adding its properties to a style object - * @static - * @function - * @memberOf fabric - * @param {String} value font declaration - * @param {Object} oStyle definition - */ - parseFontDeclaration: function(value, oStyle) { - var match = value.match(reFontDeclaration); - - if (!match) { - return; - } - var fontStyle = match[1], - // font variant is not used - // fontVariant = match[2], - fontWeight = match[3], - fontSize = match[4], - lineHeight = match[5], - fontFamily = match[6]; - - if (fontStyle) { - oStyle.fontStyle = fontStyle; - } - if (fontWeight) { - oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); - } - if (fontSize) { - oStyle.fontSize = parseUnit(fontSize); - } - if (fontFamily) { - oStyle.fontFamily = fontFamily; - } - if (lineHeight) { - oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; - } - }, - - /** - * Parses an SVG document, returning all of the gradient declarations found in it - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element - */ - getGradientDefs: function(doc) { - var tagArray = [ - 'linearGradient', - 'radialGradient', - 'svg:linearGradient', - 'svg:radialGradient'], - elList = _getMultipleNodes(doc, tagArray), - el, j = 0, id, xlink, - gradientDefs = { }, idsToXlinkMap = { }; - - j = elList.length; - - while (j--) { - el = elList[j]; - xlink = el.getAttribute('xlink:href'); - id = el.getAttribute('id'); - if (xlink) { - idsToXlinkMap[id] = xlink.substr(1); - } - gradientDefs[id] = el; - } - - for (id in idsToXlinkMap) { - var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); - el = gradientDefs[id]; - while (el2.firstChild) { - el.appendChild(el2.firstChild); - } - } - return gradientDefs; - }, - - /** - * Returns an object of attributes' name/value, given element and an array of attribute names; - * Parses parent "g" nodes recursively upwards. - * @static - * @memberOf fabric - * @param {DOMElement} element Element to parse - * @param {Array} attributes Array of attributes to parse - * @return {Object} object containing parsed attributes' names/values - */ - parseAttributes: function(element, attributes, svgUid) { - - if (!element) { - return; - } - - var value, - parentAttributes = { }, - fontSize; - - if (typeof svgUid === 'undefined') { - svgUid = element.getAttribute('svgUid'); - } - // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards - if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) { - parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); - } - fontSize = (parentAttributes && parentAttributes.fontSize ) || - element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE; - - var ownAttributes = attributes.reduce(function(memo, attr) { - value = element.getAttribute(attr); - if (value) { - attr = normalizeAttr(attr); - value = normalizeValue(attr, value, parentAttributes, fontSize); - - memo[attr] = value; - } - return memo; - }, { }); - - // add values parsed from style, which take precedence over attributes - // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) - ownAttributes = extend(ownAttributes, - extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); - if (ownAttributes.font) { - fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); - } - return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); - }, - - /** - * Transforms an array of svg elements to corresponding fabric.* instances - * @static - * @memberOf fabric - * @param {Array} elements Array of elements to parse - * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) - * @param {Object} [options] Options object - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - parseElements: function(elements, callback, options, reviver) { - new fabric.ElementsParser(elements, callback, options, reviver).parse(); - }, - - /** - * Parses "style" attribute, retuning an object with values - * @static - * @memberOf fabric - * @param {SVGElement} element Element to parse - * @return {Object} Objects with values parsed from style attribute of an element - */ - parseStyleAttribute: function(element) { - var oStyle = { }, - style = element.getAttribute('style'); - - if (!style) { - return oStyle; - } - - if (typeof style === 'string') { - parseStyleString(style, oStyle); - } - else { - parseStyleObject(style, oStyle); - } - - return oStyle; - }, - - /** - * Parses "points" attribute, returning an array of values - * @static - * @memberOf fabric - * @param {String} points points attribute string - * @return {Array} array of points - */ - parsePointsAttribute: function(points) { - - // points attribute is required and must not be empty - if (!points) { - return null; - } - - // replace commas with whitespace and remove bookending whitespace - points = points.replace(/,/g, ' ').trim(); - - points = points.split(/\s+/); - var parsedPoints = [], i, len; - - i = 0; - len = points.length; - for (; i < len; i += 2) { - parsedPoints.push({ - x: parseFloat(points[i]), - y: parseFloat(points[i + 1]) - }); - } - - // odd number of points is an error - // if (parsedPoints.length % 2 !== 0) { - // return null; - // } - - return parsedPoints; - }, - - /** - * Returns CSS rules for a given SVG document - * @static - * @function - * @memberOf fabric - * @param {SVGDocument} doc SVG document to parse - * @return {Object} CSS rules of this document - */ - getCSSRules: function(doc) { - var styles = doc.getElementsByTagName('style'), - allRules = { }, rules; - - // very crude parsing of style contents - for (var i = 0, len = styles.length; i < len; i++) { - // IE9 doesn't support textContent, but provides text instead. - var styleContents = styles[i].textContent || styles[i].text; - - // remove comments - styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); - if (styleContents.trim() === '') { - continue; - } - rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); - rules = rules.map(function(rule) { return rule.trim(); }); - rules.forEach(function(rule) { - - var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), - ruleObj = { }, declaration = match[2].trim(), - propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); - - for (var i = 0, len = propertyValuePairs.length; i < len; i++) { - var pair = propertyValuePairs[i].split(/\s*:\s*/), - property = normalizeAttr(pair[0]), - value = normalizeValue(property, pair[1], pair[0]); - ruleObj[property] = value; - } - rule = match[1]; - rule.split(',').forEach(function(_rule) { - _rule = _rule.replace(/^svg/i, '').trim(); - if (_rule === '') { - return; - } - if (allRules[_rule]) { - fabric.util.object.extend(allRules[_rule], ruleObj); - } - else { - allRules[_rule] = fabric.util.object.clone(ruleObj); - } - }); - }); - } - return allRules; - }, - - /** - * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. - * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) - * @memberOf fabric - * @param {String} url - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromURL: function(url, callback, reviver) { - - url = url.replace(/^\n\s*/, '').trim(); - svgCache.has(url, function (hasUrl) { - if (hasUrl) { - svgCache.get(url, function (value) { - var enlivedRecord = _enlivenCachedObject(value); - callback(enlivedRecord.objects, enlivedRecord.options); - }); - } - else { - new fabric.util.request(url, { - method: 'get', - onComplete: onComplete - }); - } - }); - - function onComplete(r) { - - var xml = r.responseXML; - if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { - xml = new ActiveXObject('Microsoft.XMLDOM'); - xml.async = 'false'; - //IE chokes on DOCTYPE - xml.loadXML(r.responseText.replace(//i, '')); - } - if (!xml || !xml.documentElement) { - callback && callback(null); - } - - fabric.parseSVGDocument(xml.documentElement, function (results, options) { - svgCache.set(url, { - objects: fabric.util.array.invoke(results, 'toObject'), - options: options - }); - callback && callback(results, options); - }, reviver); - } - }, - - /** - * Takes string corresponding to an SVG document, and parses it into a set of fabric objects - * @memberOf fabric - * @param {String} string - * @param {Function} callback - * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. - */ - loadSVGFromString: function(string, callback, reviver) { - string = string.trim(); - var doc; - if (typeof DOMParser !== 'undefined') { - var parser = new DOMParser(); - if (parser && parser.parseFromString) { - doc = parser.parseFromString(string, 'text/xml'); - } - } - else if (fabric.window.ActiveXObject) { - doc = new ActiveXObject('Microsoft.XMLDOM'); - doc.async = 'false'; - // IE chokes on DOCTYPE - doc.loadXML(string.replace(//i, '')); - } - - fabric.parseSVGDocument(doc.documentElement, function (results, options) { - callback(results, options); - }, reviver); - }, - - /** - * Creates markup containing SVG font faces, - * font URLs for font faces must be collected by developers - * and are not extracted from the DOM by fabricjs - * @param {Array} objects Array of fabric objects - * @return {String} - */ - createSVGFontFacesMarkup: function(objects) { - var markup = '', fontList = { }, obj, fontFamily, - style, row, rowIndex, _char, charIndex, - fontPaths = fabric.fontPaths; - - for (var i = 0, len = objects.length; i < len; i++) { - obj = objects[i]; - fontFamily = obj.fontFamily; - if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { - continue; - } - fontList[fontFamily] = true; - if (!obj.styles) { - continue; - } - style = obj.styles; - for (rowIndex in style) { - row = style[rowIndex]; - for (charIndex in row) { - _char = row[charIndex]; - fontFamily = _char.fontFamily; - if (!fontList[fontFamily] && fontPaths[fontFamily]) { - fontList[fontFamily] = true; - } - } - } - } - - for (var j in fontList) { - markup += [ - '\t\t@font-face {\n', - '\t\t\tfont-family: \'', j, '\';\n', - '\t\t\tsrc: url(\'', fontPaths[j], '\');\n', - '\t\t}\n' - ].join(''); - } - - if (markup) { - markup = [ - '\t\n' - ].join(''); - } - - return markup; - }, - - /** - * Creates markup containing SVG referenced elements like patterns, gradients etc. - * @param {fabric.Canvas} canvas instance of fabric.Canvas - * @return {String} - */ - createSVGRefElementsMarkup: function(canvas) { - var markup = []; - - _createSVGPattern(markup, canvas, 'backgroundColor'); - _createSVGPattern(markup, canvas, 'overlayColor'); - - return markup.join(''); - } - }); - -})(typeof exports !== 'undefined' ? exports : this); - - -fabric.ElementsParser = function(elements, callback, options, reviver) { - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; - this.svgUid = (options && options.svgUid) || 0; -}; - -fabric.ElementsParser.prototype.parse = function() { - this.instances = new Array(this.elements.length); - this.numElements = this.elements.length; - - this.createObjects(); -}; - -fabric.ElementsParser.prototype.createObjects = function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - this.elements[i].setAttribute('svgUid', this.svgUid); - (function(_obj, i) { - setTimeout(function() { - _obj.createObject(_obj.elements[i], i); - }, 0); - })(this, i); - } -}; - -fabric.ElementsParser.prototype.createObject = function(el, index) { - var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))]; - if (klass && klass.fromElement) { - try { - this._createObject(klass, el, index); - } - catch (err) { - fabric.log(err); - } - } - else { - this.checkIfDone(); - } -}; - -fabric.ElementsParser.prototype._createObject = function(klass, el, index) { - if (klass.async) { - klass.fromElement(el, this.createCallback(index, el), this.options); - } - else { - var obj = klass.fromElement(el, this.options); - this.resolveGradient(obj, 'fill'); - this.resolveGradient(obj, 'stroke'); - this.reviver && this.reviver(el, obj); - this.instances[index] = obj; - this.checkIfDone(); - } -}; - -fabric.ElementsParser.prototype.createCallback = function(index, el) { - var _this = this; - return function(obj) { - _this.resolveGradient(obj, 'fill'); - _this.resolveGradient(obj, 'stroke'); - _this.reviver && _this.reviver(el, obj); - _this.instances[index] = obj; - _this.checkIfDone(); - }; -}; - -fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { - - var instanceFillValue = obj.get(property); - if (!(/^url\(/).test(instanceFillValue)) { - return; - } - var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); - if (fabric.gradientDefs[this.svgUid][gradientId]) { - obj.set(property, - fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); - } -}; - -fabric.ElementsParser.prototype.checkIfDone = function() { - if (--this.numElements === 0) { - this.instances = this.instances.filter(function(el) { - // eslint-disable-next-line no-eq-null, eqeqeq - return el != null; - }); - this.callback(this.instances); - } -}; - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Point) { - fabric.warn('fabric.Point is already defined'); - return; - } - - fabric.Point = Point; - - /** - * Point class - * @class fabric.Point - * @memberOf fabric - * @constructor - * @param {Number} x - * @param {Number} y - * @return {fabric.Point} thisArg - */ - function Point(x, y) { - this.x = x; - this.y = y; - } - - Point.prototype = /** @lends fabric.Point.prototype */ { - - type: 'point', - - constructor: Point, - - /** - * Adds another point to this one and returns another one - * @param {fabric.Point} that - * @return {fabric.Point} new Point instance with added values - */ - add: function (that) { - return new Point(this.x + that.x, this.y + that.y); - }, - - /** - * Adds another point to this one - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - * @chainable - */ - addEquals: function (that) { - this.x += that.x; - this.y += that.y; - return this; - }, - - /** - * Adds value to this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} new Point with added value - */ - scalarAdd: function (scalar) { - return new Point(this.x + scalar, this.y + scalar); - }, - - /** - * Adds value to this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - * @chainable - */ - scalarAddEquals: function (scalar) { - this.x += scalar; - this.y += scalar; - return this; - }, - - /** - * Subtracts another point from this point and returns a new one - * @param {fabric.Point} that - * @return {fabric.Point} new Point object with subtracted values - */ - subtract: function (that) { - return new Point(this.x - that.x, this.y - that.y); - }, - - /** - * Subtracts another point from this point - * @param {fabric.Point} that - * @return {fabric.Point} thisArg - * @chainable - */ - subtractEquals: function (that) { - this.x -= that.x; - this.y -= that.y; - return this; - }, - - /** - * Subtracts value from this point and returns a new one - * @param {Number} scalar - * @return {fabric.Point} - */ - scalarSubtract: function (scalar) { - return new Point(this.x - scalar, this.y - scalar); - }, - - /** - * Subtracts value from this point - * @param {Number} scalar - * @return {fabric.Point} thisArg - * @chainable - */ - scalarSubtractEquals: function (scalar) { - this.x -= scalar; - this.y -= scalar; - return this; - }, - - /** - * Miltiplies this point by a value and returns a new one - * TODO: rename in scalarMultiply in 2.0 - * @param {Number} scalar - * @return {fabric.Point} - */ - multiply: function (scalar) { - return new Point(this.x * scalar, this.y * scalar); - }, - - /** - * Miltiplies this point by a value - * TODO: rename in scalarMultiplyEquals in 2.0 - * @param {Number} scalar - * @return {fabric.Point} thisArg - * @chainable - */ - multiplyEquals: function (scalar) { - this.x *= scalar; - this.y *= scalar; - return this; - }, - - /** - * Divides this point by a value and returns a new one - * TODO: rename in scalarDivide in 2.0 - * @param {Number} scalar - * @return {fabric.Point} - */ - divide: function (scalar) { - return new Point(this.x / scalar, this.y / scalar); - }, - - /** - * Divides this point by a value - * TODO: rename in scalarDivideEquals in 2.0 - * @param {Number} scalar - * @return {fabric.Point} thisArg - * @chainable - */ - divideEquals: function (scalar) { - this.x /= scalar; - this.y /= scalar; - return this; - }, - - /** - * Returns true if this point is equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - eq: function (that) { - return (this.x === that.x && this.y === that.y); - }, - - /** - * Returns true if this point is less than another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lt: function (that) { - return (this.x < that.x && this.y < that.y); - }, - - /** - * Returns true if this point is less than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - lte: function (that) { - return (this.x <= that.x && this.y <= that.y); - }, - - /** - - * Returns true if this point is greater another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gt: function (that) { - return (this.x > that.x && this.y > that.y); - }, - - /** - * Returns true if this point is greater than or equal to another one - * @param {fabric.Point} that - * @return {Boolean} - */ - gte: function (that) { - return (this.x >= that.x && this.y >= that.y); - }, - - /** - * Returns new point which is the result of linear interpolation with this one and another one - * @param {fabric.Point} that - * @param {Number} t , position of interpolation, between 0 and 1 default 0.5 - * @return {fabric.Point} - */ - lerp: function (that, t) { - if (typeof t === 'undefined') { - t = 0.5; - } - t = Math.max(Math.min(1, t), 0); - return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); - }, - - /** - * Returns distance from this point and another one - * @param {fabric.Point} that - * @return {Number} - */ - distanceFrom: function (that) { - var dx = this.x - that.x, - dy = this.y - that.y; - return Math.sqrt(dx * dx + dy * dy); - }, - - /** - * Returns the point between this point and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - midPointFrom: function (that) { - return this.lerp(that); - }, - - /** - * Returns a new point which is the min of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - min: function (that) { - return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); - }, - - /** - * Returns a new point which is the max of this and another one - * @param {fabric.Point} that - * @return {fabric.Point} - */ - max: function (that) { - return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); - }, - - /** - * Returns string representation of this point - * @return {String} - */ - toString: function () { - return this.x + ',' + this.y; - }, - - /** - * Sets x/y of this point - * @param {Number} x - * @param {Number} y - * @chainable - */ - setXY: function (x, y) { - this.x = x; - this.y = y; - return this; - }, - - /** - * Sets x of this point - * @param {Number} x - * @chainable - */ - setX: function (x) { - this.x = x; - return this; - }, - - /** - * Sets y of this point - * @param {Number} y - * @chainable - */ - setY: function (y) { - this.y = y; - return this; - }, - - /** - * Sets x/y of this point from another point - * @param {fabric.Point} that - * @chainable - */ - setFromPoint: function (that) { - this.x = that.x; - this.y = that.y; - return this; - }, - - /** - * Swaps x/y of this point and another point - * @param {fabric.Point} that - */ - swap: function (that) { - var x = this.x, - y = this.y; - this.x = that.x; - this.y = that.y; - that.x = x; - that.y = y; - }, - - /** - * return a cloned instance of the point - * @return {fabric.Point} - */ - clone: function () { - return new Point(this.x, this.y); - } - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Intersection) { - fabric.warn('fabric.Intersection is already defined'); - return; - } - - /** - * Intersection class - * @class fabric.Intersection - * @memberOf fabric - * @constructor - */ - function Intersection(status) { - this.status = status; - this.points = []; - } - - fabric.Intersection = Intersection; - - fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { - - constructor: Intersection, - - /** - * Appends a point to intersection - * @param {fabric.Point} point - * @return {fabric.Intersection} thisArg - * @chainable - */ - appendPoint: function (point) { - this.points.push(point); - return this; - }, - - /** - * Appends points to intersection - * @param {Array} points - * @return {fabric.Intersection} thisArg - * @chainable - */ - appendPoints: function (points) { - this.points = this.points.concat(points); - return this; - } - }; - - /** - * Checks if one line intersects another - * TODO: rename in intersectSegmentSegment - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {fabric.Point} b1 - * @param {fabric.Point} b2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { - var result, - uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), - ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), - uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); - if (uB !== 0) { - var ua = uaT / uB, - ub = ubT / uB; - if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { - result = new Intersection('Intersection'); - result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); - } - else { - result = new Intersection(); - } - } - else { - if (uaT === 0 || ubT === 0) { - result = new Intersection('Coincident'); - } - else { - result = new Intersection('Parallel'); - } - } - return result; - }; - - /** - * Checks if line intersects polygon - * TODO: rename in intersectSegmentPolygon - * fix detection of coincident - * @static - * @param {fabric.Point} a1 - * @param {fabric.Point} a2 - * @param {Array} points - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { - var result = new Intersection(), - length = points.length, - b1, b2, inter; - - for (var i = 0; i < length; i++) { - b1 = points[i]; - b2 = points[(i + 1) % length]; - inter = Intersection.intersectLineLine(a1, a2, b1, b2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects another polygon - * @static - * @param {Array} points1 - * @param {Array} points2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { - var result = new Intersection(), - length = points1.length; - - for (var i = 0; i < length; i++) { - var a1 = points1[i], - a2 = points1[(i + 1) % length], - inter = Intersection.intersectLinePolygon(a1, a2, points2); - - result.appendPoints(inter.points); - } - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - - /** - * Checks if polygon intersects rectangle - * @static - * @param {Array} points - * @param {fabric.Point} r1 - * @param {fabric.Point} r2 - * @return {fabric.Intersection} - */ - fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { - var min = r1.min(r2), - max = r1.max(r2), - topRight = new fabric.Point(max.x, min.y), - bottomLeft = new fabric.Point(min.x, max.y), - inter1 = Intersection.intersectLinePolygon(min, topRight, points), - inter2 = Intersection.intersectLinePolygon(topRight, max, points), - inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), - inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), - result = new Intersection(); - - result.appendPoints(inter1.points); - result.appendPoints(inter2.points); - result.appendPoints(inter3.points); - result.appendPoints(inter4.points); - - if (result.points.length > 0) { - result.status = 'Intersection'; - } - return result; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Color) { - fabric.warn('fabric.Color is already defined.'); - return; - } - - /** - * Color class - * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; - * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. - * - * @class fabric.Color - * @param {String} color optional in hex or rgb(a) or hsl format or from known color list - * @return {fabric.Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} - */ - function Color(color) { - if (!color) { - this.setSource([0, 0, 0, 1]); - } - else { - this._tryParsingColor(color); - } - } - - fabric.Color = Color; - - fabric.Color.prototype = /** @lends fabric.Color.prototype */ { - - /** - * @private - * @param {String|Array} color Color value to parse - */ - _tryParsingColor: function(color) { - var source; - - if (color in Color.colorNameMap) { - color = Color.colorNameMap[color]; - } - - if (color === 'transparent') { - source = [255, 255, 255, 0]; - } - - if (!source) { - source = Color.sourceFromHex(color); - } - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (!source) { - //if color is not recognize let's make black as canvas does - source = [0, 0, 0, 1]; - } - if (source) { - this.setSource(source); - } - }, - - /** - * Adapted from https://github.com/mjijackson - * @private - * @param {Number} r Red color value - * @param {Number} g Green color value - * @param {Number} b Blue color value - * @return {Array} Hsl color - */ - _rgbToHsl: function(r, g, b) { - r /= 255; g /= 255; b /= 255; - - var h, s, l, - max = fabric.util.array.max([r, g, b]), - min = fabric.util.array.min([r, g, b]); - - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } - else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - - return [ - Math.round(h * 360), - Math.round(s * 100), - Math.round(l * 100) - ]; - }, - - /** - * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} - */ - getSource: function() { - return this._source; - }, - - /** - * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source - */ - setSource: function(source) { - this._source = source; - }, - - /** - * Returns color represenation in RGB format - * @return {String} ex: rgb(0-255,0-255,0-255) - */ - toRgb: function() { - var source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; - }, - - /** - * Returns color represenation in RGBA format - * @return {String} ex: rgba(0-255,0-255,0-255,0-1) - */ - toRgba: function() { - var source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; - }, - - /** - * Returns color represenation in HSL format - * @return {String} ex: hsl(0-360,0%-100%,0%-100%) - */ - toHsl: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; - }, - - /** - * Returns color represenation in HSLA format - * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) - */ - toHsla: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; - }, - - /** - * Returns color represenation in HEX format - * @return {String} ex: FF5555 - */ - toHex: function() { - var source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); - }, - - /** - * Gets value of alpha channel for this color - * @return {Number} 0-1 - */ - getAlpha: function() { - return this.getSource()[3]; - }, - - /** - * Sets value of alpha channel for this color - * @param {Number} alpha Alpha value 0-1 - * @return {fabric.Color} thisArg - */ - setAlpha: function(alpha) { - var source = this.getSource(); - source[3] = alpha; - this.setSource(source); - return this; - }, - - /** - * Transforms color to its grayscale representation - * @return {fabric.Color} thisArg - */ - toGrayscale: function() { - var source = this.getSource(), - average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), - currentAlpha = source[3]; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Transforms color to its black and white representation - * @param {Number} threshold - * @return {fabric.Color} thisArg - */ - toBlackWhite: function(threshold) { - var source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), - currentAlpha = source[3]; - - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Overlays color with another color - * @param {String|fabric.Color} otherColor - * @return {fabric.Color} thisArg - */ - overlayWith: function(otherColor) { - if (!(otherColor instanceof Color)) { - otherColor = new Color(otherColor); - } - - var result = [], - alpha = this.getAlpha(), - otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(); - - for (var i = 0; i < 3; i++) { - result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); - } - - result[3] = alpha; - this.setSource(result); - return this; - } - }; - - /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) - * @static - * @field - * @memberOf fabric.Color - */ - // eslint-disable-next-line max-len - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; - - /** - * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) - * @static - * @field - * @memberOf fabric.Color - */ - fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; - - /** - * Map of the 17 basic color names with HEX code - * @static - * @field - * @memberOf fabric.Color - * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units - */ - fabric.Color.colorNameMap = { - aqua: '#00FFFF', - black: '#000000', - blue: '#0000FF', - fuchsia: '#FF00FF', - gray: '#808080', - grey: '#808080', - green: '#008000', - lime: '#00FF00', - maroon: '#800000', - navy: '#000080', - olive: '#808000', - orange: '#FFA500', - purple: '#800080', - red: '#FF0000', - silver: '#C0C0C0', - teal: '#008080', - white: '#FFFFFF', - yellow: '#FFFF00' - }; - - /** - * @private - * @param {Number} p - * @param {Number} q - * @param {Number} t - * @return {Number} - */ - function hue2rgb(p, q, t) { - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1 / 6) { - return p + (q - p) * 6 * t; - } - if (t < 1 / 2) { - return q; - } - if (t < 2 / 3) { - return p + (q - p) * (2 / 3 - t) * 6; - } - return p; - } - - /** - * Returns new color object, when given a color in RGB format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255) - * @return {fabric.Color} - */ - fabric.Color.fromRgb = function(color) { - return Color.fromSource(Color.sourceFromRgb(color)); - }; - - /** - * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format - * @memberOf fabric.Color - * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {Array} source - */ - fabric.Color.sourceFromRgb = function(color) { - var match = color.match(Color.reRGBa); - if (match) { - var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), - g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), - b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); - - return [ - parseInt(r, 10), - parseInt(g, 10), - parseInt(b, 10), - match[4] ? parseFloat(match[4]) : 1 - ]; - } - }; - - /** - * Returns new color object, when given a color in RGBA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromRgba = Color.fromRgb; - - /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) - * @memberOf fabric.Color - * @return {fabric.Color} - */ - fabric.Color.fromHsl = function(color) { - return Color.fromSource(Color.sourceFromHsl(color)); - }; - - /** - * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. - * Adapted from https://github.com/mjijackson - * @memberOf fabric.Color - * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {Array} source - * @see http://http://www.w3.org/TR/css3-color/#hsl-color - */ - fabric.Color.sourceFromHsl = function(color) { - var match = color.match(Color.reHSLa); - if (!match) { - return; - } - - var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, - s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; - - if (s === 0) { - r = g = b = l; - } - else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, - p = l * 2 - q; - - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return [ - Math.round(r * 255), - Math.round(g * 255), - Math.round(b * 255), - match[4] ? parseFloat(match[4]) : 1 - ]; - }; - - /** - * Returns new color object, when given a color in HSLA format - * @static - * @function - * @memberOf fabric.Color - * @param {String} color - * @return {fabric.Color} - */ - fabric.Color.fromHsla = Color.fromHsl; - - /** - * Returns new color object, when given a color in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color Color value ex: FF5555 - * @return {fabric.Color} - */ - fabric.Color.fromHex = function(color) { - return Color.fromSource(Color.sourceFromHex(color)); - }; - - /** - * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format - * @static - * @memberOf fabric.Color - * @param {String} color ex: FF5555 or FF5544CC (RGBa) - * @return {Array} source - */ - fabric.Color.sourceFromHex = function(color) { - if (color.match(Color.reHex)) { - var value = color.slice(color.indexOf('#') + 1), - isShortNotation = (value.length === 3 || value.length === 4), - isRGBa = (value.length === 8 || value.length === 4), - r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), - g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), - b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), - a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; - - return [ - parseInt(r, 16), - parseInt(g, 16), - parseInt(b, 16), - parseFloat((parseInt(a, 16) / 255).toFixed(2)) - ]; - } - }; - - /** - * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @static - * @memberOf fabric.Color - * @param {Array} source - * @return {fabric.Color} - */ - fabric.Color.fromSource = function(source) { - var oColor = new Color(); - oColor.setSource(source); - return oColor; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - /* _FROM_SVG_START_ */ - function getColorStop(el) { - var style = el.getAttribute('style'), - offset = el.getAttribute('offset') || 0, - color, colorAlpha, opacity; - - // convert percents to absolute values - offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); - offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; - if (style) { - var keyValuePairs = style.split(/\s*;\s*/); - - if (keyValuePairs[keyValuePairs.length - 1] === '') { - keyValuePairs.pop(); - } - - for (var i = keyValuePairs.length; i--; ) { - - var split = keyValuePairs[i].split(/\s*:\s*/), - key = split[0].trim(), - value = split[1].trim(); - - if (key === 'stop-color') { - color = value; - } - else if (key === 'stop-opacity') { - opacity = value; - } - } - } - - if (!color) { - color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; - } - if (!opacity) { - opacity = el.getAttribute('stop-opacity'); - } - - color = new fabric.Color(color); - colorAlpha = color.getAlpha(); - opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); - opacity *= colorAlpha; - - return { - offset: offset, - color: color.toRgb(), - opacity: opacity - }; - } - - function getLinearCoords(el) { - return { - x1: el.getAttribute('x1') || 0, - y1: el.getAttribute('y1') || 0, - x2: el.getAttribute('x2') || '100%', - y2: el.getAttribute('y2') || 0 - }; - } - - function getRadialCoords(el) { - return { - x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', - y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', - r1: 0, - x2: el.getAttribute('cx') || '50%', - y2: el.getAttribute('cy') || '50%', - r2: el.getAttribute('r') || '50%' - }; - } - /* _FROM_SVG_END_ */ - - /** - * Gradient class - * @class fabric.Gradient - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients} - * @see {@link fabric.Gradient#initialize} for constructor definition - */ - fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { - - /** - * Horizontal offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetX: 0, - - /** - * Vertical offset for aligning gradients coming from SVG when outside pathgroups - * @type Number - * @default 0 - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops - * @return {fabric.Gradient} thisArg - */ - initialize: function(options) { - options || (options = { }); - - var coords = { }; - - this.id = fabric.Object.__uid++; - this.type = options.type || 'linear'; - - coords = { - x1: options.coords.x1 || 0, - y1: options.coords.y1 || 0, - x2: options.coords.x2 || 0, - y2: options.coords.y2 || 0 - }; - - if (this.type === 'radial') { - coords.r1 = options.coords.r1 || 0; - coords.r2 = options.coords.r2 || 0; - } - this.coords = coords; - this.colorStops = options.colorStops.slice(); - if (options.gradientTransform) { - this.gradientTransform = options.gradientTransform; - } - this.offsetX = options.offsetX || this.offsetX; - this.offsetY = options.offsetY || this.offsetY; - }, - - /** - * Adds another colorStop - * @param {Object} colorStop Object with offset and color - * @return {fabric.Gradient} thisArg - */ - addColorStop: function(colorStop) { - for (var position in colorStop) { - var color = new fabric.Color(colorStop[position]); - this.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - return this; - }, - - /** - * Returns object representation of a gradient - * @return {Object} - */ - toObject: function() { - return { - type: this.type, - coords: this.coords, - colorStops: this.colorStops, - offsetX: this.offsetX, - offsetY: this.offsetY, - gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an gradient - * @param {Object} object Object to create a gradient for - * @return {String} SVG representation of an gradient (linear/radial) - */ - toSVG: function(object) { - var coords = fabric.util.object.clone(this.coords), - markup, commonAttributes; - - // colorStops must be sorted ascending - this.colorStops.sort(function(a, b) { - return a.offset - b.offset; - }); - - if (!(object.group && object.group.type === 'path-group')) { - for (var prop in coords) { - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - coords[prop] += this.offsetX - object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += this.offsetY - object.height / 2; - } - } - } - - commonAttributes = 'id="SVGID_' + this.id + - '" gradientUnits="userSpaceOnUse"'; - if (this.gradientTransform) { - commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; - } - if (this.type === 'linear') { - markup = [ - '\n' - ]; - } - else if (this.type === 'radial') { - markup = [ - '\n' - ]; - } - - for (var i = 0; i < this.colorStops.length; i++) { - markup.push( - '\n' - ); - } - - markup.push((this.type === 'linear' ? '\n' : '\n')); - - return markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasGradient - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Object} object - * @return {CanvasGradient} - */ - toLive: function(ctx, object) { - var gradient, prop, coords = fabric.util.object.clone(this.coords); - - if (!this.type) { - return; - } - - if (object.group && object.group.type === 'path-group') { - for (prop in coords) { - if (prop === 'x1' || prop === 'x2') { - coords[prop] += -this.offsetX + object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += -this.offsetY + object.height / 2; - } - } - } - - if (this.type === 'linear') { - gradient = ctx.createLinearGradient( - coords.x1, coords.y1, coords.x2, coords.y2); - } - else if (this.type === 'radial') { - gradient = ctx.createRadialGradient( - coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); - } - - for (var i = 0, len = this.colorStops.length; i < len; i++) { - var color = this.colorStops[i].color, - opacity = this.colorStops[i].opacity, - offset = this.colorStops[i].offset; - - if (typeof opacity !== 'undefined') { - color = new fabric.Color(color).setAlpha(opacity).toRgba(); - } - gradient.addColorStop(parseFloat(offset), color); - } - - return gradient; - } - }); - - fabric.util.object.extend(fabric.Gradient, { - - /* _FROM_SVG_START_ */ - /** - * Returns {@link fabric.Gradient} instance from an SVG element - * @static - * @memberOf fabric.Gradient - * @param {SVGGradientElement} el SVG gradient element - * @param {fabric.Object} instance - * @return {fabric.Gradient} Gradient instance - * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement - * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement - */ - fromElement: function(el, instance) { - - /** - * @example: - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - * OR - * - * - * - * - * - * - * - */ - - var colorStopEls = el.getElementsByTagName('stop'), - type, - gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', - gradientTransform = el.getAttribute('gradientTransform'), - colorStops = [], - coords, ellipseMatrix; - - if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') { - type = 'linear'; - } - else { - type = 'radial'; - } - - if (type === 'linear') { - coords = getLinearCoords(el); - } - else if (type === 'radial') { - coords = getRadialCoords(el); - } - - for (var i = colorStopEls.length; i--; ) { - colorStops.push(getColorStop(colorStopEls[i])); - } - - ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); - - var gradient = new fabric.Gradient({ - type: type, - coords: coords, - colorStops: colorStops, - offsetX: -instance.left, - offsetY: -instance.top - }); - - if (gradientTransform || ellipseMatrix !== '') { - gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); - } - return gradient; - }, - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Gradient} instance from its object representation - * @static - * @memberOf fabric.Gradient - * @param {Object} obj - * @param {Object} [options] Options object - */ - forObject: function(obj, options) { - options || (options = { }); - _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); - return new fabric.Gradient(options); - } - }); - - /** - * @private - */ - function _convertPercentUnitsToValues(object, options, gradientUnits) { - var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; - for (var prop in options) { - if (options[prop] === 'Infinity') { - options[prop] = 1; - } - else if (options[prop] === '-Infinity') { - options[prop] = 0; - } - propValue = parseFloat(options[prop], 10); - if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { - multFactor = 0.01; - } - else { - multFactor = 1; - } - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; - } - else if (prop === 'y1' || prop === 'y2') { - multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; - addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; - } - options[prop] = propValue * multFactor + addFactor; - } - if (object.type === 'ellipse' && - options.r2 !== null && - gradientUnits === 'objectBoundingBox' && - object.rx !== object.ry) { - - var scaleFactor = object.ry / object.rx; - ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; - if (options.y1) { - options.y1 /= scaleFactor; - } - if (options.y2) { - options.y2 /= scaleFactor; - } - } - return ellipseMatrix; - } -})(); - - -/** - * Pattern class - * @class fabric.Pattern - * @see {@link http://fabricjs.com/patterns|Pattern demo} - * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo} - * @see {@link fabric.Pattern#initialize} for constructor definition - */ -fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { - - /** - * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @type String - * @default - */ - repeat: 'repeat', - - /** - * Pattern horizontal offset from object's left/top corner - * @type Number - * @default - */ - offsetX: 0, - - /** - * Pattern vertical offset from object's left/top corner - * @type Number - * @default - */ - offsetY: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Pattern} thisArg - */ - initialize: function(options) { - options || (options = { }); - - this.id = fabric.Object.__uid++; - - if (options.source) { - if (typeof options.source === 'string') { - // function string - if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { - this.source = new Function(fabric.util.getFunctionBody(options.source)); - } - else { - // img src string - var _this = this; - this.source = fabric.util.createImage(); - fabric.util.loadImage(options.source, function(img) { - _this.source = img; - }); - } - } - else { - // img element - this.source = options.source; - } - } - if (options.repeat) { - this.repeat = options.repeat; - } - if (options.offsetX) { - this.offsetX = options.offsetX; - } - if (options.offsetY) { - this.offsetY = options.offsetY; - } - }, - - /** - * Returns object representation of a pattern - * @return {Object} Object representation of a pattern instance - */ - toObject: function() { - - var source; - - // callback - if (typeof this.source === 'function') { - source = String(this.source); - } - // element - else if (typeof this.source.src === 'string') { - source = this.source.src; - } - // element - else if (typeof this.source === 'object' && this.source.toDataURL) { - source = this.source.toDataURL(); - } - - return { - source: source, - repeat: this.repeat, - offsetX: this.offsetX, - offsetY: this.offsetY - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a pattern - * @param {fabric.Object} object - * @return {String} SVG representation of a pattern - */ - toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source, - patternWidth = patternSource.width / object.getWidth(), - patternHeight = patternSource.height / object.getHeight(), - patternOffsetX = this.offsetX / object.getWidth(), - patternOffsetY = this.offsetY / object.getHeight(), - patternImgSrc = ''; - if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { - patternHeight = 1; - } - if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { - patternWidth = 1; - } - if (patternSource.src) { - patternImgSrc = patternSource.src; - } - else if (patternSource.toDataURL) { - patternImgSrc = patternSource.toDataURL(); - } - - return '\n' + - '\n' + - '\n'; - }, - /* _TO_SVG_END_ */ - - /** - * Returns an instance of CanvasPattern - * @param {CanvasRenderingContext2D} ctx Context to create pattern - * @return {CanvasPattern} - */ - toLive: function(ctx) { - var source = typeof this.source === 'function' - ? this.source() - : this.source; - - // if the image failed to load, return, and allow rest to continue loading - if (!source) { - return ''; - } - - // if an image - if (typeof source.src !== 'undefined') { - if (!source.complete) { - return ''; - } - if (source.naturalWidth === 0 || source.naturalHeight === 0) { - return ''; - } - } - return ctx.createPattern(source, this.repeat); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - toFixed = fabric.util.toFixed; - - if (fabric.Shadow) { - fabric.warn('fabric.Shadow is already defined.'); - return; - } - - /** - * Shadow class - * @class fabric.Shadow - * @see {@link http://fabricjs.com/shadows|Shadow demo} - * @see {@link fabric.Shadow#initialize} for constructor definition - */ - fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { - - /** - * Shadow color - * @type String - * @default - */ - color: 'rgb(0,0,0)', - - /** - * Shadow blur - * @type Number - */ - blur: 0, - - /** - * Shadow horizontal offset - * @type Number - * @default - */ - offsetX: 0, - - /** - * Shadow vertical offset - * @type Number - * @default - */ - offsetY: 0, - - /** - * Whether the shadow should affect stroke operations - * @type Boolean - * @default - */ - affectStroke: false, - - /** - * Indicates whether toObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Constructor - * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Shadow} thisArg - */ - initialize: function(options) { - - if (typeof options === 'string') { - options = this._parseShadow(options); - } - - for (var prop in options) { - this[prop] = options[prop]; - } - - this.id = fabric.Object.__uid++; - }, - - /** - * @private - * @param {String} shadow Shadow value to parse - * @return {Object} Shadow object with color, offsetX, offsetY and blur - */ - _parseShadow: function(shadow) { - var shadowStr = shadow.trim(), - offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], - color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; - - return { - color: color.trim(), - offsetX: parseInt(offsetsAndBlur[1], 10) || 0, - offsetY: parseInt(offsetsAndBlur[2], 10) || 0, - blur: parseInt(offsetsAndBlur[3], 10) || 0 - }; - }, - - /** - * Returns a string representation of an instance - * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow - * @return {String} Returns CSS3 text-shadow declaration - */ - toString: function() { - return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of a shadow - * @param {fabric.Object} object - * @return {String} SVG representation of a shadow - */ - toSVG: function(object) { - var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - offset = fabric.util.rotateVector( - { x: this.offsetX, y: this.offsetY }, - fabric.util.degreesToRadians(-object.angle)), - BLUR_BOX = 20; - - if (object.width && object.height) { - //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion - // we add some extra space to filter box to contain the blur ( 20 ) - fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; - fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; - } - if (object.flipX) { - offset.x *= -1; - } - if (object.flipY) { - offset.y *= -1; - } - return ( - '\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\t\n' + - '\t\t\n' + - '\t\n' + - '\n'); - }, - /* _TO_SVG_END_ */ - - /** - * Returns object representation of a shadow - * @return {Object} Object representation of a shadow instance - */ - toObject: function() { - if (this.includeDefaultValues) { - return { - color: this.color, - blur: this.blur, - offsetX: this.offsetX, - offsetY: this.offsetY, - affectStroke: this.affectStroke - }; - } - var obj = { }, proto = fabric.Shadow.prototype; - - ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function(prop) { - if (this[prop] !== proto[prop]) { - obj[prop] = this[prop]; - } - }, this); - - return obj; - } - }); - - /** - * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") - * @static - * @field - * @memberOf fabric.Shadow - */ - // eslint-disable-next-line max-len - fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function () { - - 'use strict'; - - if (fabric.StaticCanvas) { - fabric.warn('fabric.StaticCanvas is already defined.'); - return; - } - - // aliases for faster resolution - var extend = fabric.util.object.extend, - getElementOffset = fabric.util.getElementOffset, - removeFromArray = fabric.util.removeFromArray, - toFixed = fabric.util.toFixed, - - CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); - - /** - * Static canvas class - * @class fabric.StaticCanvas - * @mixes fabric.Collection - * @mixes fabric.Observable - * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo} - * @see {@link fabric.StaticCanvas#initialize} for constructor definition - * @fires before:render - * @fires after:render - * @fires canvas:cleared - * @fires object:added - * @fires object:removed - */ - fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - }, - - /** - * Background color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. - * @type {(String|fabric.Pattern)} - * @default - */ - backgroundColor: '', - - /** - * Background image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. - * Backwards incompatibility note: The "backgroundImageOpacity" - * and "backgroundImageStretch" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. - * @type fabric.Image - * @default - */ - backgroundImage: null, - - /** - * Overlay color of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayColor} - * @since 1.3.9 - * @type {(String|fabric.Pattern)} - * @default - */ - overlayColor: '', - - /** - * Overlay image of canvas instance. - * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. - * Backwards incompatibility note: The "overlayImageLeft" - * and "overlayImageTop" properties are deprecated since 1.3.9. - * Use {@link fabric.Image#left} and {@link fabric.Image#top}. - * @type fabric.Image - * @default - */ - overlayImage: null, - - /** - * Indicates whether toObject/toDatalessObject should include default values - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Indicates whether objects' state should be saved - * @type Boolean - * @default - */ - stateful: true, - - /** - * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. - * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once - * (followed by a manual rendering after addition/deletion) - * @type Boolean - * @default - */ - renderOnAddRemove: true, - - /** - * Function that determines clipping of entire canvas area - * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} - * @type Function - * @default - */ - clipTo: null, - - /** - * Indicates whether object controls (borders/controls) are rendered above overlay image - * @type Boolean - * @default - */ - controlsAboveOverlay: false, - - /** - * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas - * @type Boolean - * @default - */ - allowTouchScrolling: false, - - /** - * Indicates whether this canvas will use image smoothing, this is on by default in browsers - * @type Boolean - * @default - */ - imageSmoothingEnabled: true, - - /** - * The transformation (in the format of Canvas transform) which focuses the viewport - * @type Array - * @default - */ - viewportTransform: [1, 0, 0, 1, 0, 0], - - /** - * if set to false background image is not affected by viewport transform - * @since 1.6.3 - * @type Boolean - * @default - */ - backgroundVpt: true, - - /** - * if set to false overlya image is not affected by viewport transform - * @since 1.6.3 - * @type Boolean - * @default - */ - overlayVpt: true, - - /** - * Callback; invoked right before object is about to be scaled/rotated - */ - onBeforeScaleRotate: function () { - /* NOOP */ - }, - - /** - * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens - */ - enableRetinaScaling: true, - - /** - * @private - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - */ - _initStatic: function(el, options) { - var cb = fabric.StaticCanvas.prototype.renderAll.bind(this); - this._objects = []; - this._createLowerCanvas(el); - this._initOptions(options); - this._setImageSmoothing(); - // only initialize retina scaling once - if (!this.interactive) { - this._initRetinaScaling(); - } - - if (options.overlayImage) { - this.setOverlayImage(options.overlayImage, cb); - } - if (options.backgroundImage) { - this.setBackgroundImage(options.backgroundImage, cb); - } - if (options.backgroundColor) { - this.setBackgroundColor(options.backgroundColor, cb); - } - if (options.overlayColor) { - this.setOverlayColor(options.overlayColor, cb); - } - this.calcOffset(); - }, - - /** - * @private - */ - _isRetinaScaling: function() { - return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); - }, - - /** - * @private - * @return {Number} retinaScaling if applied, otherwise 1; - */ - getRetinaScaling: function() { - return this._isRetinaScaling() ? fabric.devicePixelRatio : 1; - }, - - /** - * @private - */ - _initRetinaScaling: function() { - if (!this._isRetinaScaling()) { - return; - } - this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio); - this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio); - - this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio); - }, - - /** - * Calculates canvas element offset relative to the document - * This method is also attached as "resize" event handler of window - * @return {fabric.Canvas} instance - * @chainable - */ - calcOffset: function () { - this._offset = getElementOffset(this.lowerCanvasEl); - return this; - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to - * @param {Function} callback callback to invoke when image is loaded and set as an overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} - * @example Normal overlayImage with left/top = 0 - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage with different properties - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched overlayImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched overlayImage #2 - width/height correspond to canvas width/height - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position overlayImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example overlayImage loaded from cross-origin - * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top', - * crossOrigin: 'anonymous' - * }); - */ - setOverlayImage: function (image, callback, options) { - return this.__setBgOverlayImage('overlayImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas - * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to - * @param {Function} callback Callback to invoke when image is loaded and set as background - * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} - * @example Normal backgroundImage with left/top = 0 - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage with different properties - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top' - * }); - * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height - * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { - * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); - * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); - * }); - * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * width: canvas.width, - * height: canvas.height, - * // Needed to position backgroundImage at 0/0 - * originX: 'left', - * originY: 'top' - * }); - * @example backgroundImage loaded from cross-origin - * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { - * opacity: 0.5, - * angle: 45, - * left: 400, - * top: 400, - * originX: 'left', - * originY: 'top', - * crossOrigin: 'anonymous' - * }); - */ - setBackgroundImage: function (image, callback, options) { - return this.__setBgOverlayImage('backgroundImage', image, callback, options); - }, - - /** - * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas - * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} - * @example Normal overlayColor - color value - * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as overlayColor with repeat and offset - * canvas.setOverlayColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setOverlayColor: function(overlayColor, callback) { - return this.__setBgOverlayColor('overlayColor', overlayColor, callback); - }, - - /** - * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas - * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to - * @param {Function} callback Callback to invoke when background color is set - * @return {fabric.Canvas} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} - * @example Normal backgroundColor - color value - * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png' - * }, canvas.renderAll.bind(canvas)); - * @example fabric.Pattern used as backgroundColor with repeat and offset - * canvas.setBackgroundColor({ - * source: 'http://fabricjs.com/assets/escheresque_ste.png', - * repeat: 'repeat', - * offsetX: 200, - * offsetY: 100 - * }, canvas.renderAll.bind(canvas)); - */ - setBackgroundColor: function(backgroundColor, callback) { - return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); - }, - - /** - * @private - * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} - */ - _setImageSmoothing: function() { - var ctx = this.getContext(); - - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled - || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled; - ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} - * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) - * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to - * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay - * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. - */ - __setBgOverlayImage: function(property, image, callback, options) { - if (typeof image === 'string') { - fabric.util.loadImage(image, function(img) { - img && (this[property] = new fabric.Image(img, options)); - callback && callback(img); - }, this, options && options.crossOrigin); - } - else { - options && image.setOptions(options); - this[property] = image; - callback && callback(image); - } - - return this; - }, - - /** - * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} - * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) - * @param {(Object|String|null)} color Object with pattern information, color value or null - * @param {Function} [callback] Callback is invoked when color is set - */ - __setBgOverlayColor: function(property, color, callback) { - if (color && color.source) { - var _this = this; - fabric.util.loadImage(color.source, function(img) { - _this[property] = new fabric.Pattern({ - source: img, - repeat: color.repeat, - offsetX: color.offsetX, - offsetY: color.offsetY - }); - callback && callback(); - }); - } - else { - this[property] = color; - callback && callback(); - } - - return this; - }, - - /** - * @private - */ - _createCanvasElement: function(canvasEl) { - var element = fabric.util.createCanvasElement(canvasEl) - if (!element.style) { - element.style = { }; - } - if (!element) { - throw CANVAS_INIT_ERROR; - } - if (typeof element.getContext === 'undefined') { - throw CANVAS_INIT_ERROR; - } - return element; - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initOptions: function (options) { - for (var prop in options) { - this[prop] = options[prop]; - } - - this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; - - if (!this.lowerCanvasEl.style) { - return; - } - - this.lowerCanvasEl.width = this.width; - this.lowerCanvasEl.height = this.height; - - this.lowerCanvasEl.style.width = this.width + 'px'; - this.lowerCanvasEl.style.height = this.height + 'px'; - - this.viewportTransform = this.viewportTransform.slice(); - }, - - /** - * Creates a bottom canvas - * @private - * @param {HTMLElement} [canvasEl] - */ - _createLowerCanvas: function (canvasEl) { - this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(canvasEl); - - fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); - - if (this.interactive) { - this._applyCanvasStyle(this.lowerCanvasEl); - } - - this.contextContainer = this.lowerCanvasEl.getContext('2d'); - }, - - /** - * Returns canvas width (in px) - * @return {Number} - */ - getWidth: function () { - return this.width; - }, - - /** - * Returns canvas height (in px) - * @return {Number} - */ - getHeight: function () { - return this.height; - }, - - /** - * Sets width of this canvas instance - * @param {Number|String} value Value to set width to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setWidth: function (value, options) { - return this.setDimensions({ width: value }, options); - }, - - /** - * Sets height of this canvas instance - * @param {Number|String} value Value to set height to - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} instance - * @chainable true - */ - setHeight: function (value, options) { - return this.setDimensions({ height: value }, options); - }, - - /** - * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) - * @param {Object} dimensions Object with width/height properties - * @param {Number|String} [dimensions.width] Width of canvas element - * @param {Number|String} [dimensions.height] Height of canvas element - * @param {Object} [options] Options object - * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions - * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions - * @return {fabric.Canvas} thisArg - * @chainable - */ - setDimensions: function (dimensions, options) { - var cssValue; - - options = options || {}; - - for (var prop in dimensions) { - cssValue = dimensions[prop]; - - if (!options.cssOnly) { - this._setBackstoreDimension(prop, dimensions[prop]); - cssValue += 'px'; - } - - if (!options.backstoreOnly) { - this._setCssDimension(prop, cssValue); - } - } - this._initRetinaScaling(); - this._setImageSmoothing(); - this.calcOffset(); - - if (!options.cssOnly) { - this.renderAll(); - } - - return this; - }, - - /** - * Helper for setting width/height - * @private - * @param {String} prop property (width|height) - * @param {Number} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setBackstoreDimension: function (prop, value) { - this.lowerCanvasEl[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl[prop] = value; - } - - if (this.cacheCanvasEl) { - this.cacheCanvasEl[prop] = value; - } - - this[prop] = value; - - return this; - }, - - /** - * Helper for setting css width/height - * @private - * @param {String} prop property (width|height) - * @param {String} value value to set property to - * @return {fabric.Canvas} instance - * @chainable true - */ - _setCssDimension: function (prop, value) { - this.lowerCanvasEl.style[prop] = value; - - if (this.upperCanvasEl) { - this.upperCanvasEl.style[prop] = value; - } - - if (this.wrapperEl) { - this.wrapperEl.style[prop] = value; - } - - return this; - }, - - /** - * Returns canvas zoom level - * @return {Number} - */ - getZoom: function () { - return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); - }, - - /** - * Sets viewport transform of this canvas instance - * @param {Array} vpt the transform in the form of context.transform - * @return {fabric.Canvas} instance - * @chainable true - */ - setViewportTransform: function (vpt) { - var activeGroup = this._activeGroup, object; - this.viewportTransform = vpt; - for (var i = 0, len = this._objects.length; i < len; i++) { - object = this._objects[i]; - object.group || object.setCoords(); - } - if (activeGroup) { - activeGroup.setCoords(); - } - this.renderAll(); - return this; - }, - - /** - * Sets zoom level of this canvas instance, zoom centered around point - * @param {fabric.Point} point to zoom with respect to - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - zoomToPoint: function (point, value) { - // TODO: just change the scale, preserve other transformations - var before = point, vpt = this.viewportTransform.slice(0); - point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); - vpt[0] = value; - vpt[3] = value; - var after = fabric.util.transformPoint(point, vpt); - vpt[4] += before.x - after.x; - vpt[5] += before.y - after.y; - return this.setViewportTransform(vpt); - }, - - /** - * Sets zoom level of this canvas instance - * @param {Number} value to set zoom to, less than 1 zooms out - * @return {fabric.Canvas} instance - * @chainable true - */ - setZoom: function (value) { - this.zoomToPoint(new fabric.Point(0, 0), value); - return this; - }, - - /** - * Pan viewport so as to place point at top left corner of canvas - * @param {fabric.Point} point to move to - * @return {fabric.Canvas} instance - * @chainable true - */ - absolutePan: function (point) { - var vpt = this.viewportTransform.slice(0); - vpt[4] = -point.x; - vpt[5] = -point.y; - return this.setViewportTransform(vpt); - }, - - /** - * Pans viewpoint relatively - * @param {fabric.Point} point (position vector) to move by - * @return {fabric.Canvas} instance - * @chainable true - */ - relativePan: function (point) { - return this.absolutePan(new fabric.Point( - -point.x - this.viewportTransform[4], - -point.y - this.viewportTransform[5] - )); - }, - - /** - * Returns <canvas> element corresponding to this instance - * @return {HTMLCanvasElement} - */ - getElement: function () { - return this.lowerCanvasEl; - }, - - /** - * @private - * @param {fabric.Object} obj Object that was added - */ - _onObjectAdded: function(obj) { - this.stateful && obj.setupState(); - obj._set('canvas', this); - obj.setCoords(); - this.fire('object:added', { target: obj }); - obj.fire('added'); - }, - - /** - * @private - * @param {fabric.Object} obj Object that was removed - */ - _onObjectRemoved: function(obj) { - this.fire('object:removed', { target: obj }); - obj.fire('removed'); - delete obj.canvas; - }, - - /** - * Clears specified context of canvas element - * @param {CanvasRenderingContext2D} ctx Context to clear - * @return {fabric.Canvas} thisArg - * @chainable - */ - clearContext: function(ctx) { - ctx.clearRect(0, 0, this.width, this.height); - return this; - }, - - /** - * Returns context of canvas where objects are drawn - * @return {CanvasRenderingContext2D} - */ - getContext: function () { - return this.contextContainer; - }, - - /** - * Clears all contexts (background, main, top) of an instance - * @return {fabric.Canvas} thisArg - * @chainable - */ - clear: function () { - this._objects.length = 0; - this.backgroundImage = null; - this.overlayImage = null; - this.backgroundColor = ''; - this.overlayColor = '' - if (this._hasITextHandlers) { - this.off('selection:cleared', this._canvasITextSelectionClearedHanlder); - this.off('object:selected', this._canvasITextSelectionClearedHanlder); - this.off('mouse:up', this._mouseUpITextHandler); - this._iTextInstances = null; - this._hasITextHandlers = false; - } - this.clearContext(this.contextContainer); - this.fire('canvas:cleared'); - this.renderAll(); - return this; - }, - - /** - * Renders both the canvas. - * @return {fabric.Canvas} instance - * @chainable - */ - renderAll: function () { - var canvasToDrawOn = this.contextContainer; - this.renderCanvas(canvasToDrawOn, this._objects); - return this; - }, - - /** - * Renders background, objects, overlay and controls. - * @param {CanvasRenderingContext2D} ctx - * @param {Array} objects to render - * @return {fabric.Canvas} instance - * @chainable - */ - renderCanvas: function(ctx, objects) { - this.clearContext(ctx); - this.fire('before:render'); - if (this.clipTo) { - fabric.util.clipContext(this, ctx); - } - this._renderBackground(ctx); - - ctx.save(); - //apply viewport transform once for all rendering process - ctx.transform.apply(ctx, this.viewportTransform); - this._renderObjects(ctx, objects); - ctx.restore(); - if (!this.controlsAboveOverlay && this.interactive) { - this.drawControls(ctx); - } - if (this.clipTo) { - ctx.restore(); - } - this._renderOverlay(ctx); - if (this.controlsAboveOverlay && this.interactive) { - this.drawControls(ctx); - } - this.fire('after:render'); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Array} objects to render - */ - _renderObjects: function(ctx, objects) { - for (var i = 0, length = objects.length; i < length; ++i) { - objects[i] && objects[i].render(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {string} property 'background' or 'overlay' - */ - _renderBackgroundOrOverlay: function(ctx, property) { - var object = this[property + 'Color']; - if (object) { - ctx.fillStyle = object.toLive - ? object.toLive(ctx) - : object; - - ctx.fillRect( - object.offsetX || 0, - object.offsetY || 0, - this.width, - this.height); - } - object = this[property + 'Image']; - if (object) { - if (this[property + 'Vpt']) { - ctx.save(); - ctx.transform.apply(ctx, this.viewportTransform); - } - object.render(ctx); - this[property + 'Vpt'] && ctx.restore(); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderBackground: function(ctx) { - this._renderBackgroundOrOverlay(ctx, 'background'); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderOverlay: function(ctx) { - this._renderBackgroundOrOverlay(ctx, 'overlay'); - }, - - /** - * Returns coordinates of a center of canvas. - * Returned value is an object with top and left properties - * @return {Object} object with "top" and "left" number values - */ - getCenter: function () { - return { - top: this.getHeight() / 2, - left: this.getWidth() / 2 - }; - }, - - /** - * Centers object horizontally in the canvas - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center horizontally - * @return {fabric.Canvas} thisArg - */ - centerObjectH: function (object) { - return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); - }, - - /** - * Centers object vertically in the canvas - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObjectV: function (object) { - return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); - }, - - /** - * Centers object vertically and horizontally in the canvas - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - centerObject: function(object) { - var center = this.getCenter(); - - return this._centerObject(object, new fabric.Point(center.left, center.top)); - }, - - /** - * Centers object vertically and horizontally in the viewport - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - viewportCenterObject: function(object) { - var vpCenter = this.getVpCenter(); - - return this._centerObject(object, vpCenter); - }, - - /** - * Centers object horizontally in the viewport, object.top is unchanged - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - viewportCenterObjectH: function(object) { - var vpCenter = this.getVpCenter(); - this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y)); - return this; - }, - - /** - * Centers object Vertically in the viewport, object.top is unchanged - * You might need to call `setCoords` on an object after centering, to update controls area. - * @param {fabric.Object} object Object to center vertically and horizontally - * @return {fabric.Canvas} thisArg - * @chainable - */ - viewportCenterObjectV: function(object) { - var vpCenter = this.getVpCenter(); - - return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y)); - }, - - /** - * Calculate the point in canvas that correspond to the center of actual viewport. - * @return {fabric.Point} vpCenter, viewport center - * @chainable - */ - getVpCenter: function() { - var center = this.getCenter(), - iVpt = fabric.util.invertTransform(this.viewportTransform); - return fabric.util.transformPoint({ x: center.left, y: center.top }, iVpt); - }, - - /** - * @private - * @param {fabric.Object} object Object to center - * @param {fabric.Point} center Center point - * @return {fabric.Canvas} thisArg - * @chainable - */ - _centerObject: function(object, center) { - object.setPositionByOrigin(center, 'center', 'center'); - this.renderAll(); - return this; - }, - - /** - * Returs dataless JSON representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} json string - */ - toDatalessJSON: function (propertiesToInclude) { - return this.toDatalessObject(propertiesToInclude); - }, - - /** - * Returns object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function (propertiesToInclude) { - return this._toObjectMethod('toObject', propertiesToInclude); - }, - - /** - * Returns dataless object representation of canvas - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function (propertiesToInclude) { - return this._toObjectMethod('toDatalessObject', propertiesToInclude); - }, - - /** - * @private - */ - _toObjectMethod: function (methodName, propertiesToInclude) { - - var data = { - objects: this._toObjects(methodName, propertiesToInclude) - }; - - extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); - - fabric.util.populateWithProperties(this, data, propertiesToInclude); - - return data; - }, - - /** - * @private - */ - _toObjects: function(methodName, propertiesToInclude) { - return this.getObjects().filter(function(object) { - return !object.excludeFromExport; - }).map(function(instance) { - return this._toObject(instance, methodName, propertiesToInclude); - }, this); - }, - - /** - * @private - */ - _toObject: function(instance, methodName, propertiesToInclude) { - var originalValue; - - if (!this.includeDefaultValues) { - originalValue = instance.includeDefaultValues; - instance.includeDefaultValues = false; - } - - var object = instance[methodName](propertiesToInclude); - if (!this.includeDefaultValues) { - instance.includeDefaultValues = originalValue; - } - return object; - }, - - /** - * @private - */ - __serializeBgOverlay: function(methodName, propertiesToInclude) { - var data = { - background: (this.backgroundColor && this.backgroundColor.toObject) - ? this.backgroundColor.toObject(propertiesToInclude) - : this.backgroundColor - }; - - if (this.overlayColor) { - data.overlay = this.overlayColor.toObject - ? this.overlayColor.toObject(propertiesToInclude) - : this.overlayColor; - } - if (this.backgroundImage) { - data.backgroundImage = this._toObject(this.backgroundImage, methodName, propertiesToInclude); - } - if (this.overlayImage) { - data.overlayImage = this._toObject(this.overlayImage, methodName, propertiesToInclude); - } - - return data; - }, - - /* _TO_SVG_START_ */ - /** - * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, - * a zoomed canvas will then produce zoomed SVG output. - * @type Boolean - * @default - */ - svgViewportTransformation: true, - - /** - * Returns SVG representation of canvas - * @function - * @param {Object} [options] Options object for SVG output - * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included - * @param {Object} [options.viewBox] SVG viewbox object - * @param {Number} [options.viewBox.x] x-cooridnate of viewbox - * @param {Number} [options.viewBox.y] y-coordinate of viewbox - * @param {Number} [options.viewBox.width] Width of viewbox - * @param {Number} [options.viewBox.height] Height of viewbox - * @param {String} [options.encoding=UTF-8] Encoding of SVG output - * @param {String} [options.width] desired width of svg with or without units - * @param {String} [options.height] desired height of svg with or without units - * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. - * @return {String} SVG string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} - * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} - * @example Normal SVG output - * var svg = canvas.toSVG(); - * @example SVG output without preamble (without <?xml ../>) - * var svg = canvas.toSVG({suppressPreamble: true}); - * @example SVG output with viewBox attribute - * var svg = canvas.toSVG({ - * viewBox: { - * x: 100, - * y: 100, - * width: 200, - * height: 300 - * } - * }); - * @example SVG output with different encoding (default: UTF-8) - * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); - * @example Modify SVG output with reviver function - * var svg = canvas.toSVG(null, function(svg) { - * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); - * }); - */ - toSVG: function(options, reviver) { - options || (options = { }); - - var markup = []; - - this._setSVGPreamble(markup, options); - this._setSVGHeader(markup, options); - - this._setSVGBgOverlayColor(markup, 'backgroundColor'); - this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); - - this._setSVGObjects(markup, reviver); - - this._setSVGBgOverlayColor(markup, 'overlayColor'); - this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); - - markup.push(''); - - return markup.join(''); - }, - - /** - * @private - */ - _setSVGPreamble: function(markup, options) { - if (options.suppressPreamble) { - return; - } - markup.push( - '\n', - '\n' - ); - }, - - /** - * @private - */ - _setSVGHeader: function(markup, options) { - var width = options.width || this.width, - height = options.height || this.height, - vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ', - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; - - if (options.viewBox) { - viewBox = 'viewBox="' + - options.viewBox.x + ' ' + - options.viewBox.y + ' ' + - options.viewBox.width + ' ' + - options.viewBox.height + '" '; - } - else { - if (this.svgViewportTransformation) { - vpt = this.viewportTransform; - viewBox = 'viewBox="' + - toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' + - toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' + - toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' + - toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" '; - } - } - - markup.push( - '\n', - 'Created with Fabric.js ', fabric.version, '\n', - '', - fabric.createSVGFontFacesMarkup(this.getObjects()), - fabric.createSVGRefElementsMarkup(this), - '\n' - ); - }, - - /** - * @private - */ - _setSVGObjects: function(markup, reviver) { - var instance; - for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { - instance = objects[i]; - if (instance.excludeFromExport) { - continue; - } - this._setSVGObject(markup, instance, reviver); - } - }, - - /** - * push single object svg representation in the markup - * @private - */ - _setSVGObject: function(markup, instance, reviver) { - markup.push(instance.toSVG(reviver)); - }, - - /** - * @private - */ - _setSVGBgOverlayImage: function(markup, property, reviver) { - if (this[property] && this[property].toSVG) { - markup.push(this[property].toSVG(reviver)); - } - }, - - /** - * @private - */ - _setSVGBgOverlayColor: function(markup, property) { - if (this[property] && this[property].source) { - markup.push( - '\n' - ); - } - else if (this[property] && property === 'overlayColor') { - markup.push( - '\n' - ); - } - }, - /* _TO_SVG_END_ */ - - /** - * Moves an object or the objects of a multiple selection - * to the bottom of the stack of drawn objects - * @param {fabric.Object} object Object to send to back - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendToBack: function (object) { - if (!object) { - return this; - } - var activeGroup = this._activeGroup, - i, obj, objs; - if (object === activeGroup) { - objs = activeGroup._objects; - for (i = objs.length; i--;) { - obj = objs[i]; - removeFromArray(this._objects, obj); - this._objects.unshift(obj); - } - } - else { - removeFromArray(this._objects, object); - this._objects.unshift(object); - } - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object or the objects of a multiple selection - * to the top of the stack of drawn objects - * @param {fabric.Object} object Object to send - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringToFront: function (object) { - if (!object) { - return this; - } - var activeGroup = this._activeGroup, - i, obj, objs; - if (object === activeGroup) { - objs = activeGroup._objects; - for (i = 0; i < objs.length; i++) { - obj = objs[i]; - removeFromArray(this._objects, obj); - this._objects.push(obj); - } - } - else { - removeFromArray(this._objects, object); - this._objects.push(object); - } - return this.renderAll && this.renderAll(); - }, - - /** - * Moves an object or a selection down in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - sendBackwards: function (object, intersecting) { - if (!object) { - return this; - } - var activeGroup = this._activeGroup, - i, obj, idx, newIdx, objs; - - if (object === activeGroup) { - objs = activeGroup._objects; - for (i = 0; i < objs.length; i++) { - obj = objs[i]; - idx = this._objects.indexOf(obj); - if (idx !== 0) { - newIdx = idx - 1; - removeFromArray(this._objects, obj); - this._objects.splice(newIdx, 0, obj); - } - } - } - else { - idx = this._objects.indexOf(object); - if (idx !== 0) { - // if object is not on the bottom of stack - newIdx = this._findNewLowerIndex(object, idx, intersecting); - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - } - } - this.renderAll && this.renderAll(); - return this; - }, - - /** - * @private - */ - _findNewLowerIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse down the stack looking for the nearest intersecting object - for (var i = idx - 1; i >= 0; --i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx - 1; - } - - return newIdx; - }, - - /** - * Moves an object or a selection up in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Canvas} thisArg - * @chainable - */ - bringForward: function (object, intersecting) { - if (!object) { - return this; - } - var activeGroup = this._activeGroup, - i, obj, idx, newIdx, objs; - - if (object === activeGroup) { - objs = activeGroup._objects; - for (i = objs.length; i--;) { - obj = objs[i]; - idx = this._objects.indexOf(obj); - if (idx !== this._objects.length - 1) { - newIdx = idx + 1; - removeFromArray(this._objects, obj); - this._objects.splice(newIdx, 0, obj); - } - } - } - else { - idx = this._objects.indexOf(object); - if (idx !== this._objects.length - 1) { - // if object is not on top of stack (last item in an array) - newIdx = this._findNewUpperIndex(object, idx, intersecting); - removeFromArray(this._objects, object); - this._objects.splice(newIdx, 0, object); - } - } - this.renderAll && this.renderAll(); - return this; - }, - - /** - * @private - */ - _findNewUpperIndex: function(object, idx, intersecting) { - var newIdx; - - if (intersecting) { - newIdx = idx; - - // traverse up the stack looking for the nearest intersecting object - for (var i = idx + 1; i < this._objects.length; ++i) { - - var isIntersecting = object.intersectsWithObject(this._objects[i]) || - object.isContainedWithinObject(this._objects[i]) || - this._objects[i].isContainedWithinObject(object); - - if (isIntersecting) { - newIdx = i; - break; - } - } - } - else { - newIdx = idx + 1; - } - - return newIdx; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {fabric.Object} object Object to send - * @param {Number} index Position to move to - * @return {fabric.Canvas} thisArg - * @chainable - */ - moveTo: function (object, index) { - removeFromArray(this._objects, object); - this._objects.splice(index, 0, object); - return this.renderAll && this.renderAll(); - }, - - /** - * Clears a canvas element and removes all event listeners - * @return {fabric.Canvas} thisArg - * @chainable - */ - dispose: function () { - this.clear(); - return this; - }, - - /** - * Returns a string representation of an instance - * @return {String} string representation of an instance - */ - toString: function () { - return '#'; - } - }); - - extend(fabric.StaticCanvas.prototype, fabric.Observable); - extend(fabric.StaticCanvas.prototype, fabric.Collection); - extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); - - extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { - - /** - * @static - * @type String - * @default - */ - EMPTY_JSON: '{"objects": [], "background": "white"}', - - /** - * Provides a way to check support of some of the canvas methods - * (either those of HTMLCanvasElement itself, or rendering context) - * - * @param {String} methodName Method to check support for; - * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" - * @return {Boolean | null} `true` if method is supported (or at least exists), - * `null` if canvas element or context can not be initialized - */ - supports: function (methodName) { - var el = fabric.util.createCanvasElement(); - - if (!el || !el.getContext) { - return null; - } - - var ctx = el.getContext('2d'); - if (!ctx) { - return null; - } - - switch (methodName) { - - case 'getImageData': - return typeof ctx.getImageData !== 'undefined'; - - case 'setLineDash': - return typeof ctx.setLineDash !== 'undefined'; - - case 'toDataURL': - return typeof el.toDataURL !== 'undefined'; - - case 'toDataURLWithQuality': - try { - el.toDataURL('image/jpeg', 0); - return true; - } - catch (e) { } - return false; - - default: - return null; - } - } - }); - - /** - * Returns JSON representation of canvas - * @function - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {String} JSON string - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} - * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} - * @example JSON without additional properties - * var json = canvas.toJSON(); - * @example JSON with additional properties included - * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); - * @example JSON without default values - * canvas.includeDefaultValues = false; - * var json = canvas.toJSON(); - */ - fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; - -})(); - - -/** - * BaseBrush class - * @class fabric.BaseBrush - * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo} - */ -fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { - - /** - * Color of a brush - * @type String - * @default - */ - color: 'rgb(0, 0, 0)', - - /** - * Width of a brush - * @type Number - * @default - */ - width: 1, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), - * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Line endings style of a brush (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'round', - - /** - * Corner style of a brush (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'round', - - /** - * Stroke Dash Array. - * @type Array - * @default - */ - strokeDashArray: null, - - /** - * Sets shadow of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @return {fabric.Object} thisArg - * @chainable - */ - setShadow: function(options) { - this.shadow = new fabric.Shadow(options); - return this; - }, - - /** - * Sets brush styles - * @private - */ - _setBrushStyles: function() { - var ctx = this.canvas.contextTop; - - ctx.strokeStyle = this.color; - ctx.lineWidth = this.width; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) { - ctx.setLineDash(this.strokeDashArray); - } - }, - - /** - * Sets brush shadow styles - * @private - */ - _setShadow: function() { - if (!this.shadow) { - return; - } - - var ctx = this.canvas.contextTop; - - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur; - ctx.shadowOffsetX = this.shadow.offsetX; - ctx.shadowOffsetY = this.shadow.offsetY; - }, - - /** - * Removes brush shadow styles - * @private - */ - _resetShadow: function() { - var ctx = this.canvas.contextTop; - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - } -}); - - -(function() { - - /** - * PencilBrush class - * @class fabric.PencilBrush - * @extends fabric.BaseBrush - */ - fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.PencilBrush} Instance of a pencil brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this._points = []; - }, - - /** - * Inovoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this._prepareForDrawing(pointer); - // capture coordinates immediately - // this allows to draw dots (when movement never occurs) - this._captureDrawingPath(pointer); - this._render(); - }, - - /** - * Inovoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this._captureDrawingPath(pointer); - // redraw curve - // clear top canvas - this.canvas.clearContext(this.canvas.contextTop); - this._render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - this._finalizeAndAddPath(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _prepareForDrawing: function(pointer) { - - var p = new fabric.Point(pointer.x, pointer.y); - - this._reset(); - this._addPoint(p); - - this.canvas.contextTop.moveTo(p.x, p.y); - }, - - /** - * @private - * @param {fabric.Point} point Point to be added to points array - */ - _addPoint: function(point) { - this._points.push(point); - }, - - /** - * Clear points array and set contextTop canvas style. - * @private - */ - _reset: function() { - this._points.length = 0; - - this._setBrushStyles(); - this._setShadow(); - }, - - /** - * @private - * @param {Object} pointer Actual mouse position related to the canvas. - */ - _captureDrawingPath: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); - this._addPoint(pointerPoint); - }, - - /** - * Draw a smooth path on the topCanvas using quadraticCurveTo - * @private - */ - _render: function() { - var ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform, - p1 = this._points[0], - p2 = this._points[1]; - - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - ctx.beginPath(); - - //if we only have 2 points in the path and they are the same - //it means that the user only clicked the canvas without moving the mouse - //then we should be drawing a dot. A path isn't drawn between two identical dots - //that's why we set them apart a bit - if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; - } - ctx.moveTo(p1.x, p1.y); - - for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi + 1 & pi + 2 as the - // end point and p1 as our control point. - var midPoint = p1.midPointFrom(p2); - ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); - - p1 = this._points[i]; - p2 = this._points[i + 1]; - } - // Draw last line as a straight line while - // we wait for the next point to be able to calculate - // the bezier control point - ctx.lineTo(p1.x, p1.y); - ctx.stroke(); - ctx.restore(); - }, - - /** - * Converts points to SVG path - * @param {Array} points Array of points - * @return {String} SVG path - */ - convertPointsToSVGPath: function(points) { - var path = [], - p1 = new fabric.Point(points[0].x, points[0].y), - p2 = new fabric.Point(points[1].x, points[1].y); - - path.push('M ', points[0].x, ' ', points[0].y, ' '); - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = p1.midPointFrom(p2); - // p1 is our bezier control point - // midpoint is our endpoint - // start point is p(i-1) value. - path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); - p1 = new fabric.Point(points[i].x, points[i].y); - if ((i + 1) < points.length) { - p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); - } - } - path.push('L ', p1.x, ' ', p1.y, ' '); - return path; - }, - - /** - * Creates fabric.Path object to add on canvas - * @param {String} pathData Path data - * @return {fabric.Path} Path to add on canvas - */ - createPath: function(pathData) { - var path = new fabric.Path(pathData, { - fill: null, - stroke: this.color, - strokeWidth: this.width, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeDashArray: this.strokeDashArray, - originX: 'center', - originY: 'center' - }); - - if (this.shadow) { - this.shadow.affectStroke = true; - path.setShadow(this.shadow); - } - - return path; - }, - - /** - * On mouseup after drawing the path on contextTop canvas - * we use the points captured to create an new fabric path object - * and add it to the fabric canvas. - */ - _finalizeAndAddPath: function() { - var ctx = this.canvas.contextTop; - ctx.closePath(); - - var pathData = this.convertPointsToSVGPath(this._points).join(''); - if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { - // do not create 0 width/height paths, as they are - // rendered inconsistently across browsers - // Firefox 4, for example, renders a dot, - // whereas Chrome 10 renders nothing - this.canvas.renderAll(); - return; - } - - var path = this.createPath(pathData); - - this.canvas.add(path); - path.setCoords(); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderAll(); - - // fire event 'path' created - this.canvas.fire('path:created', { path: path }); - } - }); -})(); - - -/** - * CircleBrush class - * @class fabric.CircleBrush - */ -fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { - - /** - * Width of a brush - * @type Number - * @default - */ - width: 10, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.CircleBrush} Instance of a circle brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.points = []; - }, - - /** - * Invoked inside on mouse down and mouse move - * @param {Object} pointer - */ - drawDot: function(pointer) { - var point = this.addPoint(pointer), - ctx = this.canvas.contextTop, - v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - ctx.fillStyle = point.fill; - ctx.beginPath(); - ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - }, - - /** - * Invoked on mouse down - */ - onMouseDown: function(pointer) { - this.points.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - this.drawDot(pointer); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.drawDot(pointer); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var circles = []; - - for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i], - circle = new fabric.Circle({ - radius: point.radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: point.fill - }); - - this.shadow && circle.setShadow(this.shadow); - - circles.push(circle); - } - var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @param {Object} pointer - * @return {fabric.Point} Just added pointer point - */ - addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y), - - circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2, - - circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); - - pointerPoint.radius = circleRadius; - pointerPoint.fill = circleColor; - - this.points.push(pointerPoint); - - return pointerPoint; - } -}); - - -/** - * SprayBrush class - * @class fabric.SprayBrush - */ -fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { - - /** - * Width of a spray - * @type Number - * @default - */ - width: 10, - - /** - * Density of a spray (number of dots per chunk) - * @type Number - * @default - */ - density: 20, - - /** - * Width of spray dots - * @type Number - * @default - */ - dotWidth: 1, - - /** - * Width variance of spray dots - * @type Number - * @default - */ - dotWidthVariance: 1, - - /** - * Whether opacity of a dot should be random - * @type Boolean - * @default - */ - randomOpacity: false, - - /** - * Whether overlapping dots (rectangles) should be removed (for performance reasons) - * @type Boolean - * @default - */ - optimizeOverlapping: true, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.SprayBrush} Instance of a spray brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.sprayChunks = []; - }, - - /** - * Invoked on mouse down - * @param {Object} pointer - */ - onMouseDown: function(pointer) { - this.sprayChunks.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.addSprayChunk(pointer); - this.render(); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var rects = []; - - for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - var sprayChunk = this.sprayChunks[i]; - - for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { - - var rect = new fabric.Rect({ - width: sprayChunk[j].width, - height: sprayChunk[j].width, - left: sprayChunk[j].x + 1, - top: sprayChunk[j].y + 1, - originX: 'center', - originY: 'center', - fill: this.color - }); - - this.shadow && rect.setShadow(this.shadow); - rects.push(rect); - } - } - - if (this.optimizeOverlapping) { - rects = this._getOptimizedRects(rects); - } - - var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @private - * @param {Array} rects - */ - _getOptimizedRects: function(rects) { - - // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key; - - for (var i = 0, len = rects.length; i < len; i++) { - key = rects[i].left + '' + rects[i].top; - if (!uniqueRects[key]) { - uniqueRects[key] = rects[i]; - } - } - var uniqueRectsArray = []; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - - /** - * Renders brush - */ - render: function() { - var ctx = this.canvas.contextTop; - ctx.fillStyle = this.color; - - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { - var point = this.sprayChunkPoints[i]; - if (typeof point.opacity !== 'undefined') { - ctx.globalAlpha = point.opacity; - } - ctx.fillRect(point.x, point.y, point.width, point.width); - } - ctx.restore(); - }, - - /** - * @param {Object} pointer - */ - addSprayChunk: function(pointer) { - this.sprayChunkPoints = []; - - var x, y, width, radius = this.width / 2; - - for (var i = 0; i < this.density; i++) { - - x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); - y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); - - if (this.dotWidthVariance) { - width = fabric.util.getRandomInt( - // bottom clamp width to 1 - Math.max(1, this.dotWidth - this.dotWidthVariance), - this.dotWidth + this.dotWidthVariance); - } - else { - width = this.dotWidth; - } - - var point = new fabric.Point(x, y); - point.width = width; - - if (this.randomOpacity) { - point.opacity = fabric.util.getRandomInt(0, 100) / 100; - } - - this.sprayChunkPoints.push(point); - } - - this.sprayChunks.push(this.sprayChunkPoints); - } -}); - - -/** - * PatternBrush class - * @class fabric.PatternBrush - * @extends fabric.BaseBrush - */ -fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { - - getPatternSrc: function() { - - var dotWidth = 20, - dotDistance = 5, - patternCanvas = fabric.document.createElement('canvas'), - patternCtx = patternCanvas.getContext('2d'); - - patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; - - patternCtx.fillStyle = this.color; - patternCtx.beginPath(); - patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); - patternCtx.closePath(); - patternCtx.fill(); - - return patternCanvas; - }, - - getPatternSrcFunction: function() { - return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); - }, - - /** - * Creates "pattern" instance property - */ - getPattern: function() { - return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); - }, - - /** - * Sets brush styles - */ - _setBrushStyles: function() { - this.callSuper('_setBrushStyles'); - this.canvas.contextTop.strokeStyle = this.getPattern(); - }, - - /** - * Creates path - */ - createPath: function(pathData) { - var path = this.callSuper('createPath', pathData), - topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); - - path.stroke = new fabric.Pattern({ - source: this.source || this.getPatternSrcFunction(), - offsetX: -topLeft.x, - offsetY: -topLeft.y - }); - return path; - } -}); - - -(function() { - - var getPointer = fabric.util.getPointer, - degreesToRadians = fabric.util.degreesToRadians, - radiansToDegrees = fabric.util.radiansToDegrees, - atan2 = Math.atan2, - abs = Math.abs, - supportLineDash = fabric.StaticCanvas.supports('setLineDash'), - - STROKE_OFFSET = 0.5; - - /** - * Canvas class - * @class fabric.Canvas - * @extends fabric.StaticCanvas - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas} - * @see {@link fabric.Canvas#initialize} for constructor definition - * - * @fires object:added - * @fires object:modified - * @fires object:rotating - * @fires object:scaling - * @fires object:moving - * @fires object:selected - * - * @fires before:selection:cleared - * @fires selection:cleared - * @fires selection:created - * - * @fires path:created - * @fires mouse:down - * @fires mouse:move - * @fires mouse:up - * @fires mouse:over - * @fires mouse:out - * - */ - fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { - - /** - * Constructor - * @param {HTMLElement | String} el <canvas> element to initialize instance on - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(el, options) { - options || (options = { }); - - this._initStatic(el, options); - this._initInteractive(); - this._createCacheCanvas(); - }, - - /** - * When true, objects can be transformed by one side (unproportionally) - * @type Boolean - * @default - */ - uniScaleTransform: false, - - /** - * Indicates which key enable unproportional scaling - * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. - * @since 1.6.2 - * @type String - * @default - */ - uniScaleKey: 'shiftKey', - - /** - * When true, objects use center point as the origin of scale transformation. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, objects use center point as the origin of rotate transformation. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: false, - - /** - * Indicates which key enable centered Transfrom - * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. - * @since 1.6.2 - * @type String - * @default - */ - centeredKey: 'altKey', - - /** - * Indicates which key enable alternate action on corner - * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. - * @since 1.6.2 - * @type String - * @default - */ - altActionKey: 'shiftKey', - - /** - * Indicates that canvas is interactive. This property should not be changed. - * @type Boolean - * @default - */ - interactive: true, - - /** - * Indicates whether group selection should be enabled - * @type Boolean - * @default - */ - selection: true, - - /** - * Indicates which key enable multiple click selection - * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. - * @since 1.6.2 - * @type String - * @default - */ - selectionKey: 'shiftKey', - - /** - * Indicates which key enable alternative selection - * in case of target overlapping with active object - * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. - * @since 1.6.5 - * @type null|String - * @default - */ - altSelectionKey: null, - - /** - * Color of selection - * @type String - * @default - */ - selectionColor: 'rgba(100, 100, 255, 0.3)', // blue - - /** - * Default dash array pattern - * If not empty the selection border is dashed - * @type Array - */ - selectionDashArray: [], - - /** - * Color of the border of selection (usually slightly darker than color of selection itself) - * @type String - * @default - */ - selectionBorderColor: 'rgba(255, 255, 255, 0.3)', - - /** - * Width of a line used in object/group selection - * @type Number - * @default - */ - selectionLineWidth: 1, - - /** - * Default cursor value used when hovering over an object on canvas - * @type String - * @default - */ - hoverCursor: 'move', - - /** - * Default cursor value used when moving an object on canvas - * @type String - * @default - */ - moveCursor: 'move', - - /** - * Default cursor value used for the entire canvas - * @type String - * @default - */ - defaultCursor: 'default', - - /** - * Cursor value used during free drawing - * @type String - * @default - */ - freeDrawingCursor: 'crosshair', - - /** - * Cursor value used for rotation point - * @type String - * @default - */ - rotationCursor: 'crosshair', - - /** - * Default element class that's given to wrapper (div) element of canvas - * @type String - * @default - */ - containerClass: 'canvas-container', - - /** - * When true, object detection happens on per-pixel basis rather than on per-bounding-box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * Number of pixels around target pixel to tolerate (consider active) during object detection - * @type Number - * @default - */ - targetFindTolerance: 0, - - /** - * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. - * @type Boolean - * @default - */ - skipTargetFind: false, - - /** - * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. - * After mousedown, mousemove creates a shape, - * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. - * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing} - * @type Boolean - * @default - */ - isDrawingMode: false, - - /** - * Indicates whether objects should remain in current stack position when selected. - * When false objects are brought to top and rendered as part of the selection group - * @type Boolean - * @default - */ - preserveObjectStacking: false, - - /** - * Indicates the angle that an object will lock to while rotating. - * @type Number - * @since 1.6.7 - * @default - */ - snapAngle: 0, - - /** - * Indicates the distance from the snapAngle the rotation will lock to the snapAngle. - * When `null`, the snapThreshold will default to the snapAngle. - * @type null|Number - * @since 1.6.7 - * @default - */ - snapThreshold: null, - - /** - * Indicates if the right click on canvas can output the context menu or not - * @type Boolean - * @since 1.6.5 - * @default - */ - stopContextMenu: false, - - /** - * Indicates if the canvas can fire right click events - * @type Boolean - * @since 1.6.5 - * @default - */ - fireRightClick: false, - - /** - * @private - */ - _initInteractive: function() { - this._currentTransform = null; - this._groupSelector = null; - this._initWrapperElement(); - this._createUpperCanvas(); - this._initEventListeners(); - - this._initRetinaScaling(); - - this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); - - this.calcOffset(); - }, - - /** - * Divides objects in two groups, one to render immediately - * and one to render as activeGroup. - * @return {Array} objects to render immediately and pushes the other in the activeGroup. - */ - _chooseObjectsToRender: function() { - var activeGroup = this.getActiveGroup(), - activeObject = this.getActiveObject(), - object, objsToRender = [], activeGroupObjects = []; - - if ((activeGroup || activeObject) && !this.preserveObjectStacking) { - for (var i = 0, length = this._objects.length; i < length; i++) { - object = this._objects[i]; - if ((!activeGroup || !activeGroup.contains(object)) && object !== activeObject) { - objsToRender.push(object); - } - else { - activeGroupObjects.push(object); - } - } - if (activeGroup) { - activeGroup._set('_objects', activeGroupObjects); - objsToRender.push(activeGroup); - } - activeObject && objsToRender.push(activeObject); - } - else { - objsToRender = this._objects; - } - return objsToRender; - }, - - /** - * Renders both the top canvas and the secondary container canvas. - * @return {fabric.Canvas} instance - * @chainable - */ - renderAll: function () { - if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { - this.clearContext(this.contextTop); - this.contextTopDirty = false; - } - var canvasToDrawOn = this.contextContainer; - this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); - return this; - }, - - /** - * Method to render only the top canvas. - * Also used to render the group selection box. - * @return {fabric.Canvas} thisArg - * @chainable - */ - renderTop: function () { - var ctx = this.contextTop; - this.clearContext(ctx); - - // we render the top context - last object - if (this.selection && this._groupSelector) { - this._drawSelection(ctx); - } - - this.fire('after:render'); - this.contextTopDirty = true; - return this; - }, - - /** - * Resets the current transform to its original values and chooses the type of resizing based on the event - * @private - */ - _resetCurrentTransform: function() { - var t = this._currentTransform; - - t.target.set({ - scaleX: t.original.scaleX, - scaleY: t.original.scaleY, - skewX: t.original.skewX, - skewY: t.original.skewY, - left: t.original.left, - top: t.original.top - }); - - if (this._shouldCenterTransform(t.target)) { - if (t.action === 'rotate') { - this._setOriginToCenter(t.target); - } - else { - if (t.originX !== 'center') { - if (t.originX === 'right') { - t.mouseXSign = -1; - } - else { - t.mouseXSign = 1; - } - } - if (t.originY !== 'center') { - if (t.originY === 'bottom') { - t.mouseYSign = -1; - } - else { - t.mouseYSign = 1; - } - } - - t.originX = 'center'; - t.originY = 'center'; - } - } - else { - t.originX = t.original.originX; - t.originY = t.original.originY; - } - }, - - /** - * Checks if point is contained within an area of given object - * @param {Event} e Event object - * @param {fabric.Object} target Object to test against - * @param {Object} [point] x,y object of point coordinates we want to check. - * @return {Boolean} true if point is contained within an area of given object - */ - containsPoint: function (e, target, point) { - var ignoreZoom = true, - pointer = point || this.getPointer(e, ignoreZoom), - xy; - - if (target.group && target.group === this.getActiveGroup()) { - xy = this._normalizePointer(target.group, pointer); - } - else { - xy = { x: pointer.x, y: pointer.y }; - } - // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html - // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html - return (target.containsPoint(xy) || target._findTargetCorner(pointer)); - }, - - /** - * @private - */ - _normalizePointer: function (object, pointer) { - var m = object.calcTransformMatrix(), - invertedM = fabric.util.invertTransform(m), - vpt = this.viewportTransform, - vptPointer = this.restorePointerVpt(pointer), - p = fabric.util.transformPoint(vptPointer, invertedM); - return fabric.util.transformPoint(p, vpt); - }, - - /** - * Returns true if object is transparent at a certain location - * @param {fabric.Object} target Object to check - * @param {Number} x Left coordinate - * @param {Number} y Top coordinate - * @return {Boolean} - */ - isTargetTransparent: function (target, x, y) { - var hasBorders = target.hasBorders, - transparentCorners = target.transparentCorners, - ctx = this.contextCache, - originalColor = target.selectionBackgroundColor; - - target.hasBorders = target.transparentCorners = false; - target.selectionBackgroundColor = ''; - - ctx.save(); - ctx.transform.apply(ctx, this.viewportTransform); - target.render(ctx); - ctx.restore(); - - target.active && target._renderControls(ctx); - - target.hasBorders = hasBorders; - target.transparentCorners = transparentCorners; - target.selectionBackgroundColor = originalColor; - - var isTransparent = fabric.util.isTransparent( - ctx, x, y, this.targetFindTolerance); - - this.clearContext(ctx); - - return isTransparent; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _shouldClearSelection: function (e, target) { - var activeGroup = this.getActiveGroup(), - activeObject = this.getActiveObject(); - - return ( - !target - || - (target && - activeGroup && - !activeGroup.contains(target) && - activeGroup !== target && - !e[this.selectionKey]) - || - (target && !target.evented) - || - (target && - !target.selectable && - activeObject && - activeObject !== target) - ); - }, - - /** - * @private - * @param {fabric.Object} target - */ - _shouldCenterTransform: function (target) { - if (!target) { - return; - } - - var t = this._currentTransform, - centerTransform; - - if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { - centerTransform = this.centeredScaling || target.centeredScaling; - } - else if (t.action === 'rotate') { - centerTransform = this.centeredRotation || target.centeredRotation; - } - - return centerTransform ? !t.altKey : t.altKey; - }, - - /** - * @private - */ - _getOriginFromCorner: function(target, corner) { - var origin = { - x: target.originX, - y: target.originY - }; - - if (corner === 'ml' || corner === 'tl' || corner === 'bl') { - origin.x = 'right'; - } - else if (corner === 'mr' || corner === 'tr' || corner === 'br') { - origin.x = 'left'; - } - - if (corner === 'tl' || corner === 'mt' || corner === 'tr') { - origin.y = 'bottom'; - } - else if (corner === 'bl' || corner === 'mb' || corner === 'br') { - origin.y = 'top'; - } - - return origin; - }, - - /** - * @private - */ - _getActionFromCorner: function(target, corner, e) { - if (!corner) { - return 'drag'; - } - - switch (corner) { - case 'mtr': - return 'rotate'; - case 'ml': - case 'mr': - return e[this.altActionKey] ? 'skewY' : 'scaleX'; - case 'mt': - case 'mb': - return e[this.altActionKey] ? 'skewX' : 'scaleY'; - default: - return 'scale'; - } - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _setupCurrentTransform: function (e, target) { - if (!target) { - return; - } - - var pointer = this.getPointer(e), - corner = target._findTargetCorner(this.getPointer(e, true)), - action = this._getActionFromCorner(target, corner, e), - origin = this._getOriginFromCorner(target, corner); - - this._currentTransform = { - target: target, - action: action, - corner: corner, - scaleX: target.scaleX, - scaleY: target.scaleY, - skewX: target.skewX, - skewY: target.skewY, - offsetX: pointer.x - target.left, - offsetY: pointer.y - target.top, - originX: origin.x, - originY: origin.y, - ex: pointer.x, - ey: pointer.y, - lastX: pointer.x, - lastY: pointer.y, - left: target.left, - top: target.top, - theta: degreesToRadians(target.angle), - width: target.width * target.scaleX, - mouseXSign: 1, - mouseYSign: 1, - shiftKey: e.shiftKey, - altKey: e[this.centeredKey] - }; - - this._currentTransform.original = { - left: target.left, - top: target.top, - scaleX: target.scaleX, - scaleY: target.scaleY, - skewX: target.skewX, - skewY: target.skewY, - originX: origin.x, - originY: origin.y - }; - - this._resetCurrentTransform(); - }, - - /** - * Translates object by "setting" its left/top - * @private - * @param {Number} x pointer's x coordinate - * @param {Number} y pointer's y coordinate - * @return {Boolean} true if the translation occurred - */ - _translateObject: function (x, y) { - var transform = this._currentTransform, - target = transform.target, - newLeft = x - transform.offsetX, - newTop = y - transform.offsetY, - moveX = !target.get('lockMovementX') && target.left !== newLeft, - moveY = !target.get('lockMovementY') && target.top !== newTop; - - moveX && target.set('left', newLeft); - moveY && target.set('top', newTop); - return moveX || moveY; - }, - - /** - * Check if we are increasing a positive skew or lower it, - * checking mouse direction and pressed corner. - * @private - */ - _changeSkewTransformOrigin: function(mouseMove, t, by) { - var property = 'originX', origins = { 0: 'center' }, - skew = t.target.skewX, originA = 'left', originB = 'right', - corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1, - flipSign = 1; - - mouseMove = mouseMove > 0 ? 1 : -1; - if (by === 'y') { - skew = t.target.skewY; - originA = 'top'; - originB = 'bottom'; - property = 'originY'; - } - origins[-1] = originA; - origins[1] = originB; - - t.target.flipX && (flipSign *= -1); - t.target.flipY && (flipSign *= -1); - - if (skew === 0) { - t.skewSign = -corner * mouseMove * flipSign; - t[property] = origins[-mouseMove]; - } - else { - skew = skew > 0 ? 1 : -1; - t.skewSign = skew; - t[property] = origins[skew * corner * flipSign]; - } - }, - - /** - * Skew object by mouse events - * @private - * @param {Number} x pointer's x coordinate - * @param {Number} y pointer's y coordinate - * @param {String} by Either 'x' or 'y' - * @return {Boolean} true if the skewing occurred - */ - _skewObject: function (x, y, by) { - var t = this._currentTransform, - target = t.target, skewed = false, - lockSkewingX = target.get('lockSkewingX'), - lockSkewingY = target.get('lockSkewingY'); - - if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) { - return false; - } - - // Get the constraint point - var center = target.getCenterPoint(), - actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by], - lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by], - actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions(); - - this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by); - actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by]; - constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY); - // Actually skew the object - skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim); - t.lastX = x; - t.lastY = y; - // Make sure the constraints apply - target.setPositionByOrigin(constraintPosition, t.originX, t.originY); - return skewed; - }, - - /** - * Set object skew - * @private - * @return {Boolean} true if the skewing occurred - */ - _setObjectSkew: function(localMouse, transform, by, _dim) { - var target = transform.target, newValue, skewed = false, - skewSign = transform.skewSign, newDim, dimNoSkew, - otherBy, _otherBy, _by, newDimMouse, skewX, skewY; - - if (by === 'x') { - otherBy = 'y'; - _otherBy = 'Y'; - _by = 'X'; - skewX = 0; - skewY = target.skewY; - } - else { - otherBy = 'x'; - _otherBy = 'X'; - _by = 'Y'; - skewX = target.skewX; - skewY = 0; - } - - dimNoSkew = target._getTransformedDimensions(skewX, skewY); - newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by]; - if (newDimMouse <= 2) { - newValue = 0; - } - else { - newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) / - (dimNoSkew[otherBy] / target['scale' + _otherBy])); - newValue = fabric.util.radiansToDegrees(newValue); - } - skewed = target['skew' + _by] !== newValue; - target.set('skew' + _by, newValue); - if (target['skew' + _otherBy] !== 0) { - newDim = target._getTransformedDimensions(); - newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy]; - target.set('scale' + _otherBy, newValue); - } - return skewed; - }, - - /** - * Scales object by invoking its scaleX/scaleY methods - * @private - * @param {Number} x pointer's x coordinate - * @param {Number} y pointer's y coordinate - * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object. - * When not provided, an object is scaled by both dimensions equally - * @return {Boolean} true if the scaling occurred - */ - _scaleObject: function (x, y, by) { - var t = this._currentTransform, - target = t.target, - lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'), - lockScalingFlip = target.get('lockScalingFlip'); - - if (lockScalingX && lockScalingY) { - return false; - } - - // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), - localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY), - dim = target._getTransformedDimensions(), scaled = false; - - this._setLocalMouse(localMouse, t); - - // Actually scale the object - scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim); - - // Make sure the constraints apply - target.setPositionByOrigin(constraintPosition, t.originX, t.originY); - return scaled; - }, - - /** - * @private - * @return {Boolean} true if the scaling occurred - */ - _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { - var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false, - changeX, changeY, scaleX, scaleY; - - scaleX = localMouse.x * target.scaleX / _dim.x; - scaleY = localMouse.y * target.scaleY / _dim.y; - changeX = target.scaleX !== scaleX; - changeY = target.scaleY !== scaleY; - - if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) { - forbidScalingX = true; - } - - if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) { - forbidScalingY = true; - } - - if (by === 'equally' && !lockScalingX && !lockScalingY) { - forbidScalingX || forbidScalingY || (scaled = this._scaleObjectEqually(localMouse, target, transform, _dim)); - } - else if (!by) { - forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX)); - forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY)); - } - else if (by === 'x' && !target.get('lockUniScaling')) { - forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX)); - } - else if (by === 'y' && !target.get('lockUniScaling')) { - forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY)); - } - transform.newScaleX = scaleX; - transform.newScaleY = scaleY; - forbidScalingX || forbidScalingY || this._flipObject(transform, by); - return scaled; - }, - - /** - * @private - * @return {Boolean} true if the scaling occurred - */ - _scaleObjectEqually: function(localMouse, target, transform, _dim) { - - var dist = localMouse.y + localMouse.x, - lastDist = _dim.y * transform.original.scaleY / target.scaleY + - _dim.x * transform.original.scaleX / target.scaleX, - scaled; - - // We use transform.scaleX/Y instead of target.scaleX/Y - // because the object may have a min scale and we'll loose the proportions - transform.newScaleX = transform.original.scaleX * dist / lastDist; - transform.newScaleY = transform.original.scaleY * dist / lastDist; - scaled = transform.newScaleX !== target.scaleX || transform.newScaleY !== target.scaleY; - target.set('scaleX', transform.newScaleX); - target.set('scaleY', transform.newScaleY); - return scaled; - }, - - /** - * @private - */ - _flipObject: function(transform, by) { - if (transform.newScaleX < 0 && by !== 'y') { - if (transform.originX === 'left') { - transform.originX = 'right'; - } - else if (transform.originX === 'right') { - transform.originX = 'left'; - } - } - - if (transform.newScaleY < 0 && by !== 'x') { - if (transform.originY === 'top') { - transform.originY = 'bottom'; - } - else if (transform.originY === 'bottom') { - transform.originY = 'top'; - } - } - }, - - /** - * @private - */ - _setLocalMouse: function(localMouse, t) { - var target = t.target; - - if (t.originX === 'right') { - localMouse.x *= -1; - } - else if (t.originX === 'center') { - localMouse.x *= t.mouseXSign * 2; - if (localMouse.x < 0) { - t.mouseXSign = -t.mouseXSign; - } - } - - if (t.originY === 'bottom') { - localMouse.y *= -1; - } - else if (t.originY === 'center') { - localMouse.y *= t.mouseYSign * 2; - if (localMouse.y < 0) { - t.mouseYSign = -t.mouseYSign; - } - } - - // adjust the mouse coordinates when dealing with padding - if (abs(localMouse.x) > target.padding) { - if (localMouse.x < 0) { - localMouse.x += target.padding; - } - else { - localMouse.x -= target.padding; - } - } - else { // mouse is within the padding, set to 0 - localMouse.x = 0; - } - - if (abs(localMouse.y) > target.padding) { - if (localMouse.y < 0) { - localMouse.y += target.padding; - } - else { - localMouse.y -= target.padding; - } - } - else { - localMouse.y = 0; - } - }, - - /** - * Rotates object by invoking its rotate method - * @private - * @param {Number} x pointer's x coordinate - * @param {Number} y pointer's y coordinate - * @return {Boolean} true if the rotation occurred - */ - _rotateObject: function (x, y) { - - var t = this._currentTransform; - - if (t.target.get('lockRotation')) { - return false; - } - - var lastAngle = atan2(t.ey - t.top, t.ex - t.left), - curAngle = atan2(y - t.top, x - t.left), - angle = radiansToDegrees(curAngle - lastAngle + t.theta), - hasRoated = true; - - // normalize angle to positive value - if (angle < 0) { - angle = 360 + angle; - } - - angle %= 360 - - if (t.target.snapAngle > 0) { - var snapAngle = t.target.snapAngle, - snapThreshold = t.target.snapThreshold || snapAngle, - rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, - leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; - - if (Math.abs(angle - leftAngleLocked) < snapThreshold) { - angle = leftAngleLocked; - } - else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { - angle = rightAngleLocked; - } - - if (t.target.angle === angle) { - hasRoated = false - } - } - - t.target.angle = angle; - return hasRoated; - }, - - /** - * Set the cursor type of the canvas element - * @param {String} value Cursor type of the canvas element. - * @see http://www.w3.org/TR/css3-ui/#cursor - */ - setCursor: function (value) { - this.upperCanvasEl.style.cursor = value; - }, - - /** - * @param {fabric.Object} target to reset transform - * @private - */ - _resetObjectTransform: function (target) { - target.scaleX = 1; - target.scaleY = 1; - target.skewX = 0; - target.skewY = 0; - target.setAngle(0); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx to draw the selection on - */ - _drawSelection: function (ctx) { - var groupSelector = this._groupSelector, - left = groupSelector.left, - top = groupSelector.top, - aleft = abs(left), - atop = abs(top); - - if (this.selectionColor) { - ctx.fillStyle = this.selectionColor; - - ctx.fillRect( - groupSelector.ex - ((left > 0) ? 0 : -left), - groupSelector.ey - ((top > 0) ? 0 : -top), - aleft, - atop - ); - } - - if (!this.selectionLineWidth || !this.selectionBorderColor) { - return; - } - ctx.lineWidth = this.selectionLineWidth; - ctx.strokeStyle = this.selectionBorderColor; - - // selection border - if (this.selectionDashArray.length > 1 && !supportLineDash) { - - var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), - py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop); - - ctx.beginPath(); - - fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); - - ctx.closePath(); - ctx.stroke(); - } - else { - fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray); - ctx.strokeRect( - groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), - groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), - aleft, - atop - ); - } - }, - - /** - * Method that determines what object we are clicking on - * @param {Event} e mouse event - * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through - */ - findTarget: function (e, skipGroup) { - if (this.skipTargetFind) { - return; - } - - var ignoreZoom = true, - pointer = this.getPointer(e, ignoreZoom), - activeGroup = this.getActiveGroup(), - activeObject = this.getActiveObject(), - activeTarget; - - // first check current group (if one exists) - // active group does not check sub targets like normal groups. - // if active group just exits. - if (activeGroup && !skipGroup && this._checkTarget(pointer, activeGroup)) { - this._fireOverOutEvents(activeGroup, e); - return activeGroup; - } - // if we hit the corner of an activeObject, let's return that. - if (activeObject && activeObject._findTargetCorner(pointer)) { - this._fireOverOutEvents(activeObject, e); - return activeObject; - } - if (activeObject && this._checkTarget(pointer, activeObject)) { - if (!this.preserveObjectStacking) { - this._fireOverOutEvents(activeObject, e); - return activeObject; - } - else { - activeTarget = activeObject; - } - } - - this.targets = []; - - var target = this._searchPossibleTargets(this._objects, pointer); - if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) { - target = activeTarget; - } - this._fireOverOutEvents(target, e); - return target; - }, - - /** - * @private - */ - _fireOverOutEvents: function(target, e) { - if (target) { - if (this._hoveredTarget !== target) { - if (this._hoveredTarget) { - this.fire('mouse:out', { target: this._hoveredTarget, e: e }); - this._hoveredTarget.fire('mouseout'); - } - this.fire('mouse:over', { target: target, e: e }); - target.fire('mouseover'); - this._hoveredTarget = target; - } - } - else if (this._hoveredTarget) { - this.fire('mouse:out', { target: this._hoveredTarget, e: e }); - this._hoveredTarget.fire('mouseout'); - this._hoveredTarget = null; - } - }, - - /** - * @private - */ - _checkTarget: function(pointer, obj) { - if (obj && - obj.visible && - obj.evented && - this.containsPoint(null, obj, pointer)){ - if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { - var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); - if (!isTransparent) { - return true; - } - } - else { - return true; - } - } - }, - - /** - * @private - */ - _searchPossibleTargets: function(objects, pointer) { - - // Cache all targets where their bounding box contains point. - var target, i = objects.length, normalizedPointer, subTarget; - // Do not check for currently grouped objects, since we check the parent group itself. - // untill we call this function specifically to search inside the activeGroup - while (i--) { - if (this._checkTarget(pointer, objects[i])) { - target = objects[i]; - if (target.type === 'group' && target.subTargetCheck) { - normalizedPointer = this._normalizePointer(target, pointer); - subTarget = this._searchPossibleTargets(target._objects, normalizedPointer); - subTarget && this.targets.push(subTarget); - } - break; - } - } - return target; - }, - - /** - * Returns pointer coordinates without the effect of the viewport - * @param {Object} pointer with "x" and "y" number values - * @return {Object} object with "x" and "y" number values - */ - restorePointerVpt: function(pointer) { - return fabric.util.transformPoint( - pointer, - fabric.util.invertTransform(this.viewportTransform) - ); - }, - - /** - * Returns pointer coordinates relative to canvas. - * @param {Event} e - * @param {Boolean} ignoreZoom - * @return {Object} object with "x" and "y" number values - */ - getPointer: function (e, ignoreZoom, upperCanvasEl) { - if (!upperCanvasEl) { - upperCanvasEl = this.upperCanvasEl; - } - var pointer = getPointer(e), - bounds = upperCanvasEl.getBoundingClientRect(), - boundsWidth = bounds.width || 0, - boundsHeight = bounds.height || 0, - cssScale; - - if (!boundsWidth || !boundsHeight ) { - if ('top' in bounds && 'bottom' in bounds) { - boundsHeight = Math.abs( bounds.top - bounds.bottom ); - } - if ('right' in bounds && 'left' in bounds) { - boundsWidth = Math.abs( bounds.right - bounds.left ); - } - } - - this.calcOffset(); - - pointer.x = pointer.x - this._offset.left; - pointer.y = pointer.y - this._offset.top; - if (!ignoreZoom) { - pointer = this.restorePointerVpt(pointer); - } - - if (boundsWidth === 0 || boundsHeight === 0) { - // If bounds are not available (i.e. not visible), do not apply scale. - cssScale = { width: 1, height: 1 }; - } - else { - cssScale = { - width: upperCanvasEl.width / boundsWidth, - height: upperCanvasEl.height / boundsHeight - }; - } - - return { - x: pointer.x * cssScale.width, - y: pointer.y * cssScale.height - }; - }, - - /** - * @private - * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized - */ - _createUpperCanvas: function () { - var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); - - this.upperCanvasEl = this._createCanvasElement(); - fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); - - this.wrapperEl.appendChild(this.upperCanvasEl); - - this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); - this._applyCanvasStyle(this.upperCanvasEl); - this.contextTop = this.upperCanvasEl.getContext('2d'); - }, - - /** - * @private - */ - _createCacheCanvas: function () { - this.cacheCanvasEl = this._createCanvasElement(); - this.cacheCanvasEl.setAttribute('width', this.width); - this.cacheCanvasEl.setAttribute('height', this.height); - this.contextCache = this.cacheCanvasEl.getContext('2d'); - }, - - /** - * @private - */ - _initWrapperElement: function () { - this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { - 'class': this.containerClass - }); - fabric.util.setStyle(this.wrapperEl, { - width: this.getWidth() + 'px', - height: this.getHeight() + 'px', - position: 'relative' - }); - fabric.util.makeElementUnselectable(this.wrapperEl); - }, - - /** - * @private - * @param {HTMLElement} element canvas element to apply styles on - */ - _applyCanvasStyle: function (element) { - var width = this.getWidth() || element.width, - height = this.getHeight() || element.height; - - fabric.util.setStyle(element, { - position: 'absolute', - width: width + 'px', - height: height + 'px', - left: 0, - top: 0 - }); - element.width = width; - element.height = height; - fabric.util.makeElementUnselectable(element); - }, - - /** - * Copys the the entire inline style from one element (fromEl) to another (toEl) - * @private - * @param {Element} fromEl Element style is copied from - * @param {Element} toEl Element copied style is applied to - */ - _copyCanvasStyle: function (fromEl, toEl) { - toEl.style.cssText = fromEl.style.cssText; - }, - - /** - * Returns context of canvas where object selection is drawn - * @return {CanvasRenderingContext2D} - */ - getSelectionContext: function() { - return this.contextTop; - }, - - /** - * Returns <canvas> element on which object selection is drawn - * @return {HTMLCanvasElement} - */ - getSelectionElement: function () { - return this.upperCanvasEl; - }, - - /** - * @private - * @param {Object} object - */ - _setActiveObject: function(object) { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = object; - object.set('active', true); - }, - - /** - * Sets given object as the only active object on canvas - * @param {fabric.Object} object Object to set as an active one - * @param {Event} [e] Event (passed along when firing "object:selected") - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveObject: function (object, e) { - this._setActiveObject(object); - this.renderAll(); - this.fire('object:selected', { target: object, e: e }); - object.fire('selected', { e: e }); - return this; - }, - - /** - * Returns currently active object - * @return {fabric.Object} active object - */ - getActiveObject: function () { - return this._activeObject; - }, - - /** - * @private - * @param {fabric.Object} obj Object that was removed - */ - _onObjectRemoved: function(obj) { - // removing active object should fire "selection:cleared" events - if (this.getActiveObject() === obj) { - this.fire('before:selection:cleared', { target: obj }); - this._discardActiveObject(); - this.fire('selection:cleared', { target: obj }); - obj.fire('deselected'); - } - this.callSuper('_onObjectRemoved', obj); - }, - - /** - * @private - */ - _discardActiveObject: function() { - if (this._activeObject) { - this._activeObject.set('active', false); - } - this._activeObject = null; - }, - - /** - * Discards currently active object and fire events - * @param {event} e - * @return {fabric.Canvas} thisArg - * @chainable - */ - discardActiveObject: function (e) { - var activeObject = this._activeObject; - this.fire('before:selection:cleared', { target: activeObject, e: e }); - this._discardActiveObject(); - this.fire('selection:cleared', { e: e }); - activeObject && activeObject.fire('deselected', { e: e }); - return this; - }, - - /** - * @private - * @param {fabric.Group} group - */ - _setActiveGroup: function(group) { - this._activeGroup = group; - if (group) { - group.set('active', true); - } - }, - - /** - * Sets active group to a specified one - * @param {fabric.Group} group Group to set as a current one - * @param {Event} e Event object - * @return {fabric.Canvas} thisArg - * @chainable - */ - setActiveGroup: function (group, e) { - this._setActiveGroup(group); - if (group) { - this.fire('object:selected', { target: group, e: e }); - group.fire('selected', { e: e }); - } - return this; - }, - - /** - * Returns currently active group - * @return {fabric.Group} Current group - */ - getActiveGroup: function () { - return this._activeGroup; - }, - - /** - * @private - */ - _discardActiveGroup: function() { - var g = this.getActiveGroup(); - if (g) { - g.destroy(); - } - this.setActiveGroup(null); - }, - - /** - * Discards currently active group and fire events - * @return {fabric.Canvas} thisArg - * @chainable - */ - discardActiveGroup: function (e) { - var g = this.getActiveGroup(); - this.fire('before:selection:cleared', { e: e, target: g }); - this._discardActiveGroup(); - this.fire('selection:cleared', { e: e }); - return this; - }, - - /** - * Deactivates all objects on canvas, removing any active group or object - * @return {fabric.Canvas} thisArg - * @chainable - */ - deactivateAll: function () { - var allObjects = this.getObjects(), - i = 0, - len = allObjects.length; - for ( ; i < len; i++) { - allObjects[i].set('active', false); - } - this._discardActiveGroup(); - this._discardActiveObject(); - return this; - }, - - /** - * Deactivates all objects and dispatches appropriate events - * @return {fabric.Canvas} thisArg - * @chainable - */ - deactivateAllWithDispatch: function (e) { - var activeGroup = this.getActiveGroup(), - activeObject = this.getActiveObject(); - if (activeObject || activeGroup) { - this.fire('before:selection:cleared', { target: activeObject || activeGroup, e: e }); - } - this.deactivateAll(); - if (activeObject || activeGroup) { - this.fire('selection:cleared', { e: e, target: activeObject }); - activeObject && activeObject.fire('deselected'); - } - return this; - }, - - /** - * Clears a canvas element and removes all event listeners - * @return {fabric.Canvas} thisArg - * @chainable - */ - dispose: function () { - this.callSuper('dispose'); - var wrapper = this.wrapperEl; - this.removeListeners(); - wrapper.removeChild(this.upperCanvasEl); - wrapper.removeChild(this.lowerCanvasEl); - delete this.upperCanvasEl; - if (wrapper.parentNode) { - wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); - } - delete this.wrapperEl; - return this; - }, - - /** - * Clears all contexts (background, main, top) of an instance - * @return {fabric.Canvas} thisArg - * @chainable - */ - clear: function () { - this.discardActiveGroup(); - this.discardActiveObject(); - this.clearContext(this.contextTop); - return this.callSuper('clear'); - }, - - /** - * Draws objects' controls (borders/controls) - * @param {CanvasRenderingContext2D} ctx Context to render controls on - */ - drawControls: function(ctx) { - var activeGroup = this.getActiveGroup(); - - if (activeGroup) { - activeGroup._renderControls(ctx); - } - else { - this._drawObjectsControls(ctx); - } - }, - - /** - * @private - */ - _drawObjectsControls: function(ctx) { - for (var i = 0, len = this._objects.length; i < len; ++i) { - if (!this._objects[i] || !this._objects[i].active) { - continue; - } - this._objects[i]._renderControls(ctx); - } - }, - - /** - * @private - */ - _toObject: function(instance, methodName, propertiesToInclude) { - //If the object is part of the current selection group, it should - //be transformed appropriately - //i.e. it should be serialised as it would appear if the selection group - //were to be destroyed. - var originalProperties = this._realizeGroupTransformOnObject(instance), - object = this.callSuper('_toObject', instance, methodName, propertiesToInclude); - //Undo the damage we did by changing all of its properties - this._unwindGroupTransformOnObject(instance, originalProperties); - return object; - }, - - /** - * Realises an object's group transformation on it - * @private - * @param {fabric.Object} [instance] the object to transform (gets mutated) - * @returns the original values of instance which were changed - */ - _realizeGroupTransformOnObject: function(instance) { - var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width']; - if (instance.group && instance.group === this.getActiveGroup()) { - //Copy all the positionally relevant properties across now - var originalValues = {}; - layoutProps.forEach(function(prop) { - originalValues[prop] = instance[prop]; - }); - this.getActiveGroup().realizeTransform(instance); - return originalValues; - } - else { - return null; - } - }, - - /** - * Restores the changed properties of instance - * @private - * @param {fabric.Object} [instance] the object to un-transform (gets mutated) - * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject - */ - _unwindGroupTransformOnObject: function(instance, originalValues) { - if (originalValues) { - instance.set(originalValues); - } - }, - - /** - * @private - */ - _setSVGObject: function(markup, instance, reviver) { - var originalProperties; - //If the object is in a selection group, simulate what would happen to that - //object when the group is deselected - originalProperties = this._realizeGroupTransformOnObject(instance); - this.callSuper('_setSVGObject', markup, instance, reviver); - this._unwindGroupTransformOnObject(instance, originalProperties); - }, - }); - - // copying static properties manually to work around Opera's bug, - // where "prototype" property is enumerable and overrides existing prototype - for (var prop in fabric.StaticCanvas) { - if (prop !== 'prototype') { - fabric.Canvas[prop] = fabric.StaticCanvas[prop]; - } - } - - if (fabric.isTouchSupported) { - /** @ignore */ - fabric.Canvas.prototype._setCursorFromEvent = function() { }; - } - - /** - * @ignore - * @class fabric.Element - * @alias fabric.Canvas - * @deprecated Use {@link fabric.Canvas} instead. - * @constructor - */ - fabric.Element = fabric.Canvas; -})(); - - -(function() { - - var cursorOffset = { - mt: 0, // n - tr: 1, // ne - mr: 2, // e - br: 3, // se - mb: 4, // s - bl: 5, // sw - ml: 6, // w - tl: 7 // nw - }, - addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener; - - fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { - - /** - * Map of cursor style values for each of the object controls - * @private - */ - cursorMap: [ - 'n-resize', - 'ne-resize', - 'e-resize', - 'se-resize', - 's-resize', - 'sw-resize', - 'w-resize', - 'nw-resize' - ], - - /** - * Adds mouse listeners to canvas - * @private - */ - _initEventListeners: function () { - - this._bindEvents(); - - addListener(fabric.window, 'resize', this._onResize); - - // mouse events - addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - addListener(this.upperCanvasEl, 'mouseout', this._onMouseOut); - addListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter); - addListener(this.upperCanvasEl, 'wheel', this._onMouseWheel); - addListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu); - - // touch events - addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof eventjs !== 'undefined' && 'add' in eventjs) { - eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture); - eventjs.add(this.upperCanvasEl, 'drag', this._onDrag); - eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); - eventjs.add(this.upperCanvasEl, 'shake', this._onShake); - eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress); - } - }, - - /** - * @private - */ - _bindEvents: function() { - this._onMouseDown = this._onMouseDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseUp = this._onMouseUp.bind(this); - this._onResize = this._onResize.bind(this); - this._onGesture = this._onGesture.bind(this); - this._onDrag = this._onDrag.bind(this); - this._onShake = this._onShake.bind(this); - this._onLongPress = this._onLongPress.bind(this); - this._onOrientationChange = this._onOrientationChange.bind(this); - this._onMouseWheel = this._onMouseWheel.bind(this); - this._onMouseOut = this._onMouseOut.bind(this); - this._onMouseEnter = this._onMouseEnter.bind(this); - this._onContextMenu = this._onContextMenu.bind(this); - }, - - /** - * Removes all event listeners - */ - removeListeners: function() { - removeListener(fabric.window, 'resize', this._onResize); - - removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'mouseout', this._onMouseOut); - removeListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter); - removeListener(this.upperCanvasEl, 'wheel', this._onMouseWheel); - removeListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu); - - removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (typeof eventjs !== 'undefined' && 'remove' in eventjs) { - eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture); - eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag); - eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); - eventjs.remove(this.upperCanvasEl, 'shake', this._onShake); - eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress); - } - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js gesture - * @param {Event} [self] Inner Event object - */ - _onGesture: function(e, self) { - this.__onTransformGesture && this.__onTransformGesture(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js drag - * @param {Event} [self] Inner Event object - */ - _onDrag: function(e, self) { - this.__onDrag && this.__onDrag(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on wheel event - */ - _onMouseWheel: function(e) { - this.__onMouseWheel(e); - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseOut: function(e) { - var target = this._hoveredTarget; - this.fire('mouse:out', { target: target, e: e }); - this._hoveredTarget = null; - target && target.fire('mouseout', { e: e }); - }, - - /** - * @private - * @param {Event} e Event object fired on mouseenter - */ - _onMouseEnter: function(e) { - if (!this.findTarget(e)) { - this.fire('mouse:over', { target: null, e: e }); - this._hoveredTarget = null; - } - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js orientation change - * @param {Event} [self] Inner Event object - */ - _onOrientationChange: function(e, self) { - this.__onOrientationChange && this.__onOrientationChange(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onShake: function(e, self) { - this.__onShake && this.__onShake(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onLongPress: function(e, self) { - this.__onLongPress && this.__onLongPress(e, self); - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onContextMenu: function (e) { - if (this.stopContextMenu) { - e.stopPropagation() - e.preventDefault(); - } - return false; - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseDown: function (e) { - this.__onMouseDown(e); - - addListener(fabric.document, 'touchend', this._onMouseUp); - addListener(fabric.document, 'touchmove', this._onMouseMove); - - removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (e.type === 'touchstart') { - // Unbind mousedown to prevent double triggers from touch devices - removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); - } - else { - addListener(fabric.document, 'mouseup', this._onMouseUp); - addListener(fabric.document, 'mousemove', this._onMouseMove); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mouseup - */ - _onMouseUp: function (e) { - this.__onMouseUp(e); - - removeListener(fabric.document, 'mouseup', this._onMouseUp); - removeListener(fabric.document, 'touchend', this._onMouseUp); - - removeListener(fabric.document, 'mousemove', this._onMouseMove); - removeListener(fabric.document, 'touchmove', this._onMouseMove); - - addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); - addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); - - if (e.type === 'touchend') { - // Wait 400ms before rebinding mousedown to prevent double triggers - // from touch devices - var _this = this; - setTimeout(function() { - addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); - }, 400); - } - }, - - /** - * @private - * @param {Event} e Event object fired on mousemove - */ - _onMouseMove: function (e) { - !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); - this.__onMouseMove(e); - }, - - /** - * @private - */ - _onResize: function () { - this.calcOffset(); - }, - - /** - * Decides whether the canvas should be redrawn in mouseup and mousedown events. - * @private - * @param {Object} target - * @param {Object} pointer - */ - _shouldRender: function(target, pointer) { - var activeObject = this.getActiveGroup() || this.getActiveObject(); - - return !!( - (target && ( - target.isMoving || - target !== activeObject)) - || - (!target && !!activeObject) - || - (!target && !activeObject && !this._groupSelector) - || - (pointer && - this._previousPointer && - this.selection && ( - pointer.x !== this._previousPointer.x || - pointer.y !== this._previousPointer.y)) - ); - }, - - /** - * Method that defines the actions when mouse is released on canvas. - * The method resets the currentTransform parameters, store the image corner - * position in the image object and render the canvas on top. - * @private - * @param {Event} e Event object fired on mouseup - */ - __onMouseUp: function (e) { - var target, searchTarget = true, transform = this._currentTransform, - groupSelector = this._groupSelector, - isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0)); - - if (this.isDrawingMode && this._isCurrentlyDrawing) { - this._onMouseUpInDrawingMode(e); - return; - } - - if (transform) { - this._finalizeCurrentTransform(); - searchTarget = !transform.actionPerformed; - } - - target = searchTarget ? this.findTarget(e, true) : transform.target; - - var shouldRender = this._shouldRender(target, this.getPointer(e)); - - if (target || !isClick) { - this._maybeGroupObjects(e); - } - else { - // those are done by default on mouse up - // by _maybeGroupObjects, we are skipping it in case of no target find - this._groupSelector = null; - this._currentTransform = null; - } - - if (target) { - target.isMoving = false; - } - - this._handleCursorAndEvent(e, target, 'up'); - target && (target.__corner = 0); - shouldRender && this.renderAll(); - }, - - /** - * set cursor for mouse up and handle mouseUp event - * @param {Event} e event from mouse - * @param {fabric.Object} target receiving event - * @param {String} eventType event to fire (up, down or move) - */ - _handleCursorAndEvent: function(e, target, eventType) { - this._setCursorFromEvent(e, target); - this._handleEvent(e, eventType, target ? target : null); - }, - - /** - * Handle event firing for target and subtargets - * @param {Event} e event from mouse - * @param {String} eventType event to fire (up, down or move) - * @param {fabric.Object} targetObj receiving event - */ - _handleEvent: function(e, eventType, targetObj) { - var target = typeof targetObj === undefined ? this.findTarget(e) : targetObj, - targets = this.targets || [], - options = { e: e, target: target, subTargets: targets }; - - this.fire('mouse:' + eventType, options); - target && target.fire('mouse' + eventType, options); - for (var i = 0; i < targets.length; i++) { - targets[i].fire('mouse' + eventType, options); - } - }, - - /** - * @private - */ - _finalizeCurrentTransform: function() { - - var transform = this._currentTransform, - target = transform.target; - - if (target._scaling) { - target._scaling = false; - } - - target.setCoords(); - this._restoreOriginXY(target); - - if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) { - this.fire('object:modified', { target: target }); - target.fire('modified'); - } - }, - - /** - * @private - * @param {Object} target Object to restore - */ - _restoreOriginXY: function(target) { - if (this._previousOriginX && this._previousOriginY) { - - var originPoint = target.translateToOriginPoint( - target.getCenterPoint(), - this._previousOriginX, - this._previousOriginY); - - target.originX = this._previousOriginX; - target.originY = this._previousOriginY; - - target.left = originPoint.x; - target.top = originPoint.y; - - this._previousOriginX = null; - this._previousOriginY = null; - } - }, - - /** - * @private - * @param {Event} e Event object fired on mousedown - */ - _onMouseDownInDrawingMode: function(e) { - this._isCurrentlyDrawing = true; - this.discardActiveObject(e).renderAll(); - if (this.clipTo) { - fabric.util.clipContext(this, this.contextTop); - } - var pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseDown(pointer); - this._handleEvent(e, 'down'); - }, - - /** - * @private - * @param {Event} e Event object fired on mousemove - */ - _onMouseMoveInDrawingMode: function(e) { - if (this._isCurrentlyDrawing) { - var pointer = this.getPointer(e); - this.freeDrawingBrush.onMouseMove(pointer); - } - this.setCursor(this.freeDrawingCursor); - this._handleEvent(e, 'move'); - }, - - /** - * @private - * @param {Event} e Event object fired on mouseup - */ - _onMouseUpInDrawingMode: function(e) { - this._isCurrentlyDrawing = false; - if (this.clipTo) { - this.contextTop.restore(); - } - this.freeDrawingBrush.onMouseUp(); - this._handleEvent(e, 'up'); - }, - - /** - * Method that defines the actions when mouse is clic ked on canvas. - * The method inits the currentTransform parameters and renders all the - * canvas so the current image can be placed on the top canvas and the rest - * in on the container one. - * @private - * @param {Event} e Event object fired on mousedown - */ - __onMouseDown: function (e) { - - var target = this.findTarget(e), - pointer = this.getPointer(e, true); - - // if right click just fire events - var isRightClick = 'which' in e ? e.which === 3 : e.button === 2; - if (isRightClick) { - if (this.fireRightClick) { - this._handleEvent(e, 'down', target ? target : null); - } - return; - } - - if (this.isDrawingMode) { - this._onMouseDownInDrawingMode(e); - return; - } - - // ignore if some object is being transformed at this moment - if (this._currentTransform) { - return; - } - - // save pointer for check in __onMouseUp event - this._previousPointer = pointer; - - var shouldRender = this._shouldRender(target, pointer), - shouldGroup = this._shouldGroup(e, target); - - if (this._shouldClearSelection(e, target)) { - this._clearSelection(e, target, pointer); - } - else if (shouldGroup) { - this._handleGrouping(e, target); - target = this.getActiveGroup(); - } - - if (target) { - if (target.selectable && (target.__corner || !shouldGroup)) { - this._beforeTransform(e, target); - this._setupCurrentTransform(e, target); - } - - if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { - this.deactivateAll(); - target.selectable && this.setActiveObject(target, e); - } - } - this._handleEvent(e, 'down', target ? target : null); - // we must renderAll so that we update the visuals - shouldRender && this.renderAll(); - }, - - /** - * @private - */ - _beforeTransform: function(e, target) { - this.stateful && target.saveState(); - - // determine if it's a drag or rotate case - if (target._findTargetCorner(this.getPointer(e))) { - this.onBeforeScaleRotate(target); - } - - }, - - /** - * @private - */ - _clearSelection: function(e, target, pointer) { - this.deactivateAllWithDispatch(e); - - if (target && target.selectable) { - this.setActiveObject(target, e); - } - else if (this.selection) { - this._groupSelector = { - ex: pointer.x, - ey: pointer.y, - top: 0, - left: 0 - }; - } - }, - - /** - * @private - * @param {Object} target Object for that origin is set to center - */ - _setOriginToCenter: function(target) { - this._previousOriginX = this._currentTransform.target.originX; - this._previousOriginY = this._currentTransform.target.originY; - - var center = target.getCenterPoint(); - - target.originX = 'center'; - target.originY = 'center'; - - target.left = center.x; - target.top = center.y; - - this._currentTransform.left = target.left; - this._currentTransform.top = target.top; - }, - - /** - * @private - * @param {Object} target Object for that center is set to origin - */ - _setCenterToOrigin: function(target) { - var originPoint = target.translateToOriginPoint( - target.getCenterPoint(), - this._previousOriginX, - this._previousOriginY); - - target.originX = this._previousOriginX; - target.originY = this._previousOriginY; - - target.left = originPoint.x; - target.top = originPoint.y; - - this._previousOriginX = null; - this._previousOriginY = null; - }, - - /** - * Method that defines the actions when mouse is hovering the canvas. - * The currentTransform parameter will definde whether the user is rotating/scaling/translating - * an image or neither of them (only hovering). A group selection is also possible and would cancel - * all any other type of action. - * In case of an image transformation only the top canvas will be rendered. - * @private - * @param {Event} e Event object fired on mousemove - */ - __onMouseMove: function (e) { - - var target, pointer; - - if (this.isDrawingMode) { - this._onMouseMoveInDrawingMode(e); - return; - } - if (typeof e.touches !== 'undefined' && e.touches.length > 1) { - return; - } - - var groupSelector = this._groupSelector; - - // We initially clicked in an empty area, so we draw a box for multiple selection - if (groupSelector) { - pointer = this.getPointer(e, true); - - groupSelector.left = pointer.x - groupSelector.ex; - groupSelector.top = pointer.y - groupSelector.ey; - - this.renderTop(); - } - else if (!this._currentTransform) { - target = this.findTarget(e); - this._setCursorFromEvent(e, target); - } - else { - this._transformObject(e); - } - this._handleEvent(e, 'move', target ? target : null); - }, - - /** - * Method that defines actions when an Event Mouse Wheel - * @param {Event} e Event object fired on mouseup - */ - __onMouseWheel: function(e) { - this.fire('mouse:wheel', { - e: e - }); - }, - - /** - * @private - * @param {Event} e Event fired on mousemove - */ - _transformObject: function(e) { - var pointer = this.getPointer(e), - transform = this._currentTransform; - - transform.reset = false; - transform.target.isMoving = true; - - this._beforeScaleTransform(e, transform); - this._performTransformAction(e, transform, pointer); - - transform.actionPerformed && this.renderAll(); - }, - - /** - * @private - */ - _performTransformAction: function(e, transform, pointer) { - var x = pointer.x, - y = pointer.y, - target = transform.target, - action = transform.action, - actionPerformed = false; - - if (action === 'rotate') { - (actionPerformed = this._rotateObject(x, y)) && this._fire('rotating', target, e); - } - else if (action === 'scale') { - (actionPerformed = this._onScale(e, transform, x, y)) && this._fire('scaling', target, e); - } - else if (action === 'scaleX') { - (actionPerformed = this._scaleObject(x, y, 'x')) && this._fire('scaling', target, e); - } - else if (action === 'scaleY') { - (actionPerformed = this._scaleObject(x, y, 'y')) && this._fire('scaling', target, e); - } - else if (action === 'skewX') { - (actionPerformed = this._skewObject(x, y, 'x')) && this._fire('skewing', target, e); - } - else if (action === 'skewY') { - (actionPerformed = this._skewObject(x, y, 'y')) && this._fire('skewing', target, e); - } - else { - actionPerformed = this._translateObject(x, y); - if (actionPerformed) { - this._fire('moving', target, e); - this.setCursor(target.moveCursor || this.moveCursor); - } - } - transform.actionPerformed = actionPerformed; - }, - - /** - * @private - */ - _fire: function(eventName, target, e) { - this.fire('object:' + eventName, { target: target, e: e }); - target.fire(eventName, { e: e }); - }, - - /** - * @private - */ - _beforeScaleTransform: function(e, transform) { - if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { - var centerTransform = this._shouldCenterTransform(transform.target); - - // Switch from a normal resize to center-based - if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || - // Switch from center-based resize to normal one - (!centerTransform && transform.originX === 'center' && transform.originY === 'center') - ) { - this._resetCurrentTransform(); - transform.reset = true; - } - } - }, - - /** - * @private - * @param {Event} e Event object - * @param {Object} transform current tranform - * @param {Number} x mouse position x from origin - * @param {Number} y mouse poistion y from origin - * @return {Boolean} true if the scaling occurred - */ - _onScale: function(e, transform, x, y) { - if ((e[this.uniScaleKey] || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) { - transform.currentAction = 'scale'; - return this._scaleObject(x, y); - } - else { - // Switch from a normal resize to proportional - if (!transform.reset && transform.currentAction === 'scale') { - this._resetCurrentTransform(); - } - - transform.currentAction = 'scaleEqually'; - return this._scaleObject(x, y, 'equally'); - } - }, - - /** - * Sets the cursor depending on where the canvas is being hovered. - * Note: very buggy in Opera - * @param {Event} e Event object - * @param {Object} target Object that the mouse is hovering, if so. - */ - _setCursorFromEvent: function (e, target) { - if (!target) { - this.setCursor(this.defaultCursor); - return false; - } - - var hoverCursor = target.hoverCursor || this.hoverCursor; - if (!target.selectable) { - //let's skip _findTargetCorner if object is not selectable - this.setCursor(hoverCursor); - } - else { - var activeGroup = this.getActiveGroup(), - // only show proper corner when group selection is not active - corner = target._findTargetCorner - && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(this.getPointer(e, true)); - - if (!corner) { - this.setCursor(hoverCursor); - } - else { - this._setCornerCursor(corner, target, e); - } - } - //actually unclear why it should return something - //is never evaluated - return true; - }, - - /** - * @private - */ - _setCornerCursor: function(corner, target, e) { - if (corner in cursorOffset) { - this.setCursor(this._getRotatedCornerCursor(corner, target, e)); - } - else if (corner === 'mtr' && target.hasRotatingPoint) { - this.setCursor(this.rotationCursor); - } - else { - this.setCursor(this.defaultCursor); - return false; - } - }, - - /** - * @private - */ - _getRotatedCornerCursor: function(corner, target, e) { - var n = Math.round((target.getAngle() % 360) / 45); - - if (n < 0) { - n += 8; // full circle ahead - } - n += cursorOffset[corner]; - if (e[this.altActionKey] && cursorOffset[corner] % 2 === 0) { - //if we are holding shift and we are on a mx corner... - n += 2; - } - // normalize n to be from 0 to 7 - n %= 8; - - return this.cursorMap[n]; - } - }); -})(); - - -(function() { - - var min = Math.min, - max = Math.max; - - fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - * @return {Boolean} - */ - _shouldGroup: function(e, target) { - var activeObject = this.getActiveObject(); - return e[this.selectionKey] && target && target.selectable && - (this.getActiveGroup() || (activeObject && activeObject !== target)) - && this.selection; - }, - - /** - * @private - * @param {Event} e Event object - * @param {fabric.Object} target - */ - _handleGrouping: function (e, target) { - var activeGroup = this.getActiveGroup(); - - if (target === activeGroup) { - // if it's a group, find target again, using activeGroup objects - target = this.findTarget(e, true); - // if even object is not found, bail out - if (!target) { - return; - } - } - if (activeGroup) { - this._updateActiveGroup(target, e); - } - else { - this._createActiveGroup(target, e); - } - - if (this._activeGroup) { - this._activeGroup.saveCoords(); - } - }, - - /** - * @private - */ - _updateActiveGroup: function(target, e) { - var activeGroup = this.getActiveGroup(); - - if (activeGroup.contains(target)) { - - activeGroup.removeWithUpdate(target); - target.set('active', false); - - if (activeGroup.size() === 1) { - // remove group alltogether if after removal it only contains 1 object - this.discardActiveGroup(e); - // activate last remaining object - this.setActiveObject(activeGroup.item(0)); - return; - } - } - else { - activeGroup.addWithUpdate(target); - } - this.fire('selection:created', { target: activeGroup, e: e }); - activeGroup.set('active', true); - }, - - /** - * @private - */ - _createActiveGroup: function(target, e) { - - if (this._activeObject && target !== this._activeObject) { - - var group = this._createGroup(target); - group.addWithUpdate(); - - this.setActiveGroup(group); - this._activeObject = null; - - this.fire('selection:created', { target: group, e: e }); - } - - target.set('active', true); - }, - - /** - * @private - * @param {Object} target - */ - _createGroup: function(target) { - - var objects = this.getObjects(), - isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), - groupObjects = isActiveLower - ? [this._activeObject, target] - : [target, this._activeObject]; - this._activeObject.isEditing && this._activeObject.exitEditing(); - return new fabric.Group(groupObjects, { - canvas: this - }); - }, - - /** - * @private - * @param {Event} e mouse event - */ - _groupSelectedObjects: function (e) { - - var group = this._collectObjects(); - - // do not create group for 1 element only - if (group.length === 1) { - this.setActiveObject(group[0], e); - } - else if (group.length > 1) { - group = new fabric.Group(group.reverse(), { - canvas: this - }); - group.addWithUpdate(); - this.setActiveGroup(group, e); - group.saveCoords(); - this.fire('selection:created', { target: group }); - this.renderAll(); - } - }, - - /** - * @private - */ - _collectObjects: function() { - var group = [], - currentObject, - x1 = this._groupSelector.ex, - y1 = this._groupSelector.ey, - x2 = x1 + this._groupSelector.left, - y2 = y1 + this._groupSelector.top, - selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), - selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), - isClick = x1 === x2 && y1 === y2; - - for (var i = this._objects.length; i--; ) { - currentObject = this._objects[i]; - - if (!currentObject || !currentObject.selectable || !currentObject.visible) { - continue; - } - - if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || - currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || - currentObject.containsPoint(selectionX1Y1) || - currentObject.containsPoint(selectionX2Y2) - ) { - currentObject.set('active', true); - group.push(currentObject); - - // only add one object if it's a click - if (isClick) { - break; - } - } - } - - return group; - }, - - /** - * @private - */ - _maybeGroupObjects: function(e) { - if (this.selection && this._groupSelector) { - this._groupSelectedObjects(e); - } - - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - activeGroup.setObjectsCoords().setCoords(); - activeGroup.isMoving = false; - this.setCursor(this.defaultCursor); - } - - // clear selection and current transformation - this._groupSelector = null; - this._currentTransform = null; - } - }); - -})(); - - -(function () { - - var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality'); - - fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately - * @param {Object} [options] Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} - * @example Generate jpeg dataURL with lower quality - * var dataURL = canvas.toDataURL({ - * format: 'jpeg', - * quality: 0.8 - * }); - * @example Generate cropped png dataURL (clipping of canvas) - * var dataURL = canvas.toDataURL({ - * format: 'png', - * left: 100, - * top: 100, - * width: 200, - * height: 200 - * }); - * @example Generate double scaled png dataURL - * var dataURL = canvas.toDataURL({ - * format: 'png', - * multiplier: 2 - * }); - */ - toDataURL: function (options) { - options || (options = { }); - - var format = options.format || 'png', - quality = options.quality || 1, - multiplier = options.multiplier || 1, - cropping = { - left: options.left || 0, - top: options.top || 0, - width: options.width || 0, - height: options.height || 0, - }; - return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); - }, - - /** - * @private - */ - __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { - - var origWidth = this.getWidth(), - origHeight = this.getHeight(), - scaledWidth = (cropping.width || this.getWidth()) * multiplier, - scaledHeight = (cropping.height || this.getHeight()) * multiplier, - zoom = this.getZoom(), - newZoom = zoom * multiplier, - vp = this.viewportTransform, - translateX = (vp[4] - cropping.left) * multiplier, - translateY = (vp[5] - cropping.top) * multiplier, - newVp = [newZoom, 0, 0, newZoom, translateX, translateY], - originalInteractive = this.interactive; - - this.viewportTransform = newVp; - // setting interactive to false avoid exporting controls - this.interactive && (this.interactive = false); - if (origWidth !== scaledWidth || origHeight !== scaledHeight) { - // this.setDimensions is going to renderAll also; - this.setDimensions({ width: scaledWidth, height: scaledHeight }); - } - else { - this.renderAll(); - } - var data = this.__toDataURL(format, quality, cropping); - originalInteractive && (this.interactive = originalInteractive); - this.viewportTransform = vp; - //setDimensions with no option object is taking care of: - //this.width, this.height, this.renderAll() - this.setDimensions({ width: origWidth, height: origHeight }); - return data; - }, - - /** - * @private - */ - __toDataURL: function(format, quality) { - - var canvasEl = this.contextContainer.canvas; - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (format === 'jpg') { - format = 'jpeg'; - } - - var data = supportQuality - ? canvasEl.toDataURL('image/' + format, quality) - : canvasEl.toDataURL('image/' + format); - - return data; - }, - - /** - * Exports canvas element to a dataurl image (allowing to change image size via multiplier). - * @deprecated since 1.0.13 - * @param {String} format (png|jpeg) - * @param {Number} multiplier - * @param {Number} quality (0..1) - * @return {String} - */ - toDataURLWithMultiplier: function (format, multiplier, quality) { - return this.toDataURL({ - format: format, - multiplier: multiplier, - quality: quality - }); - }, - }); - -})(); - - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Populates canvas with data from the specified dataless JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} - * @deprecated since 1.2.2 - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} - */ - loadFromDatalessJSON: function (json, callback, reviver) { - return this.loadFromJSON(json, callback, reviver); - }, - - /** - * Populates canvas with data from the specified JSON. - * JSON format must conform to the one of {@link fabric.Canvas#toJSON} - * @param {String|Object} json JSON string or object - * @param {Function} callback Callback, invoked when json is parsed - * and corresponding objects (e.g: {@link fabric.Image}) - * are initialized - * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. - * @return {fabric.Canvas} instance - * @chainable - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} - * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} - * @example loadFromJSON - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); - * @example loadFromJSON with reviver - * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { - * // `o` = json object - * // `object` = fabric.Object instance - * // ... do some stuff ... - * }); - */ - loadFromJSON: function (json, callback, reviver) { - if (!json) { - return; - } - - // serialize if it wasn't already - var serialized = (typeof json === 'string') - ? JSON.parse(json) - : fabric.util.object.clone(json); - - this.clear(); - - var _this = this; - this._enlivenObjects(serialized.objects, function () { - _this._setBgOverlay(serialized, function () { - // remove parts i cannot set as options - delete serialized.objects; - delete serialized.backgroundImage; - delete serialized.overlayImage; - delete serialized.background; - delete serialized.overlay; - // this._initOptions does too many things to just - // call it. Normally loading an Object from JSON - // create the Object instance. Here the Canvas is - // already an instance and we are just loading things over it - for (var prop in serialized) { - _this[prop] = serialized[prop]; - } - callback && callback(); - }); - }, reviver); - return this; - }, - - /** - * @private - * @param {Object} serialized Object with background and overlay information - * @param {Function} callback Invoked after all background and overlay images/patterns loaded - */ - _setBgOverlay: function(serialized, callback) { - var _this = this, - loaded = { - backgroundColor: false, - overlayColor: false, - backgroundImage: false, - overlayImage: false - }; - - if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { - callback && callback(); - return; - } - - var cbIfLoaded = function () { - if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { - _this.renderAll(); - callback && callback(); - } - }; - - this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); - this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); - this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); - this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); - - cbIfLoaded(); - }, - - /** - * @private - * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) - * @param {(Object|String)} value Value to set - * @param {Object} loaded Set loaded property to true if property is set - * @param {Object} callback Callback function to invoke after property is set - */ - __setBgOverlay: function(property, value, loaded, callback) { - var _this = this; - - if (!value) { - loaded[property] = true; - return; - } - - if (property === 'backgroundImage' || property === 'overlayImage') { - fabric.Image.fromObject(value, function(img) { - _this[property] = img; - loaded[property] = true; - callback && callback(); - }); - } - else { - this['set' + fabric.util.string.capitalize(property, true)](value, function() { - loaded[property] = true; - callback && callback(); - }); - } - }, - - /** - * @private - * @param {Array} objects - * @param {Function} callback - * @param {Function} [reviver] - */ - _enlivenObjects: function (objects, callback, reviver) { - var _this = this; - - if (!objects || objects.length === 0) { - callback && callback(); - return; - } - - var renderOnAddRemove = this.renderOnAddRemove; - this.renderOnAddRemove = false; - - fabric.util.enlivenObjects(objects, function(enlivenedObjects) { - enlivenedObjects.forEach(function(obj, index) { - // we splice the array just in case some custom classes restored from JSON - // will add more object to canvas at canvas init. - _this.insertAt(obj, index); - }); - - _this.renderOnAddRemove = renderOnAddRemove; - callback && callback(); - }, null, reviver); - }, - - /** - * @private - * @param {String} format - * @param {Function} callback - */ - _toDataURL: function (format, callback) { - this.clone(function (clone) { - callback(clone.toDataURL(format)); - }); - }, - - /** - * @private - * @param {String} format - * @param {Number} multiplier - * @param {Function} callback - */ - _toDataURLWithMultiplier: function (format, multiplier, callback) { - this.clone(function (clone) { - callback(clone.toDataURLWithMultiplier(format, multiplier)); - }); - }, - - /** - * Clones canvas instance - * @param {Object} [callback] Receives cloned instance as a first argument - * @param {Array} [properties] Array of properties to include in the cloned canvas and children - */ - clone: function (callback, properties) { - var data = JSON.stringify(this.toJSON(properties)); - this.cloneWithoutData(function(clone) { - clone.loadFromJSON(data, function() { - callback && callback(clone); - }); - }); - }, - - /** - * Clones canvas instance without cloning existing data. - * This essentially copies canvas dimensions, clipping properties, etc. - * but leaves data empty (so that you can populate it with your own) - * @param {Object} [callback] Receives cloned instance as a first argument - */ - cloneWithoutData: function(callback) { - var el = fabric.document.createElement('canvas'); - - el.width = this.getWidth(); - el.height = this.getHeight(); - - var clone = new fabric.Canvas(el); - clone.clipTo = this.clipTo; - if (this.backgroundImage) { - clone.setBackgroundImage(this.backgroundImage.src, function() { - clone.renderAll(); - callback && callback(clone); - }); - clone.backgroundImageOpacity = this.backgroundImageOpacity; - clone.backgroundImageStretch = this.backgroundImageStretch; - } - else { - callback && callback(clone); - } - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - toFixed = fabric.util.toFixed, - capitalize = fabric.util.string.capitalize, - degreesToRadians = fabric.util.degreesToRadians, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Object) { - return; - } - - /** - * Root object class from which all 2d shape classes inherit from - * @class fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects} - * @see {@link fabric.Object#initialize} for constructor definition - * - * @fires added - * @fires removed - * - * @fires selected - * @fires deselected - * @fires modified - * @fires rotating - * @fires scaling - * @fires moving - * @fires skewing - * - * @fires mousedown - * @fires mouseup - * @fires mouseover - * @fires mouseout - */ - fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { - - /** - * Retrieves object's {@link fabric.Object#clipTo|clipping function} - * @method getClipTo - * @memberOf fabric.Object.prototype - * @return {Function} - */ - - /** - * Sets object's {@link fabric.Object#clipTo|clipping function} - * @method setClipTo - * @memberOf fabric.Object.prototype - * @param {Function} clipTo Clipping function - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method getTransformMatrix - * @memberOf fabric.Object.prototype - * @return {Array} transformMatrix - */ - - /** - * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} - * @method setTransformMatrix - * @memberOf fabric.Object.prototype - * @param {Array} transformMatrix - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#visible|visible} state - * @method getVisible - * @memberOf fabric.Object.prototype - * @return {Boolean} True if visible - */ - - /** - * Sets object's {@link fabric.Object#visible|visible} state - * @method setVisible - * @memberOf fabric.Object.prototype - * @param {Boolean} value visible value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#shadow|shadow} - * @method getShadow - * @memberOf fabric.Object.prototype - * @return {Object} Shadow instance - */ - - /** - * Retrieves object's {@link fabric.Object#stroke|stroke} - * @method getStroke - * @memberOf fabric.Object.prototype - * @return {String} stroke value - */ - - /** - * Sets object's {@link fabric.Object#stroke|stroke} - * @method setStroke - * @memberOf fabric.Object.prototype - * @param {String} value stroke value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method getStrokeWidth - * @memberOf fabric.Object.prototype - * @return {Number} strokeWidth value - */ - - /** - * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} - * @method setStrokeWidth - * @memberOf fabric.Object.prototype - * @param {Number} value strokeWidth value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originX|originX} - * @method getOriginX - * @memberOf fabric.Object.prototype - * @return {String} originX value - */ - - /** - * Sets object's {@link fabric.Object#originX|originX} - * @method setOriginX - * @memberOf fabric.Object.prototype - * @param {String} value originX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#originY|originY} - * @method getOriginY - * @memberOf fabric.Object.prototype - * @return {String} originY value - */ - - /** - * Sets object's {@link fabric.Object#originY|originY} - * @method setOriginY - * @memberOf fabric.Object.prototype - * @param {String} value originY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#fill|fill} - * @method getFill - * @memberOf fabric.Object.prototype - * @return {String} Fill value - */ - - /** - * Sets object's {@link fabric.Object#fill|fill} - * @method setFill - * @memberOf fabric.Object.prototype - * @param {String} value Fill value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#opacity|opacity} - * @method getOpacity - * @memberOf fabric.Object.prototype - * @return {Number} Opacity value (0-1) - */ - - /** - * Sets object's {@link fabric.Object#opacity|opacity} - * @method setOpacity - * @memberOf fabric.Object.prototype - * @param {Number} value Opacity value (0-1) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) - * @method getAngle - * @memberOf fabric.Object.prototype - * @return {Number} - */ - - /** - * Retrieves object's {@link fabric.Object#top|top position} - * @method getTop - * @memberOf fabric.Object.prototype - * @return {Number} Top value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#top|top position} - * @method setTop - * @memberOf fabric.Object.prototype - * @param {Number} value Top value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#left|left position} - * @method getLeft - * @memberOf fabric.Object.prototype - * @return {Number} Left value (in pixels) - */ - - /** - * Sets object's {@link fabric.Object#left|left position} - * @method setLeft - * @memberOf fabric.Object.prototype - * @param {Number} value Left value (in pixels) - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleX|scaleX} value - * @method getScaleX - * @memberOf fabric.Object.prototype - * @return {Number} scaleX value - */ - - /** - * Sets object's {@link fabric.Object#scaleX|scaleX} value - * @method setScaleX - * @memberOf fabric.Object.prototype - * @param {Number} value scaleX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#scaleY|scaleY} value - * @method getScaleY - * @memberOf fabric.Object.prototype - * @return {Number} scaleY value - */ - - /** - * Sets object's {@link fabric.Object#scaleY|scaleY} value - * @method setScaleY - * @memberOf fabric.Object.prototype - * @param {Number} value scaleY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipX|flipX} value - * @method getFlipX - * @memberOf fabric.Object.prototype - * @return {Boolean} flipX value - */ - - /** - * Sets object's {@link fabric.Object#flipX|flipX} value - * @method setFlipX - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipX value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Retrieves object's {@link fabric.Object#flipY|flipY} value - * @method getFlipY - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value - */ - - /** - * Sets object's {@link fabric.Object#flipY|flipY} value - * @method setFlipY - * @memberOf fabric.Object.prototype - * @param {Boolean} value flipY value - * @return {fabric.Object} thisArg - * @chainable - */ - - /** - * Type of an object (rect, circle, path, etc.). - * Note that this property is meant to be read-only and not meant to be modified. - * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. - * @type String - * @default - */ - type: 'object', - - /** - * Horizontal origin of transformation of an object (one of "left", "right", "center") - * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups - * @type String - * @default - */ - originX: 'left', - - /** - * Vertical origin of transformation of an object (one of "top", "bottom", "center") - * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups - * @type String - * @default - */ - originY: 'top', - - /** - * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} - * @type Number - * @default - */ - top: 0, - - /** - * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} - * @type Number - * @default - */ - left: 0, - - /** - * Object width - * @type Number - * @default - */ - width: 0, - - /** - * Object height - * @type Number - * @default - */ - height: 0, - - /** - * Object scale factor (horizontal) - * @type Number - * @default - */ - scaleX: 1, - - /** - * Object scale factor (vertical) - * @type Number - * @default - */ - scaleY: 1, - - /** - * When true, an object is rendered as flipped horizontally - * @type Boolean - * @default - */ - flipX: false, - - /** - * When true, an object is rendered as flipped vertically - * @type Boolean - * @default - */ - flipY: false, - - /** - * Opacity of an object - * @type Number - * @default - */ - opacity: 1, - - /** - * Angle of rotation of an object (in degrees) - * @type Number - * @default - */ - angle: 0, - - /** - * Angle of skew on x axes of an object (in degrees) - * @type Number - * @default - */ - skewX: 0, - - /** - * Angle of skew on y axes of an object (in degrees) - * @type Number - * @default - */ - skewY: 0, - - /** - * Size of object's controlling corners (in pixels) - * @type Number - * @default - */ - cornerSize: 13, - - /** - * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) - * @type Boolean - * @default - */ - transparentCorners: true, - - /** - * Default cursor value used when hovering over this object on canvas - * @type String - * @default - */ - hoverCursor: null, - - /** - * Default cursor value used when moving this object on canvas - * @type String - * @default - */ - moveCursor: null, - - /** - * Padding between object and its controlling borders (in pixels) - * @type Number - * @default - */ - padding: 0, - - /** - * Color of controlling borders of an object (when it's active) - * @type String - * @default - */ - borderColor: 'rgba(102,153,255,0.75)', - - /** - * Array specifying dash pattern of an object's borders (hasBorder must be true) - * @since 1.6.2 - * @type Array - */ - borderDashArray: null, - - /** - * Color of controlling corners of an object (when it's active) - * @type String - * @default - */ - cornerColor: 'rgba(102,153,255,0.5)', - - /** - * Color of controlling corners of an object (when it's active and transparentCorners false) - * @since 1.6.2 - * @type String - * @default - */ - cornerStrokeColor: null, - - /** - * Specify style of control, 'rect' or 'circle' - * @since 1.6.2 - * @type String - */ - cornerStyle: 'rect', - - /** - * Array specifying dash pattern of an object's control (hasBorder must be true) - * @since 1.6.2 - * @type Array - */ - cornerDashArray: null, - - /** - * When true, this object will use center point as the origin of transformation - * when being scaled via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredScaling: false, - - /** - * When true, this object will use center point as the origin of transformation - * when being rotated via the controls. - * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). - * @since 1.3.4 - * @type Boolean - * @default - */ - centeredRotation: true, - - /** - * Color of object's fill - * @type String - * @default - */ - fill: 'rgb(0,0,0)', - - /** - * Fill rule used to fill an object - * accepted values are nonzero, evenodd - * Backwards incompatibility note: This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) - * @type String - * @default - */ - fillRule: 'nonzero', - - /** - * Composite rule used for canvas globalCompositeOperation - * @type String - * @default - */ - globalCompositeOperation: 'source-over', - - /** - * Background color of an object. Only works with text objects at the moment. - * @type String - * @default - */ - backgroundColor: '', - - /** - * Selection Background color of an object. colored layer behind the object when it is active. - * does not mix good with globalCompositeOperation methods. - * @type String - * @default - */ - selectionBackgroundColor: '', - - /** - * When defined, an object is rendered via stroke and this property specifies its color - * @type String - * @default - */ - stroke: null, - - /** - * Width of a stroke used to render this object - * @type Number - * @default - */ - strokeWidth: 1, - - /** - * Array specifying dash pattern of an object's stroke (stroke must be defined) - * @type Array - */ - strokeDashArray: null, - - /** - * Line endings style of an object's stroke (one of "butt", "round", "square") - * @type String - * @default - */ - strokeLineCap: 'butt', - - /** - * Corner style of an object's stroke (one of "bevil", "round", "miter") - * @type String - * @default - */ - strokeLineJoin: 'miter', - - /** - * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke - * @type Number - * @default - */ - strokeMiterLimit: 10, - - /** - * Shadow object representing shadow of this shape - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * Opacity of object's controlling borders when object is active and moving - * @type Number - * @default - */ - borderOpacityWhenMoving: 0.4, - - /** - * Scale factor of object's controlling borders - * @type Number - * @default - */ - borderScaleFactor: 1, - - /** - * Transform matrix (similar to SVG's transform matrix) - * @type Array - */ - transformMatrix: null, - - /** - * Minimum allowed scale value of an object - * @type Number - * @default - */ - minScaleLimit: 0.01, - - /** - * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). - * But events still fire on it. - * @type Boolean - * @default - */ - selectable: true, - - /** - * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 - * @type Boolean - * @default - */ - evented: true, - - /** - * When set to `false`, an object is not rendered on canvas - * @type Boolean - * @default - */ - visible: true, - - /** - * When set to `false`, object's controls are not displayed and can not be used to manipulate object - * @type Boolean - * @default - */ - hasControls: true, - - /** - * When set to `false`, object's controlling borders are not rendered - * @type Boolean - * @default - */ - hasBorders: true, - - /** - * When set to `false`, object's controlling rotating point will not be visible or selectable - * @type Boolean - * @default - */ - hasRotatingPoint: true, - - /** - * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) - * @type Number - * @default - */ - rotatingPointOffset: 40, - - /** - * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box - * @type Boolean - * @default - */ - perPixelTargetFind: false, - - /** - * When `false`, default object's values are not included in its serialization - * @type Boolean - * @default - */ - includeDefaultValues: true, - - /** - * Function that determines clipping of an object (context is passed as a first argument) - * Note that context origin is at the object's center point (not left/top corner) - * @type Function - */ - clipTo: null, - - /** - * When `true`, object horizontal movement is locked - * @type Boolean - * @default - */ - lockMovementX: false, - - /** - * When `true`, object vertical movement is locked - * @type Boolean - * @default - */ - lockMovementY: false, - - /** - * When `true`, object rotation is locked - * @type Boolean - * @default - */ - lockRotation: false, - - /** - * When `true`, object horizontal scaling is locked - * @type Boolean - * @default - */ - lockScalingX: false, - - /** - * When `true`, object vertical scaling is locked - * @type Boolean - * @default - */ - lockScalingY: false, - - /** - * When `true`, object non-uniform scaling is locked - * @type Boolean - * @default - */ - lockUniScaling: false, - - /** - * When `true`, object horizontal skewing is locked - * @type Boolean - * @default - */ - lockSkewingX: false, - - /** - * When `true`, object vertical skewing is locked - * @type Boolean - * @default - */ - lockSkewingY: false, - - /** - * When `true`, object cannot be flipped by scaling into negative values - * @type Boolean - * @default - */ - - lockScalingFlip: false, - - /** - * When `true`, object is not exported in SVG or OBJECT/JSON - * since 1.6.3 - * @type Boolean - * @default - */ - - excludeFromExport: false, - - /** - * List of properties to consider when checking if state - * of an object is changed (fabric.Object#hasStateChanged) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: ( - 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + - 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + - 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' + - 'skewX skewY' - ).split(' '), - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initGradient: function(options) { - if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { - this.set('fill', new fabric.Gradient(options.fill)); - } - if (options.stroke && options.stroke.colorStops && !(options.stroke instanceof fabric.Gradient)) { - this.set('stroke', new fabric.Gradient(options.stroke)); - } - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _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 - * @param {Object} [options] Options object - */ - _initClipping: function(options) { - if (!options.clipTo || typeof options.clipTo !== 'string') { - return; - } - - var functionBody = fabric.util.getFunctionBody(options.clipTo); - if (typeof functionBody !== 'undefined') { - this.clipTo = new Function('ctx', functionBody); - } - }, - - /** - * Sets object's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this.set(prop, options[prop]); - } - this._initGradient(options); - this._initPattern(options); - this._initClipping(options); - }, - - /** - * Transforms context when rendering an object - * @param {CanvasRenderingContext2D} ctx Context - * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node - */ - transform: function(ctx, fromLeft) { - if (this.group && !this.group._transformDone && this.group === this.canvas._activeGroup) { - this.group.transform(ctx); - } - var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); - ctx.translate(center.x, center.y); - ctx.rotate(degreesToRadians(this.angle)); - ctx.scale( - this.scaleX * (this.flipX ? -1 : 1), - this.scaleY * (this.flipY ? -1 : 1) - ); - ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0); - ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0); - }, - - /** - * Returns an object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor, - fillRule: this.fillRule, - globalCompositeOperation: this.globalCompositeOperation, - transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : this.transformMatrix, - skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), - skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS) - }; - - fabric.util.populateWithProperties(this, object, propertiesToInclude); - - if (!this.includeDefaultValues) { - object = this._removeDefaultValues(object); - } - - return object; - }, - - /** - * Returns (dataless) object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - // will be overwritten by subclasses - return this.toObject(propertiesToInclude); - }, - - /** - * @private - * @param {Object} object - */ - _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype, - stateProperties = prototype.stateProperties; - stateProperties.forEach(function(prop) { - if (object[prop] === prototype[prop]) { - delete object[prop]; - } - var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' && - Object.prototype.toString.call(prototype[prop]) === '[object Array]'; - - // basically a check for [] === [] - if (isArray && object[prop].length === 0 && prototype[prop].length === 0) { - delete object[prop]; - } - }); - - return object; - }, - - /** - * Returns a string representation of an instance - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Basic getter - * @param {String} property Property name - * @return {*} value of a property - */ - get: function(property) { - return this[property]; - }, - - /** - * Return the object scale factor counting also the group scaling - * @return {Object} object with scaleX and scaleY properties - */ - getObjectScaling: function() { - var scaleX = this.scaleX, scaleY = this.scaleY; - if (this.group) { - var scaling = this.group.getObjectScaling(); - scaleX *= scaling.scaleX; - scaleY *= scaling.scaleY; - } - return { scaleX: scaleX, scaleY: scaleY }; - }, - - /** - * @private - */ - _setObject: function(obj) { - for (var prop in obj) { - this._set(prop, obj[prop]); - } - }, - - /** - * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. - * @param {String|Object} key Property name or object (if object, iterate over the object properties) - * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) - * @return {fabric.Object} thisArg - * @chainable - */ - set: function(key, value) { - if (typeof key === 'object') { - this._setObject(key); - } - else { - if (typeof value === 'function' && key !== 'clipTo') { - this._set(key, value(this.get(key))); - } - else { - this._set(key, value); - } - } - return this; - }, - - /** - * @private - * @param {String} key - * @param {*} value - * @return {fabric.Object} thisArg - */ - _set: function(key, value) { - var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); - - if (shouldConstrainValue) { - value = this._constrainScale(value); - } - if (key === 'scaleX' && value < 0) { - this.flipX = !this.flipX; - value *= -1; - } - else if (key === 'scaleY' && value < 0) { - this.flipY = !this.flipY; - value *= -1; - } - else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { - value = new fabric.Shadow(value); - } - - this[key] = value; - - if (key === 'width' || key === 'height') { - this.minScaleLimit = Math.min(0.1, 1 / Math.max(this.width, this.height)); - } - - return this; - }, - - /** - * This callback function is called by the parent group of an object every - * time a non-delegated property changes on the group. It is passed the key - * and value as parameters. Not adding in this function's signature to avoid - * Travis build error about unused variables. - */ - setOnGroup: function() { - // implemented by sub-classes, as needed. - }, - - /** - * Toggles specified property from `true` to `false` or from `false` to `true` - * @param {String} property Property to toggle - * @return {fabric.Object} thisArg - * @chainable - */ - toggle: function(property) { - var value = this.get(property); - if (typeof value === 'boolean') { - this.set(property, !value); - } - return this; - }, - - /** - * Sets sourcePath of an object - * @param {String} value Value to set sourcePath to - * @return {fabric.Object} thisArg - * @chainable - */ - setSourcePath: function(value) { - this.sourcePath = value; - return this; - }, - - /** - * Retrieves viewportTransform from Object's canvas if possible - * @method getViewportTransform - * @memberOf fabric.Object.prototype - * @return {Boolean} flipY value // TODO - */ - getViewportTransform: function() { - if (this.canvas && this.canvas.viewportTransform) { - return this.canvas.viewportTransform; - } - return [1, 0, 0, 1, 0, 0]; - }, - - /** - * Renders an object on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - render: function(ctx, noTransform) { - // do not render if width/height are zeros or object is not visible - if ((this.width === 0 && this.height === 0) || !this.visible) { - return; - } - - ctx.save(); - - //setup fill rule for current object - this._setupCompositeOperation(ctx); - this.drawSelectionBackground(ctx); - if (!noTransform) { - this.transform(ctx); - } - this._setOpacity(ctx); - this._setShadow(ctx); - this._renderBackground(ctx); - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - this.clipTo && fabric.util.clipContext(this, ctx); - this._render(ctx, noTransform); - this.clipTo && ctx.restore(); - - ctx.restore(); - }, - - /** - * Draws a background for the object big as its width and height; - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderBackground: function(ctx) { - if (!this.backgroundColor) { - return; - } - - ctx.fillStyle = this.backgroundColor; - - ctx.fillRect( - -this.width / 2, - -this.height / 2, - this.width, - this.height - ); - // if there is background color no other shadows - // should be casted - this._removeShadow(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setOpacity: function(ctx) { - if (this.group) { - this.group._setOpacity(ctx); - } - ctx.globalAlpha *= this.opacity; - }, - - _setStrokeStyles: function(ctx) { - if (this.stroke) { - ctx.lineWidth = this.strokeWidth; - ctx.lineCap = this.strokeLineCap; - ctx.lineJoin = this.strokeLineJoin; - ctx.miterLimit = this.strokeMiterLimit; - ctx.strokeStyle = this.stroke.toLive - ? this.stroke.toLive(ctx, this) - : this.stroke; - } - }, - - _setFillStyles: function(ctx) { - if (this.fill) { - ctx.fillStyle = this.fill.toLive - ? this.fill.toLive(ctx, this) - : this.fill; - } - }, - - /** - * @private - * Sets line dash - * @param {CanvasRenderingContext2D} ctx Context to set the dash line on - * @param {Array} dashArray array representing dashes - * @param {Function} alternative function to call if browaser does not support lineDash - */ - _setLineDash: function(ctx, dashArray, alternative) { - if (!dashArray) { - return; - } - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & dashArray.length) { - dashArray.push.apply(dashArray, dashArray); - } - if (supportsLineDash) { - ctx.setLineDash(dashArray); - } - else { - alternative && alternative(ctx); - } - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - if (!this.active || noTransform - || (this.group && this.group !== this.canvas.getActiveGroup())) { - return; - } - - var vpt = this.getViewportTransform(), - matrix = this.calcTransformMatrix(), - options; - matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); - options = fabric.util.qrDecompose(matrix); - - ctx.save(); - ctx.translate(options.translateX, options.translateY); - ctx.lineWidth = 1 * this.borderScaleFactor; - ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; - - if (this.group && this.group === this.canvas.getActiveGroup()) { - ctx.rotate(degreesToRadians(options.angle)); - this.drawBordersInGroup(ctx, options); - } - else { - ctx.rotate(degreesToRadians(this.angle)); - this.drawBorders(ctx); - } - this.drawControls(ctx); - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setShadow: function(ctx) { - if (!this.shadow) { - return; - } - - var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1, - multY = (this.canvas && this.canvas.viewportTransform[3]) || 1, - scaling = this.getObjectScaling(); - if (this.canvas && this.canvas._isRetinaScaling()) { - multX *= fabric.devicePixelRatio; - multY *= fabric.devicePixelRatio; - } - ctx.shadowColor = this.shadow.color; - ctx.shadowBlur = this.shadow.blur * (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4; - ctx.shadowOffsetX = this.shadow.offsetX * multX * scaling.scaleX; - ctx.shadowOffsetY = this.shadow.offsetY * multY * scaling.scaleY; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _removeShadow: function(ctx) { - if (!this.shadow) { - return; - } - - ctx.shadowColor = ''; - ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderFill: function(ctx) { - if (!this.fill) { - return; - } - - ctx.save(); - if (this.fill.gradientTransform) { - var g = this.fill.gradientTransform; - ctx.transform.apply(ctx, g); - } - if (this.fill.toLive) { - ctx.translate( - -this.width / 2 + this.fill.offsetX || 0, - -this.height / 2 + this.fill.offsetY || 0); - } - if (this.fillRule === 'evenodd') { - ctx.fill('evenodd'); - } - else { - ctx.fill(); - } - ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderStroke: function(ctx) { - if (!this.stroke || this.strokeWidth === 0) { - return; - } - - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - - ctx.save(); - - this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke); - if (this.stroke.gradientTransform) { - var g = this.stroke.gradientTransform; - ctx.transform.apply(ctx, g); - } - if (this.stroke.toLive) { - ctx.translate( - -this.width / 2 + this.stroke.offsetX || 0, - -this.height / 2 + this.stroke.offsetY || 0); - } - ctx.stroke(); - ctx.restore(); - }, - - /** - * Clones an instance, some objects are async, so using callback method will work for every object. - * Using the direct return does not work for images and groups. - * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {fabric.Object} clone of an instance - */ - clone: function(callback, propertiesToInclude) { - if (this.constructor.fromObject) { - return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); - } - return new fabric.Object(this.toObject(propertiesToInclude)); - }, - - /** - * Creates an instance of fabric.Image out of an object - * @param {Function} callback callback, invoked with an instance as a first argument - * @param {Object} [options] for clone as image, passed to toDataURL - * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image - * @return {fabric.Object} thisArg - */ - cloneAsImage: function(callback, options) { - var dataUrl = this.toDataURL(options); - fabric.util.loadImage(dataUrl, function(img) { - if (callback) { - callback(new fabric.Image(img)); - } - }); - return this; - }, - - /** - * Converts an object into a data-url-like string - * @param {Object} options Options object - * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" - * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. - * @param {Number} [options.multiplier=1] Multiplier to scale by - * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 - * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 - * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 - * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 - * @param {Boolean} [options.enableRetina] Enable retina scaling for clone image. Introduce in 1.6.4 - * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format - */ - toDataURL: function(options) { - options || (options = { }); - - var el = fabric.util.createCanvasElement(), - boundingRect = this.getBoundingRect(); - - el.width = boundingRect.width; - el.height = boundingRect.height; - fabric.util.wrapElement(el, 'div'); - var canvas = new fabric.StaticCanvas(el, { enableRetinaScaling: options.enableRetinaScaling }); - // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 - if (options.format === 'jpg') { - options.format = 'jpeg'; - } - - if (options.format === 'jpeg') { - canvas.backgroundColor = '#fff'; - } - - var origParams = { - active: this.get('active'), - left: this.getLeft(), - top: this.getTop() - }; - - this.set('active', false); - this.setPositionByOrigin(new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2), 'center', 'center'); - - var originalCanvas = this.canvas; - canvas.add(this); - var data = canvas.toDataURL(options); - - this.set(origParams).setCoords(); - this.canvas = originalCanvas; - - canvas.dispose(); - canvas = null; - - return data; - }, - - /** - * Returns true if specified type is identical to the type of an instance - * @param {String} type Type to check against - * @return {Boolean} - */ - isType: function(type) { - return this.type === type; - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 0; - }, - - /** - * Returns a JSON representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} JSON - */ - toJSON: function(propertiesToInclude) { - // delegate, not alias - return this.toObject(propertiesToInclude); - }, - - /** - * Sets gradient (fill or stroke) of an object - * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 - * @param {String} property Property name 'stroke' or 'fill' - * @param {Object} [options] Options object - * @param {String} [options.type] Type of gradient 'radial' or 'linear' - * @param {Number} [options.x1=0] x-coordinate of start point - * @param {Number} [options.y1=0] y-coordinate of start point - * @param {Number} [options.x2=0] x-coordinate of end point - * @param {Number} [options.y2=0] y-coordinate of end point - * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) - * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) - * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} - * @param {Object} [options.gradientTransform] transforMatrix for gradient - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} - * @example Set linear gradient - * object.setGradient('fill', { - * type: 'linear', - * x1: -object.width / 2, - * y1: 0, - * x2: object.width / 2, - * y2: 0, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - * @example Set radial gradient - * object.setGradient('fill', { - * type: 'radial', - * x1: 0, - * y1: 0, - * x2: 0, - * y2: 0, - * r1: object.width / 2, - * r2: 10, - * colorStops: { - * 0: 'red', - * 0.5: '#005555', - * 1: 'rgba(0,0,255,0.5)' - * } - * }); - * canvas.renderAll(); - */ - setGradient: function(property, options) { - options || (options = { }); - - var gradient = { colorStops: [] }; - - gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); - gradient.coords = { - x1: options.x1, - y1: options.y1, - x2: options.x2, - y2: options.y2 - }; - - if (options.r1 || options.r2) { - gradient.coords.r1 = options.r1; - gradient.coords.r2 = options.r2; - } - - options.gradientTransform && (gradient.gradientTransform = options.gradientTransform); - - for (var position in options.colorStops) { - var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({ - offset: position, - color: color.toRgb(), - opacity: color.getAlpha() - }); - } - - return this.set(property, fabric.Gradient.forObject(this, gradient)); - }, - - /** - * Sets pattern fill of an object - * @param {Object} options Options object - * @param {(String|HTMLImageElement)} options.source Pattern source - * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) - * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner - * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} - * @example Set pattern - * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { - * object.setPatternFill({ - * source: img, - * repeat: 'repeat' - * }); - * canvas.renderAll(); - * }); - */ - setPatternFill: function(options) { - return this.set('fill', new fabric.Pattern(options)); - }, - - /** - * Sets {@link fabric.Object#shadow|shadow} of an object - * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") - * @param {String} [options.color=rgb(0,0,0)] Shadow color - * @param {Number} [options.blur=0] Shadow blur - * @param {Number} [options.offsetX=0] Shadow horizontal offset - * @param {Number} [options.offsetY=0] Shadow vertical offset - * @return {fabric.Object} thisArg - * @chainable - * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} - * @example Set shadow with string notation - * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); - * canvas.renderAll(); - * @example Set shadow with object notation - * object.setShadow({ - * color: 'red', - * blur: 10, - * offsetX: 20, - * offsetY: 20 - * }); - * canvas.renderAll(); - */ - setShadow: function(options) { - return this.set('shadow', options ? new fabric.Shadow(options) : null); - }, - - /** - * Sets "color" of an instance (alias of `set('fill', …)`) - * @param {String} color Color value - * @return {fabric.Object} thisArg - * @chainable - */ - setColor: function(color) { - this.set('fill', color); - return this; - }, - - /** - * Sets "angle" of an instance - * @param {Number} angle Angle value (in degrees) - * @return {fabric.Object} thisArg - * @chainable - */ - setAngle: function(angle) { - var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; - - if (shouldCenterOrigin) { - this._setOriginToCenter(); - } - - this.set('angle', angle); - - if (shouldCenterOrigin) { - this._resetOrigin(); - } - - return this; - }, - - /** - * Centers object horizontally on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerH: function () { - this.canvas && this.canvas.centerObjectH(this); - return this; - }, - - /** - * Centers object horizontally on current viewport of canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - viewportCenterH: function () { - this.canvas && this.canvas.viewportCenterObjectH(this); - return this; - }, - - /** - * Centers object vertically on canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - centerV: function () { - this.canvas && this.canvas.centerObjectV(this); - return this; - }, - - /** - * Centers object vertically on current viewport of canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - viewportCenterV: function () { - this.canvas && this.canvas.viewportCenterObjectV(this); - return this; - }, - - /** - * Centers object vertically and horizontally on canvas to which is was added last - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - center: function () { - this.canvas && this.canvas.centerObject(this); - return this; - }, - - /** - * Centers object on current viewport of canvas to which it was added last. - * You might need to call `setCoords` on an object after centering, to update controls area. - * @return {fabric.Object} thisArg - * @chainable - */ - viewportCenter: function () { - this.canvas && this.canvas.viewportCenterObject(this); - return this; - }, - - /** - * Removes object from canvas to which it was added last - * @return {fabric.Object} thisArg - * @chainable - */ - remove: function() { - this.canvas && this.canvas.remove(this); - return this; - }, - - /** - * Returns coordinates of a pointer relative to an object - * @param {Event} e Event to operate upon - * @param {Object} [pointer] Pointer to operate upon (instead of event) - * @return {Object} Coordinates of a pointer (x, y) - */ - getLocalPointer: function(e, pointer) { - pointer = pointer || this.canvas.getPointer(e); - var pClicked = new fabric.Point(pointer.x, pointer.y), - objectLeftTop = this._getLeftTopCoords(); - if (this.angle) { - pClicked = fabric.util.rotatePoint( - pClicked, objectLeftTop, fabric.util.degreesToRadians(-this.angle)); - } - return { - x: pClicked.x - objectLeftTop.x, - y: pClicked.y - objectLeftTop.y - }; - }, - - /** - * Sets canvas globalCompositeOperation for specific object - * custom composition operation for the particular object can be specifed using globalCompositeOperation property - * @param {CanvasRenderingContext2D} ctx Rendering canvas context - */ - _setupCompositeOperation: function (ctx) { - if (this.globalCompositeOperation) { - ctx.globalCompositeOperation = this.globalCompositeOperation; - } - } - }); - - fabric.util.createAccessors(fabric.Object); - - /** - * Alias for {@link fabric.Object.prototype.setAngle} - * @alias rotate -> setAngle - * @memberOf fabric.Object - */ - fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; - - extend(fabric.Object.prototype, fabric.Observable); - - /** - * Defines the number of fraction digits to use when serializing object values. - * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. - * @static - * @memberOf fabric.Object - * @constant - * @type Number - */ - fabric.Object.NUM_FRACTION_DIGITS = 2; - - /** - * Unique id used internally when creating SVG elements - * @static - * @memberOf fabric.Object - * @type Number - */ - fabric.Object.__uid = 0; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians, - originXOffset = { - left: -0.5, - center: 0, - right: 0.5 - }, - originYOffset = { - top: -0.5, - center: 0, - bottom: 0.5 - }; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' - * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' - * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' - * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) { - var x = point.x, - y = point.y, - offsetX, offsetY, dim; - - if (typeof fromOriginX === 'string') { - fromOriginX = originXOffset[fromOriginX]; - } - else { - fromOriginX -= 0.5; - } - - if (typeof toOriginX === 'string') { - toOriginX = originXOffset[toOriginX]; - } - else { - toOriginX -= 0.5; - } - - offsetX = toOriginX - fromOriginX; - - if (typeof fromOriginY === 'string') { - fromOriginY = originYOffset[fromOriginY]; - } - else { - fromOriginY -= 0.5; - } - - if (typeof toOriginY === 'string') { - toOriginY = originYOffset[toOriginY]; - } - else { - toOriginY -= 0.5; - } - - offsetY = toOriginY - fromOriginY; - - if (offsetX || offsetY) { - dim = this._getTransformedDimensions(); - x = point.x + offsetX * dim.x; - y = point.y + offsetY * dim.y; - } - - return new fabric.Point(x, y); - }, - - /** - * Translates the coordinates from origin to center coordinates (based on the object's dimensions) - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToCenterPoint: function(point, originX, originY) { - var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center'); - if (this.angle) { - return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle)); - } - return p; - }, - - /** - * Translates the coordinates from center to origin coordinates (based on the object's dimensions) - * @param {fabric.Point} center The point which corresponds to center of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - translateToOriginPoint: function(center, originX, originY) { - var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); - if (this.angle) { - return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle)); - } - return p; - }, - - /** - * Returns the real center coordinates of the object - * @return {fabric.Point} - */ - getCenterPoint: function() { - var leftTop = new fabric.Point(this.left, this.top); - return this.translateToCenterPoint(leftTop, this.originX, this.originY); - }, - - /** - * Returns the coordinates of the object based on center coordinates - * @param {fabric.Point} point The point which corresponds to the originX and originY params - * @return {fabric.Point} - */ - // getOriginPoint: function(center) { - // return this.translateToOriginPoint(center, this.originX, this.originY); - // }, - - /** - * Returns the coordinates of the object as if it has a different origin - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - getPointByOrigin: function(originX, originY) { - var center = this.getCenterPoint(); - return this.translateToOriginPoint(center, originX, originY); - }, - - /** - * Returns the point in local coordinates - * @param {fabric.Point} point The point relative to the global coordinate system - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {fabric.Point} - */ - toLocalPoint: function(point, originX, originY) { - var center = this.getCenterPoint(), - p, p2; - - if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) { - p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); - } - else { - p = new fabric.Point(this.left, this.top); - } - - p2 = new fabric.Point(point.x, point.y); - if (this.angle) { - p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle)); - } - return p2.subtractEquals(p); - }, - - /** - * Returns the point in global coordinates - * @param {fabric.Point} The point relative to the local coordinate system - * @return {fabric.Point} - */ - // toGlobalPoint: function(point) { - // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); - // }, - - /** - * Sets the position of the object taking into consideration the object's origin - * @param {fabric.Point} pos The new position of the object - * @param {String} originX Horizontal origin: 'left', 'center' or 'right' - * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' - * @return {void} - */ - setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY), - position = this.translateToOriginPoint(center, this.originX, this.originY); - - this.set('left', position.x); - this.set('top', position.y); - }, - - /** - * @param {String} to One of 'left', 'center', 'right' - */ - adjustPosition: function(to) { - var angle = degreesToRadians(this.angle), - hypotFull = this.getWidth(), - xFull = Math.cos(angle) * hypotFull, - yFull = Math.sin(angle) * hypotFull, - offsetFrom, offsetTo; - - //TODO: this function does not consider mixed situation like top, center. - if (typeof this.originX === 'string') { - offsetFrom = originXOffset[this.originX]; - } - else { - offsetFrom = this.originX - 0.5; - } - if (typeof to === 'string') { - offsetTo = originXOffset[to]; - } - else { - offsetTo = to - 0.5; - } - this.left += xFull * (offsetTo - offsetFrom); - this.top += yFull * (offsetTo - offsetFrom); - this.setCoords(); - this.originX = to; - }, - - /** - * Sets the origin/position of the object to it's center point - * @private - * @return {void} - */ - _setOriginToCenter: function() { - this._originalOriginX = this.originX; - this._originalOriginY = this.originY; - - var center = this.getCenterPoint(); - - this.originX = 'center'; - this.originY = 'center'; - - this.left = center.x; - this.top = center.y; - }, - - /** - * Resets the origin/position of the object to it's original origin - * @private - * @return {void} - */ - _resetOrigin: function() { - var originPoint = this.translateToOriginPoint( - this.getCenterPoint(), - this._originalOriginX, - this._originalOriginY); - - this.originX = this._originalOriginX; - this.originY = this._originalOriginY; - - this.left = originPoint.x; - this.top = originPoint.y; - - this._originalOriginX = null; - this._originalOriginY = null; - }, - - /** - * @private - */ - _getLeftTopCoords: function() { - return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); - } - }); - -})(); - - -(function() { - - function getCoords(oCoords) { - return [ - new fabric.Point(oCoords.tl.x, oCoords.tl.y), - new fabric.Point(oCoords.tr.x, oCoords.tr.y), - new fabric.Point(oCoords.br.x, oCoords.br.y), - new fabric.Point(oCoords.bl.x, oCoords.bl.y) - ]; - } - - var degreesToRadians = fabric.util.degreesToRadians, - multiplyMatrices = fabric.util.multiplyTransformMatrices; - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Object containing coordinates of object's controls - * @type Object - * @default - */ - oCoords: null, - - /** - * Checks if object intersects with an area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object intersects with an area formed by 2 points - */ - intersectsWithRect: function(pointTL, pointBR) { - var oCoords = getCoords(this.oCoords), - intersection = fabric.Intersection.intersectPolygonRectangle( - oCoords, - pointTL, - pointBR - ); - return intersection.status === 'Intersection'; - }, - - /** - * Checks if object intersects with another object - * @param {Object} other Object to test - * @return {Boolean} true if object intersects with another object - */ - intersectsWithObject: function(other) { - var intersection = fabric.Intersection.intersectPolygonPolygon( - getCoords(this.oCoords), - getCoords(other.oCoords) - ); - - return intersection.status === 'Intersection' - || other.isContainedWithinObject(this) - || this.isContainedWithinObject(other); - }, - - /** - * Checks if object is fully contained within area of another object - * @param {Object} other Object to test - * @return {Boolean} true if object is fully contained within area of another object - */ - isContainedWithinObject: function(other) { - var points = getCoords(this.oCoords), - i = 0; - for (; i < 4; i++) { - if (!other.containsPoint(points[i])) { - return false; - } - } - return true; - }, - - /** - * Checks if object is fully contained within area formed by 2 points - * @param {Object} pointTL top-left point of area - * @param {Object} pointBR bottom-right point of area - * @return {Boolean} true if object is fully contained within area formed by 2 points - */ - isContainedWithinRect: function(pointTL, pointBR) { - var boundingRect = this.getBoundingRect(); - - return ( - boundingRect.left >= pointTL.x && - boundingRect.left + boundingRect.width <= pointBR.x && - boundingRect.top >= pointTL.y && - boundingRect.top + boundingRect.height <= pointBR.y - ); - }, - - /** - * Checks if point is inside the object - * @param {fabric.Point} point Point to check against - * @return {Boolean} true if point is inside the object - */ - containsPoint: function(point) { - if (!this.oCoords) { - this.setCoords(); - } - var lines = this._getImageLines(this.oCoords), - xPoints = this._findCrossPoints(point, lines); - - // if xPoints is odd then point is inside the object - return (xPoints !== 0 && xPoints % 2 === 1); - }, - - /** - * Method that returns an object with the object edges in it, given the coordinates of the corners - * @private - * @param {Object} oCoords Coordinates of the object corners - */ - _getImageLines: function(oCoords) { - return { - topline: { - o: oCoords.tl, - d: oCoords.tr - }, - rightline: { - o: oCoords.tr, - d: oCoords.br - }, - bottomline: { - o: oCoords.br, - d: oCoords.bl - }, - leftline: { - o: oCoords.bl, - d: oCoords.tl - } - }; - }, - - /** - * Helper method to determine how many cross points are between the 4 object edges - * and the horizontal line determined by a point on canvas - * @private - * @param {fabric.Point} point Point to check - * @param {Object} oCoords Coordinates of the object being evaluated - */ - // remove yi, not used but left code here just in case. - _findCrossPoints: function(point, oCoords) { - var b1, b2, a1, a2, xi, // yi, - xcount = 0, - iLine; - - for (var lineKey in oCoords) { - iLine = oCoords[lineKey]; - // optimisation 1: line below point. no cross - if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { - continue; - } - // optimisation 2: line above point. no cross - if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { - continue; - } - // optimisation 3: vertical line case - if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { - xi = iLine.o.x; - // yi = point.y; - } - // calculate the intersection point - else { - b1 = 0; - b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y - b1 * point.x; - a2 = iLine.o.y - b2 * iLine.o.x; - - xi = -(a1 - a2) / (b1 - b2); - // yi = a1 + b1 * xi; - } - // dont count xi < point.x cases - if (xi >= point.x) { - xcount += 1; - } - // optimisation 4: specific for square images - if (xcount === 2) { - break; - } - } - return xcount; - }, - - /** - * Returns width of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} width value - */ - getBoundingRectWidth: function() { - return this.getBoundingRect().width; - }, - - /** - * Returns height of an object's bounding rectangle - * @deprecated since 1.0.4 - * @return {Number} height value - */ - getBoundingRectHeight: function() { - return this.getBoundingRect().height; - }, - - /** - * Returns coordinates of object's bounding rectangle (left, top, width, height) - * @return {Object} Object with left, top, width, height properties - */ - getBoundingRect: function() { - this.oCoords || this.setCoords(); - return fabric.util.makeBoundingBoxFromPoints([ - this.oCoords.tl, - this.oCoords.tr, - this.oCoords.br, - this.oCoords.bl - ]); - }, - - /** - * Returns width of an object bounding box counting transformations - * @return {Number} width value - */ - getWidth: function() { - return this._getTransformedDimensions().x; - }, - - /** - * Returns height of an object bounding box counting transformations - * to be renamed in 2.0 - * @return {Number} height value - */ - getHeight: function() { - return this._getTransformedDimensions().y; - }, - - /** - * Makes sure the scale is valid and modifies it if necessary - * @private - * @param {Number} value - * @return {Number} - */ - _constrainScale: function(value) { - if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) { - return -this.minScaleLimit; - } - else { - return this.minScaleLimit; - } - } - return value; - }, - - /** - * Scales an object (equally by x and y) - * @param {Number} value Scale factor - * @return {fabric.Object} thisArg - * @chainable - */ - scale: function(value) { - value = this._constrainScale(value); - - if (value < 0) { - this.flipX = !this.flipX; - this.flipY = !this.flipY; - value *= -1; - } - - this.scaleX = value; - this.scaleY = value; - this.setCoords(); - return this; - }, - - /** - * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) - * @param {Number} value 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.getBoundingRect().width / this.getWidth(); - return this.scale(value / this.width / boundingRectFactor); - }, - - /** - * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) - * @param {Number} value 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.getBoundingRect().height / this.getHeight(); - return this.scale(value / this.height / boundingRectFactor); - }, - - /** - * Sets corner position coordinates based on current angle, width and height - * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords - * @return {fabric.Object} thisArg - * @chainable - */ - setCoords: function() { - var theta = degreesToRadians(this.angle), - vpt = this.getViewportTransform(), - dim = this._calculateCurrentDimensions(), - currentWidth = dim.x, currentHeight = dim.y; - - // If width is negative, make postive. Fixes path selection issue - if (currentWidth < 0) { - currentWidth = Math.abs(currentWidth); - } - - var sinTh = Math.sin(theta), - cosTh = Math.cos(theta), - _angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0, - _hypotenuse = (currentWidth / Math.cos(_angle)) / 2, - offsetX = Math.cos(_angle + theta) * _hypotenuse, - offsetY = Math.sin(_angle + theta) * _hypotenuse, - - // offset added for rotate and scale actions - coords = fabric.util.transformPoint(this.getCenterPoint(), vpt), - tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), - tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)), - bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)), - br = new fabric.Point(coords.x + offsetX, coords.y + offsetY), - ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2), - mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2), - mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2), - mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2), - mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset); - // 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); - canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3); - }, 50); */ - - this.oCoords = { - // corners - tl: tl, tr: tr, br: br, bl: bl, - // middle - ml: ml, mt: mt, mr: mr, mb: mb, - // rotating point - mtr: mtr - }; - - // set coordinates of the draggable boxes in the corners used to scale/rotate the image - this._setCornerCoords && this._setCornerCoords(); - - return this; - }, - - /* - * calculate rotation matrix of an object - * @return {Array} rotation matrix for the object - */ - _calcRotateMatrix: function() { - if (this.angle) { - var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta); - return [cos, sin, -sin, cos, 0, 0]; - } - return [1, 0, 0, 1, 0, 0]; - }, - - /* - * calculate trasform Matrix that represent current transformation from - * object properties. - * @return {Array} matrix Transform Matrix for the object - */ - calcTransformMatrix: function() { - var center = this.getCenterPoint(), - translateMatrix = [1, 0, 0, 1, center.x, center.y], - rotateMatrix = this._calcRotateMatrix(), - dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true), - matrix = this.group ? this.group.calcTransformMatrix() : [1, 0, 0, 1, 0, 0]; - matrix = multiplyMatrices(matrix, translateMatrix); - matrix = multiplyMatrices(matrix, rotateMatrix); - matrix = multiplyMatrices(matrix, dimensionMatrix); - return matrix; - }, - - _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { - var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1], - skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1], - scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1), - scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1), - scaleMatrix = [scaleX, 0, 0, scaleY], - m = multiplyMatrices(scaleMatrix, skewMatrixX, true); - return multiplyMatrices(m, skewMatrixY, true); - } - }); -})(); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Moves an object to the bottom of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - sendToBack: function() { - if (this.group) { - fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); - } - else { - this.canvas.sendToBack(this); - } - return this; - }, - - /** - * Moves an object to the top of the stack of drawn objects - * @return {fabric.Object} thisArg - * @chainable - */ - bringToFront: function() { - if (this.group) { - fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); - } - else { - this.canvas.bringToFront(this); - } - return this; - }, - - /** - * Moves an object down in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - sendBackwards: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); - } - else { - this.canvas.sendBackwards(this, intersecting); - } - return this; - }, - - /** - * Moves an object up in stack of drawn objects - * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object - * @return {fabric.Object} thisArg - * @chainable - */ - bringForward: function(intersecting) { - if (this.group) { - fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); - } - else { - this.canvas.bringForward(this, intersecting); - } - return this; - }, - - /** - * Moves an object to specified level in stack of drawn objects - * @param {Number} index New position of object - * @return {fabric.Object} thisArg - * @chainable - */ - moveTo: function(index) { - if (this.group) { - fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); - } - else { - this.canvas.moveTo(this, index); - } - return this; - } -}); - - -/* _TO_SVG_START_ */ -(function() { - - function getSvgColorString(prop, value) { - if (!value) { - return prop + ': none; '; - } - else if (value.toLive) { - return prop + ': url(#SVGID_' + value.id + '); '; - } - else { - var color = new fabric.Color(value), - str = prop + ': ' + color.toRgb() + '; ', - opacity = color.getAlpha(); - if (opacity !== 1) { - //change the color in rgb + opacity - str += prop + '-opacity: ' + opacity.toString() + '; '; - } - return str; - } - } - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - /** - * Returns styles-string for svg-export - * @param {Boolean} skipShadow a boolean to skip shadow filter output - * @return {String} - */ - getSvgStyles: function(skipShadow) { - - var fillRule = this.fillRule, - strokeWidth = this.strokeWidth ? this.strokeWidth : '0', - strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', - strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', - strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', - strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', - opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - visibility = this.visible ? '' : ' visibility: hidden;', - filter = skipShadow ? '' : this.getSvgFilter(), - fill = getSvgColorString('fill', this.fill), - stroke = getSvgColorString('stroke', this.stroke); - - return [ - stroke, - 'stroke-width: ', strokeWidth, '; ', - 'stroke-dasharray: ', strokeDashArray, '; ', - 'stroke-linecap: ', strokeLineCap, '; ', - 'stroke-linejoin: ', strokeLineJoin, '; ', - 'stroke-miterlimit: ', strokeMiterLimit, '; ', - fill, - 'fill-rule: ', fillRule, '; ', - 'opacity: ', opacity, ';', - filter, - visibility - ].join(''); - }, - - /** - * Returns filter for svg shadow - * @return {String} - */ - getSvgFilter: function() { - return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; - }, - - /** - * Returns id attribute for svg output - * @return {String} - */ - getSvgId: function() { - return this.id ? 'id="' + this.id + '" ' : ''; - }, - - /** - * Returns transform-string for svg-export - * @return {String} - */ - getSvgTransform: function() { - if (this.group && this.group.type === 'path-group') { - return ''; - } - var toFixed = fabric.util.toFixed, - angle = this.getAngle(), - skewX = (this.getSkewX() % 360), - skewY = (this.getSkewY() % 360), - center = this.getCenterPoint(), - - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - - translatePart = this.type === 'path-group' ? '' : 'translate(' + - toFixed(center.x, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(center.y, NUM_FRACTION_DIGITS) + - ')', - - anglePart = angle !== 0 - ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') - : '', - - scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (' scale(' + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - ' ' + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ')'), - - skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '', - - skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '', - - addTranslateX = this.type === 'path-group' ? this.width : 0, - - flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', - - addTranslateY = this.type === 'path-group' ? this.height : 0, - - flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; - - return [ - translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart - ].join(''); - }, - - /** - * Returns transform-string for svg-export from the transform matrix of single elements - * @return {String} - */ - getSvgTransformMatrix: function() { - return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; - }, - - /** - * @private - */ - _createBaseSVGMarkup: function() { - var markup = []; - - if (this.fill && this.fill.toLive) { - markup.push(this.fill.toSVG(this, false)); - } - if (this.stroke && this.stroke.toLive) { - markup.push(this.stroke.toSVG(this, false)); - } - if (this.shadow) { - markup.push(this.shadow.toSVG(this)); - } - return markup; - } - }); -})(); -/* _TO_SVG_END_ */ - - -(function() { - - var extend = fabric.util.object.extend; - - /* - Depends on `stateProperties` - */ - function saveProps(origin, destination, props) { - var tmpObj = { }, deep = true; - props.forEach(function(prop) { - tmpObj[prop] = origin[prop]; - }); - extend(origin[destination], tmpObj, deep); - } - - function _isEqual(origValue, currentValue) { - if (!fabric.isLikelyNode && origValue instanceof Element) { - // avoid checking deep html elements - return origValue === currentValue; - } - else if (origValue instanceof Array) { - if (origValue.length !== currentValue.length) { - return false - } - var _currentValue = currentValue.concat().sort(), - _origValue = origValue.concat().sort(); - return !_origValue.some(function(v, i) { - return !_isEqual(_currentValue[i], v); - }); - } - else if (origValue instanceof Object) { - for (var key in origValue) { - if (!_isEqual(origValue[key], currentValue[key])) { - return false; - } - } - return true; - } - else { - return origValue === currentValue; - } - } - - - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * Returns true if object state (one of its state properties) was changed - * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called - */ - hasStateChanged: function() { - return !_isEqual(this.originalState, this); - }, - - /** - * Saves state of an object - * @param {Object} [options] Object with additional `stateProperties` array to include when saving state - * @return {fabric.Object} thisArg - */ - saveState: function(options) { - saveProps(this, 'originalState', this.stateProperties); - if (options && options.stateProperties) { - saveProps(this, 'originalState', options.stateProperties); - } - return this; - }, - - /** - * Setups state of an object - * @param {Object} [options] Object with additional `stateProperties` array to include when saving state - * @return {fabric.Object} thisArg - */ - setupState: function(options) { - this.originalState = { }; - this.saveState(options); - return this; - } - }); -})(); - - -(function() { - - var degreesToRadians = fabric.util.degreesToRadians, - /* eslint-disable camelcase */ - isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; }; - /* eslint-enable camelcase */ - fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * The object interactivity controls. - * @private - */ - _controlsVisibility: null, - - /** - * Determines which corner has been clicked - * @private - * @param {Object} pointer The pointer indicating the mouse position - * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found - */ - _findTargetCorner: function(pointer) { - if (!this.hasControls || !this.active) { - return false; - } - - var ex = pointer.x, - ey = pointer.y, - xPoints, - lines; - this.__corner = 0; - for (var i in this.oCoords) { - - if (!this.isControlVisible(i)) { - continue; - } - - if (i === 'mtr' && !this.hasRotatingPoint) { - continue; - } - - if (this.get('lockUniScaling') && - (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { - continue; - } - - lines = this._getImageLines(this.oCoords[i].corner); - - // debugging - - // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); - - // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); - // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); - - xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); - if (xPoints !== 0 && xPoints % 2 === 1) { - this.__corner = i; - return i; - } - } - return false; - }, - - /** - * Sets the coordinates of the draggable boxes in the corners of - * the image used to scale/rotate it. - * @private - */ - _setCornerCoords: function() { - var coords = this.oCoords, - newTheta = degreesToRadians(45 - this.angle), - /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */ - /* 0.707106 stands for sqrt(2)/2 */ - cornerHypotenuse = this.cornerSize * 0.707106, - cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), - sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), - x, y; - - for (var point in coords) { - x = coords[point].x; - y = coords[point].y; - coords[point].corner = { - tl: { - x: x - sinHalfOffset, - y: y - cosHalfOffset - }, - tr: { - x: x + cosHalfOffset, - y: y - sinHalfOffset - }, - bl: { - x: x - cosHalfOffset, - y: y + sinHalfOffset - }, - br: { - x: x + sinHalfOffset, - y: y + cosHalfOffset - } - }; - } - }, - - /* - * Calculate object dimensions from its properties - * @private - */ - _getNonTransformedDimensions: function() { - var strokeWidth = this.strokeWidth, - w = this.width, - h = this.height, - addStrokeToW = true, - addStrokeToH = true; - - if (this.type === 'line' && this.strokeLineCap === 'butt') { - addStrokeToH = w; - addStrokeToW = h; - } - - if (addStrokeToH) { - h += h < 0 ? -strokeWidth : strokeWidth; - } - - if (addStrokeToW) { - w += w < 0 ? -strokeWidth : strokeWidth; - } - - return { x: w, y: h }; - }, - - /* - * @private - */ - _getTransformedDimensions: function(skewX, skewY) { - if (typeof skewX === 'undefined') { - skewX = this.skewX; - } - if (typeof skewY === 'undefined') { - skewY = this.skewY; - } - var dimensions = this._getNonTransformedDimensions(), - dimX = dimensions.x / 2, dimY = dimensions.y / 2, - points = [ - { - x: -dimX, - y: -dimY - }, - { - x: dimX, - y: -dimY - }, - { - x: -dimX, - y: dimY - }, - { - x: dimX, - y: dimY - }], - i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), - bbox; - for (i = 0; i < points.length; i++) { - points[i] = fabric.util.transformPoint(points[i], transformMatrix); - } - bbox = fabric.util.makeBoundingBoxFromPoints(points); - return { x: bbox.width, y: bbox.height }; - }, - - /* - * private - */ - _calculateCurrentDimensions: function() { - var vpt = this.getViewportTransform(), - dim = this._getTransformedDimensions(), - w = dim.x, h = dim.y, - p = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true); - - return p.scalarAdd(2 * this.padding); - }, - - /** - * Draws a colored layer behind the object, inside its selection borders. - * Requires public options: padding, selectionBackgroundColor - * this function is called when the context is transformed - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawSelectionBackground: function(ctx) { - if (!this.selectionBackgroundColor || this.group || !this.active) { - return this; - } - ctx.save(); - var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(), - vpt = this.canvas.viewportTransform; - ctx.translate(center.x, center.y); - ctx.scale(1 / vpt[0], 1 / vpt[3]); - ctx.rotate(degreesToRadians(this.angle)); - ctx.fillStyle = this.selectionBackgroundColor; - ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y); - ctx.restore(); - return this; - }, - - /** - * Draws borders of an object's bounding box. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawBorders: function(ctx) { - if (!this.hasBorders) { - return this; - } - - var wh = this._calculateCurrentDimensions(), - strokeWidth = 1 / this.borderScaleFactor, - width = wh.x + strokeWidth, - height = wh.y + strokeWidth; - - ctx.save(); - ctx.strokeStyle = this.borderColor; - this._setLineDash(ctx, this.borderDashArray, null); - - ctx.strokeRect( - -width / 2, - -height / 2, - width, - height - ); - - if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { - - var rotateHeight = -height / 2; - - ctx.beginPath(); - ctx.moveTo(0, rotateHeight); - ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); - ctx.closePath(); - ctx.stroke(); - } - - ctx.restore(); - return this; - }, - - /** - * Draws borders of an object's bounding box when it is inside a group. - * Requires public properties: width, height - * Requires public options: padding, borderColor - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @param {object} options object representing current object parameters - * @return {fabric.Object} thisArg - * @chainable - */ - drawBordersInGroup: function(ctx, options) { - if (!this.hasBorders) { - return this; - } - - var p = this._getNonTransformedDimensions(), - matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX), - wh = fabric.util.transformPoint(p, matrix), - strokeWidth = 1 / this.borderScaleFactor, - width = wh.x + strokeWidth + 2 * this.padding, - height = wh.y + strokeWidth + 2 * this.padding; - - ctx.save(); - this._setLineDash(ctx, this.borderDashArray, null); - ctx.strokeStyle = this.borderColor; - - ctx.strokeRect( - -width / 2, - -height / 2, - width, - height - ); - - ctx.restore(); - return this; - }, - - /** - * Draws corners of an object's bounding box. - * Requires public properties: width, height - * Requires public options: cornerSize, padding - * @param {CanvasRenderingContext2D} ctx Context to draw on - * @return {fabric.Object} thisArg - * @chainable - */ - drawControls: function(ctx) { - if (!this.hasControls) { - return this; - } - - var wh = this._calculateCurrentDimensions(), - width = wh.x, - height = wh.y, - scaleOffset = this.cornerSize, - left = -(width + scaleOffset) / 2, - top = -(height + scaleOffset) / 2, - methodName = this.transparentCorners ? 'stroke' : 'fill'; - - ctx.save(); - ctx.strokeStyle = ctx.fillStyle = this.cornerColor; - if (!this.transparentCorners) { - ctx.strokeStyle = this.cornerStrokeColor; - } - this._setLineDash(ctx, this.cornerDashArray, null); - - // top-left - this._drawControl('tl', ctx, methodName, - left, - top); - - // top-right - this._drawControl('tr', ctx, methodName, - left + width, - top); - - // bottom-left - this._drawControl('bl', ctx, methodName, - left, - top + height); - - // bottom-right - this._drawControl('br', ctx, methodName, - left + width, - top + height); - - if (!this.get('lockUniScaling')) { - - // middle-top - this._drawControl('mt', ctx, methodName, - left + width / 2, - top); - - // middle-bottom - this._drawControl('mb', ctx, methodName, - left + width / 2, - top + height); - - // middle-right - this._drawControl('mr', ctx, methodName, - left + width, - top + height / 2); - - // middle-left - this._drawControl('ml', ctx, methodName, - left, - top + height / 2); - } - - // middle-top-rotate - if (this.hasRotatingPoint) { - this._drawControl('mtr', ctx, methodName, - left + width / 2, - top - this.rotatingPointOffset); - } - - ctx.restore(); - - return this; - }, - - /** - * @private - */ - _drawControl: function(control, ctx, methodName, left, top) { - if (!this.isControlVisible(control)) { - return; - } - var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor; - switch (this.cornerStyle) { - case 'circle': - ctx.beginPath(); - ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false); - ctx[methodName](); - if (stroke) { - ctx.stroke(); - } - break; - default: - isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); - ctx[methodName + 'Rect'](left, top, size, size); - if (stroke) { - ctx.strokeRect(left, top, size, size); - } - } - }, - - /** - * Returns true if the specified control is visible, false otherwise. - * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. - * @returns {Boolean} true if the specified control is visible, false otherwise - */ - isControlVisible: function(controlName) { - return this._getControlsVisibility()[controlName]; - }, - - /** - * Sets the visibility of the specified control. - * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. - * @param {Boolean} visible true to set the specified control visible, false otherwise - * @return {fabric.Object} thisArg - * @chainable - */ - setControlVisible: function(controlName, visible) { - this._getControlsVisibility()[controlName] = visible; - return this; - }, - - /** - * Sets the visibility state of object controls. - * @param {Object} [options] Options object - * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it - * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it - * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it - * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it - * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it - * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it - * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it - * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it - * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it - * @return {fabric.Object} thisArg - * @chainable - */ - setControlsVisibility: function(options) { - options || (options = { }); - - for (var p in options) { - this.setControlVisible(p, options[p]); - } - return this; - }, - - /** - * Returns the instance of the control visibility set for this object. - * @private - * @returns {Object} - */ - _getControlsVisibility: function() { - if (!this._controlsVisibility) { - this._controlsVisibility = { - tl: true, - tr: true, - br: true, - bl: true, - ml: true, - mt: true, - mr: true, - mb: true, - mtr: true - }; - } - return this._controlsVisibility; - } - }); -})(); - - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Animation duration (in ms) for fx* methods - * @type Number - * @default - */ - FX_DURATION: 500, - - /** - * Centers object horizontally with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxCenterObjectH: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('left'), - endValue: this.getCenter().left, - duration: this.FX_DURATION, - onChange: function(value) { - object.set('left', value); - _this.renderAll(); - onChange(); - }, - onComplete: function() { - object.setCoords(); - onComplete(); - } - }); - - return this; - }, - - /** - * Centers object vertically with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxCenterObjectV: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('top'), - endValue: this.getCenter().top, - duration: this.FX_DURATION, - onChange: function(value) { - object.set('top', value); - _this.renderAll(); - onChange(); - }, - onComplete: function() { - object.setCoords(); - onComplete(); - } - }); - - return this; - }, - - /** - * Same as `fabric.Canvas#remove` but animated - * @param {fabric.Object} object Object to remove - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxRemove: function (object, callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: object.get('opacity'), - endValue: 0, - duration: this.FX_DURATION, - onStart: function() { - object.set('active', false); - }, - onChange: function(value) { - object.set('opacity', value); - _this.renderAll(); - onChange(); - }, - onComplete: function () { - _this.remove(object); - onComplete(); - } - }); - - return this; - } -}); - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - /** - * Animates object's properties - * @param {String|Object} property Property to animate (if string) or properties to animate (if object) - * @param {Number|Object} value Value to animate property to (if string was given first) or options object - * @return {fabric.Object} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} - * @chainable - * - * As object — multiple properties - * - * object.animate({ left: ..., top: ... }); - * object.animate({ left: ..., top: ... }, { duration: ... }); - * - * As string — one property - * - * object.animate('left', ...); - * object.animate('left', { duration: ... }); - * - */ - animate: function() { - if (arguments[0] && typeof arguments[0] === 'object') { - var propsToAnimate = [], prop, skipCallbacks; - for (prop in arguments[0]) { - propsToAnimate.push(prop); - } - for (var i = 0, len = propsToAnimate.length; i < len; i++) { - prop = propsToAnimate[i]; - skipCallbacks = i !== len - 1; - this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); - } - } - else { - this._animate.apply(this, arguments); - } - return this; - }, - - /** - * @private - * @param {String} property Property to animate - * @param {String} to Value to animate to - * @param {Object} [options] Options object - * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked - */ - _animate: function(property, to, options, skipCallbacks) { - var _this = this, propPair; - - to = to.toString(); - - if (!options) { - options = { }; - } - else { - options = fabric.util.object.clone(options); - } - - if (~property.indexOf('.')) { - propPair = property.split('.'); - } - - var currentValue = propPair - ? this.get(propPair[0])[propPair[1]] - : this.get(property); - - if (!('from' in options)) { - options.from = currentValue; - } - - if (~to.indexOf('=')) { - to = currentValue + parseFloat(to.replace('=', '')); - } - else { - to = parseFloat(to); - } - - fabric.util.animate({ - startValue: options.from, - endValue: to, - byValue: options.by, - easing: options.easing, - duration: options.duration, - abort: options.abort && function() { - return options.abort.call(_this); - }, - onChange: function(value) { - if (propPair) { - _this[propPair[0]][propPair[1]] = value; - } - else { - _this.set(property, value); - } - if (skipCallbacks) { - return; - } - options.onChange && options.onChange(); - }, - onComplete: function() { - if (skipCallbacks) { - return; - } - - _this.setCoords(); - options.onComplete && options.onComplete(); - } - }); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, - supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); - - if (fabric.Line) { - fabric.warn('fabric.Line is already defined'); - return; - } - - /** - * Line class - * @class fabric.Line - * @extends fabric.Object - * @see {@link fabric.Line#initialize} for constructor definition - */ - fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'line', - - /** - * x value or first line edge - * @type Number - * @default - */ - x1: 0, - - /** - * y value or first line edge - * @type Number - * @default - */ - y1: 0, - - /** - * x value or second line edge - * @type Number - * @default - */ - x2: 0, - - /** - * y value or second line edge - * @type Number - * @default - */ - y2: 0, - - /** - * Constructor - * @param {Array} [points] Array of points - * @param {Object} [options] Options object - * @return {fabric.Line} thisArg - */ - initialize: function(points, options) { - options = options || { }; - - if (!points) { - points = [0, 0, 0, 0]; - } - - this.callSuper('initialize', options); - - this.set('x1', points[0]); - this.set('y1', points[1]); - this.set('x2', points[2]); - this.set('y2', points[3]); - - this._setWidthHeight(options); - }, - - /** - * @private - * @param {Object} [options] Options - */ - _setWidthHeight: function(options) { - options || (options = { }); - - this.width = Math.abs(this.x2 - this.x1); - this.height = Math.abs(this.y2 - this.y1); - - this.left = 'left' in options - ? options.left - : this._getLeftToOriginX(); - - this.top = 'top' in options - ? options.top - : this._getTopToOriginY(); - }, - - /** - * @private - * @param {String} key - * @param {*} value - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - if (typeof coordProps[key] !== 'undefined') { - this._setWidthHeight(); - } - return this; - }, - - /** - * @private - * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. - */ - _getLeftToOriginX: makeEdgeToOriginGetter( - { // property names - origin: 'originX', - axis1: 'x1', - axis2: 'x2', - dimension: 'width' - }, - { // possible values of origin - nearest: 'left', - center: 'center', - farthest: 'right' - } - ), - - /** - * @private - * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. - */ - _getTopToOriginY: makeEdgeToOriginGetter( - { // property names - origin: 'originY', - axis1: 'y1', - axis2: 'y2', - dimension: 'height' - }, - { // possible values of origin - nearest: 'top', - center: 'center', - farthest: 'bottom' - } - ), - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - - if (noTransform) { - // Line coords are distances from left-top of canvas to origin of line. - // To render line in a path-group, we need to translate them to - // distances from center of path-group to center of line. - var cp = this.getCenterPoint(); - ctx.translate( - cp.x - this.strokeWidth / 2, - cp.y - this.strokeWidth / 2 - ); - } - - if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { - // move from center (of virtual box) to its left/top corner - // we can't assume x1, y1 is top left and x2, y2 is bottom right - var p = this.calcLinePoints(); - ctx.moveTo(p.x1, p.y1); - ctx.lineTo(p.x2, p.y2); - } - - ctx.lineWidth = this.strokeWidth; - - // TODO: test this - // make sure setting "fill" changes color of a line - // (by copying fillStyle to strokeStyle, since line is stroked, not filled) - var origStrokeStyle = ctx.strokeStyle; - ctx.strokeStyle = this.stroke || ctx.fillStyle; - this.stroke && this._renderStroke(ctx); - ctx.strokeStyle = origStrokeStyle; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p = this.calcLinePoints(); - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @methd toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); - }, - - /** - * Recalculates line points given width and height - * @private - */ - calcLinePoints: function() { - var xMult = this.x1 <= this.x2 ? -1 : 1, - yMult = this.y1 <= this.y2 ? -1 : 1, - x1 = (xMult * this.width * 0.5), - y1 = (yMult * this.height * 0.5), - x2 = (xMult * this.width * -0.5), - y2 = (yMult * this.height * -0.5); - - return { - x1: x1, - x2: x2, - y1: y1, - y2: y2 - }; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 }; - - if (!(this.group && this.group.type === 'path-group')) { - p = this.calcLinePoints(); - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) - * @static - * @memberOf fabric.Line - * @see http://www.w3.org/TR/SVG/shapes.html#LineElement - */ - fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); - - /** - * Returns fabric.Line instance from an SVG element - * @static - * @memberOf fabric.Line - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromElement = function(element, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), - points = [ - parsedAttributes.x1 || 0, - parsedAttributes.y1 || 0, - parsedAttributes.x2 || 0, - parsedAttributes.y2 || 0 - ]; - return new fabric.Line(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Line instance from an object representation - * @static - * @memberOf fabric.Line - * @param {Object} object Object to create an instance from - * @param {function} [callback] invoked with new instance as first argument - * @return {fabric.Line} instance of fabric.Line - */ - fabric.Line.fromObject = function(object, callback) { - var points = [object.x1, object.y1, object.x2, object.y2], - line = new fabric.Line(points, object); - callback && callback(line); - return line; - }; - - /** - * Produces a function that calculates distance from canvas edge to Line origin. - */ - function makeEdgeToOriginGetter(propertyNames, originValues) { - var origin = propertyNames.origin, - axis1 = propertyNames.axis1, - axis2 = propertyNames.axis2, - dimension = propertyNames.dimension, - nearest = originValues.nearest, - center = originValues.center, - farthest = originValues.farthest; - - return function() { - switch (this.get(origin)) { - case nearest: - return Math.min(this.get(axis1), this.get(axis2)); - case center: - return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); - case farthest: - return Math.max(this.get(axis1), this.get(axis2)); - } - }; - - } - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - pi = Math.PI, - extend = fabric.util.object.extend; - - if (fabric.Circle) { - fabric.warn('fabric.Circle is already defined.'); - return; - } - - /** - * Circle class - * @class fabric.Circle - * @extends fabric.Object - * @see {@link fabric.Circle#initialize} for constructor definition - */ - fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'circle', - - /** - * Radius of this circle - * @type Number - * @default - */ - radius: 0, - - /** - * Start angle of the circle, moving clockwise - * @type Number - * @default 0 - */ - startAngle: 0, - - /** - * End angle of the circle - * @type Number - * @default 2Pi - */ - endAngle: pi * 2, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Circle} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this.set('radius', options.radius || 0); - - this.startAngle = options.startAngle || this.startAngle; - this.endAngle = options.endAngle || this.endAngle; - }, - - /** - * @private - * @param {String} key - * @param {*} value - * @return {fabric.Circle} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key === 'radius') { - this.setRadius(value); - } - - return this; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude)); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0, - angle = (this.endAngle - this.startAngle) % ( 2 * pi); - - if (angle === 0) { - if (this.group && this.group.type === 'path-group') { - x = this.left + this.radius; - y = this.top + this.radius; - } - markup.push( - '\n' - ); - } - else { - var startX = Math.cos(this.startAngle) * this.radius, - startY = Math.sin(this.startAngle) * this.radius, - endX = Math.cos(this.endAngle) * this.radius, - endY = Math.sin(this.endAngle) * this.radius, - largeFlag = angle > pi ? '1' : '0'; - - markup.push( - '\n' - ); - } - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.arc(noTransform ? this.left + this.radius : 0, - noTransform ? this.top + this.radius : 0, - this.radius, - this.startAngle, - this.endAngle, false); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusX: function() { - return this.get('radius') * this.get('scaleX'); - }, - - /** - * Returns vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRadiusY: function() { - return this.get('radius') * this.get('scaleY'); - }, - - /** - * Sets radius of an object (and updates width accordingly) - * @return {fabric.Circle} thisArg - */ - setRadius: function(value) { - this.radius = value; - return this.set('width', value * 2).set('height', value * 2); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) - * @static - * @memberOf fabric.Circle - * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement - */ - fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); - - /** - * Returns {@link fabric.Circle} instance from an SVG element - * @static - * @memberOf fabric.Circle - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @throws {Error} If value of `r` attribute is missing or invalid - * @return {fabric.Circle} Instance of fabric.Circle - */ - fabric.Circle.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); - - if (!isValidRadius(parsedAttributes)) { - throw new Error('value of `r` attribute is required and can not be negative'); - } - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var obj = new fabric.Circle(extend(parsedAttributes, options)); - - obj.left -= obj.radius; - obj.top -= obj.radius; - return obj; - }; - - /** - * @private - */ - function isValidRadius(attributes) { - return (('radius' in attributes) && (attributes.radius >= 0)); - } - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Circle} instance from an object representation - * @static - * @memberOf fabric.Circle - * @param {Object} object Object to create an instance from - * @param {function} [callback] invoked with new instance as first argument - * @return {Object} Instance of fabric.Circle - */ - fabric.Circle.fromObject = function(object, callback) { - var circle = new fabric.Circle(object); - callback && callback(circle); - return circle; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Triangle) { - fabric.warn('fabric.Triangle is already defined'); - return; - } - - /** - * Triangle class - * @class fabric.Triangle - * @extends fabric.Object - * @return {fabric.Triangle} thisArg - * @see {@link fabric.Triangle#initialize} for constructor definition - */ - fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'triangle', - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('width', options.width || 100) - .set('height', options.height || 100); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - ctx.moveTo(-widthBy2, heightBy2); - ctx.lineTo(0, -heightBy2); - ctx.lineTo(widthBy2, heightBy2); - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); - ctx.closePath(); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), - widthBy2 = this.width / 2, - heightBy2 = this.height / 2, - points = [ - -widthBy2 + ' ' + heightBy2, - '0 ' + -heightBy2, - widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Returns fabric.Triangle instance from an object representation - * @static - * @memberOf fabric.Triangle - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Triangle instance is created - * @return {Object} instance of Canvas.Triangle - */ - fabric.Triangle.fromObject = function(object, callback) { - var triangle = new fabric.Triangle(object); - callback && callback(triangle); - return triangle; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - piBy2 = Math.PI * 2, - extend = fabric.util.object.extend; - - if (fabric.Ellipse) { - fabric.warn('fabric.Ellipse is already defined.'); - return; - } - - /** - * Ellipse class - * @class fabric.Ellipse - * @extends fabric.Object - * @return {fabric.Ellipse} thisArg - * @see {@link fabric.Ellipse#initialize} for constructor definition - */ - fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'ellipse', - - /** - * Horizontal radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical radius - * @type Number - * @default - */ - ry: 0, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {fabric.Ellipse} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - - this.set('rx', options.rx || 0); - this.set('ry', options.ry || 0); - }, - - /** - * @private - * @param {String} key - * @param {*} value - * @return {fabric.Ellipse} thisArg - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - switch (key) { - - case 'rx': - this.rx = value; - this.set('width', value * 2); - break; - - case 'ry': - this.ry = value; - this.set('height', value * 2); - break; - - } - return this; - }, - - /** - * Returns horizontal radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRx: function() { - return this.get('rx') * this.get('scaleX'); - }, - - /** - * Returns Vertical radius of an object (according to how an object is scaled) - * @return {Number} - */ - getRy: function() { - return this.get('ry') * this.get('scaleY'); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = 0, y = 0; - if (this.group && this.group.type === 'path-group') { - x = this.left + this.rx; - y = this.top + this.ry; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _render: function(ctx, noTransform) { - ctx.beginPath(); - ctx.save(); - ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); - ctx.arc( - noTransform ? this.left + this.rx : 0, - noTransform ? (this.top + this.ry) * this.rx / this.ry : 0, - this.rx, - 0, - piBy2, - false); - ctx.restore(); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) - * @static - * @memberOf fabric.Ellipse - * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement - */ - fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); - - /** - * Returns {@link fabric.Ellipse} instance from an SVG element - * @static - * @memberOf fabric.Ellipse - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromElement = function(element, options) { - options || (options = { }); - - var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - - var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); - - ellipse.top -= ellipse.ry; - ellipse.left -= ellipse.rx; - return ellipse; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Ellipse} instance from an object representation - * @static - * @memberOf fabric.Ellipse - * @param {Object} object Object to create an instance from - * @param {function} [callback] invoked with new instance as first argument - * @return {fabric.Ellipse} - */ - fabric.Ellipse.fromObject = function(object, callback) { - var ellipse = new fabric.Ellipse(object); - callback && callback(ellipse); - return ellipse; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend; - - if (fabric.Rect) { - fabric.warn('fabric.Rect is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push('rx', 'ry', 'x', 'y'); - - /** - * Rectangle class - * @class fabric.Rect - * @extends fabric.Object - * @return {fabric.Rect} thisArg - * @see {@link fabric.Rect#initialize} for constructor definition - */ - fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { - - /** - * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * Type of an object - * @type String - * @default - */ - type: 'rect', - - /** - * Horizontal border radius - * @type Number - * @default - */ - rx: 0, - - /** - * Vertical border radius - * @type Number - * @default - */ - ry: 0, - - /** - * Used to specify dash pattern for stroke on this object - * @type Array - */ - strokeDashArray: null, - - /** - * Constructor - * @param {Object} [options] Options object - * @return {Object} thisArg - */ - initialize: function(options) { - options = options || { }; - - this.callSuper('initialize', options); - this._initRxRy(); - - }, - - /** - * Initializes rx/ry attributes - * @private - */ - _initRxRy: function() { - if (this.rx && !this.ry) { - this.ry = this.rx; - } - else if (this.ry && !this.rx) { - this.rx = this.ry; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - _render: function(ctx, noTransform) { - - // optimize 1x1 case (used in spray brush) - if (this.width === 1 && this.height === 1) { - ctx.fillRect(-0.5, -0.5, 1, 1); - return; - } - - var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, - ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, - w = this.width, - h = this.height, - x = noTransform ? this.left : -this.width / 2, - y = noTransform ? this.top : -this.height / 2, - isRounded = rx !== 0 || ry !== 0, - /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */ - k = 1 - 0.5522847498; - ctx.beginPath(); - - ctx.moveTo(x + rx, y); - - ctx.lineTo(x + w - rx, y); - isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); - - ctx.lineTo(x + w, y + h - ry); - isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); - - ctx.lineTo(x + rx, y + h); - isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); - - ctx.lineTo(x, y + ry); - isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); - - ctx.closePath(); - - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; - if (!(this.group && this.group.type === 'path-group')) { - x = -this.width / 2; - y = -this.height / 2; - } - markup.push( - '\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) - * @static - * @memberOf fabric.Rect - * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement - */ - fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); - - /** - * Returns {@link fabric.Rect} instance from an SVG element - * @static - * @memberOf fabric.Rect - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Rect} Instance of fabric.Rect - */ - fabric.Rect.fromElement = function(element, options) { - if (!element) { - return null; - } - options = options || { }; - - var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); - - parsedAttributes.left = parsedAttributes.left || 0; - parsedAttributes.top = parsedAttributes.top || 0; - var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - rect.visible = rect.visible && rect.width > 0 && rect.height > 0; - return rect; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns {@link fabric.Rect} instance from an object representation - * @static - * @memberOf fabric.Rect - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created - * @return {Object} instance of fabric.Rect - */ - fabric.Rect.fromObject = function(object, callback) { - var rect = new fabric.Rect(object); - callback && callback(rect); - return rect; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }); - - if (fabric.Polyline) { - fabric.warn('fabric.Polyline is already defined'); - return; - } - - /** - * Polyline class - * @class fabric.Polyline - * @extends fabric.Object - * @see {@link fabric.Polyline#initialize} for constructor definition - */ - fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polyline', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array} points Array of points (where each point is an object with x and y) - * @param {Object} [options] Options object - * @return {fabric.Polyline} thisArg - * @example - * var poly = new fabric.Polyline([ - * { x: 10, y: 10 }, - * { x: 50, y: 30 }, - * { x: 40, y: 70 }, - * { x: 60, y: 50 }, - * { x: 100, y: 150 }, - * { x: 40, y: 100 } - * ], { - * stroke: 'red', - * left: 100, - * top: 100 - * }); - */ - initialize: function(points, options) { - return fabric.Polygon.prototype.initialize.call(this, points, options); - }, - - /** - * @private - */ - _calcDimensions: function() { - return fabric.Polygon.prototype._calcDimensions.call(this); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - return fabric.Polygon.prototype.toSVG.call(this, reviver); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - _render: function(ctx, noTransform) { - if (!fabric.Polygon.prototype.commonRender.call(this, ctx, noTransform)) { - return; - } - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var p1, p2; - - ctx.beginPath(); - for (var i = 0, len = this.points.length; i < len; i++) { - p1 = this.points[i]; - p2 = this.points[i + 1] || p1; - fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.get('points').length; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) - * @static - * @memberOf fabric.Polyline - * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement - */ - fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns fabric.Polyline instance from an SVG element - * @static - * @memberOf fabric.Polyline - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromElement = function(element, options) { - if (!element) { - return null; - } - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); - - return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polyline instance from an object representation - * @static - * @memberOf fabric.Polyline - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created - * @return {fabric.Polyline} Instance of fabric.Polyline - */ - fabric.Polyline.fromObject = function(object, callback) { - var polyline = new fabric.Polyline(object.points, object); - callback && callback(polyline); - return polyline; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - toFixed = fabric.util.toFixed; - - if (fabric.Polygon) { - fabric.warn('fabric.Polygon is already defined'); - return; - } - - /** - * Polygon class - * @class fabric.Polygon - * @extends fabric.Object - * @see {@link fabric.Polygon#initialize} for constructor definition - */ - fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'polygon', - - /** - * Points array - * @type Array - * @default - */ - points: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array} points Array of points - * @param {Object} [options] Options object - * @return {fabric.Polygon} thisArg - */ - initialize: function(points, options) { - options = options || { }; - this.points = points || []; - this.callSuper('initialize', options); - this._calcDimensions(); - if (!('top' in options)) { - this.top = this.minY; - } - if (!('left' in options)) { - this.left = this.minX; - } - this.pathOffset = { - x: this.minX + this.width / 2, - y: this.minY + this.height / 2 - }; - }, - - /** - * @private - */ - _calcDimensions: function() { - - var points = this.points, - minX = min(points, 'x'), - minY = min(points, 'y'), - maxX = max(points, 'x'), - maxY = max(points, 'y'); - - this.width = (maxX - minX) || 0; - this.height = (maxY - minY) || 0; - this.minX = minX || 0; - this.minY = minY || 0; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - points: this.points.concat() - }); - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var points = [], addTransform, - markup = this._createBaseSVGMarkup(); - - for (var i = 0, len = this.points.length; i < len; i++) { - points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); - } - if (!(this.group && this.group.type === 'path-group')) { - addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; - } - markup.push( - '<', this.type, ' ', this.getSvgId(), - 'points="', points.join(''), - '" style="', this.getSvgStyles(), - '" transform="', this.getSvgTransform(), addTransform, - ' ', this.getSvgTransformMatrix(), - '"/>\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - _render: function(ctx, noTransform) { - if (!this.commonRender(ctx, noTransform)) { - return; - } - this._renderFill(ctx); - if (this.stroke || this.strokeDashArray) { - ctx.closePath(); - this._renderStroke(ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - commonRender: function(ctx, noTransform) { - var point, len = this.points.length; - - if (!len || isNaN(this.points[len - 1].y)) { - // do not draw if no points or odd points - // NaN comes from parseFloat of a empty string in parser - return false; - } - - noTransform || ctx.translate(-this.pathOffset.x, -this.pathOffset.y); - ctx.beginPath(); - ctx.moveTo(this.points[0].x, this.points[0].y); - for (var i = 0; i < len; i++) { - point = this.points[i]; - ctx.lineTo(point.x, point.y); - } - return true; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); - ctx.closePath(); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.points.length; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) - * @static - * @memberOf fabric.Polygon - * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement - */ - fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); - - /** - * Returns {@link fabric.Polygon} instance from an SVG element - * @static - * @memberOf fabric.Polygon - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromElement = function(element, options) { - if (!element) { - return null; - } - - options || (options = { }); - - var points = fabric.parsePointsAttribute(element.getAttribute('points')), - parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); - - return new fabric.Polygon(points, extend(parsedAttributes, options)); - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Polygon instance from an object representation - * @static - * @memberOf fabric.Polygon - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created - * @return {fabric.Polygon} Instance of fabric.Polygon - */ - fabric.Polygon.fromObject = function(object, callback) { - var polygon = new fabric.Polygon(object.points, object); - callback && callback(polygon); - return polygon; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - min = fabric.util.array.min, - max = fabric.util.array.max, - extend = fabric.util.object.extend, - _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc, - commandLengths = { - m: 2, - l: 2, - h: 1, - v: 1, - c: 6, - s: 4, - q: 4, - t: 2, - a: 7 - }, - repeatedCommands = { - m: 'l', - M: 'L' - }; - - if (fabric.Path) { - fabric.warn('fabric.Path is already defined'); - return; - } - - /** - * Path class - * @class fabric.Path - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} - * @see {@link fabric.Path#initialize} for constructor definition - */ - fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path', - - /** - * Array of path points - * @type Array - * @default - */ - path: null, - - /** - * Minimum X from points values, necessary to offset points - * @type Number - * @default - */ - minX: 0, - - /** - * Minimum Y from points values, necessary to offset points - * @type Number - * @default - */ - minY: 0, - - /** - * Constructor - * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) - * @param {Object} [options] Options object - * @return {fabric.Path} thisArg - */ - initialize: function(path, options) { - options = options || { }; - - this.setOptions(options); - - if (!path) { - path = []; - } - - var fromArray = _toString.call(path) === '[object Array]'; - - this.path = fromArray - ? path - // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) - : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); - - if (!this.path) { - return; - } - - if (!fromArray) { - this.path = this._parsePath(); - } - - this._setPositionDimensions(options); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * @private - * @param {Object} options Options object - */ - _setPositionDimensions: function(options) { - var calcDim = this._parseDimensions(); - - this.minX = calcDim.left; - this.minY = calcDim.top; - this.width = calcDim.width; - this.height = calcDim.height; - - if (typeof options.left === 'undefined') { - this.left = calcDim.left + (this.originX === 'center' - ? this.width / 2 - : this.originX === 'right' - ? this.width - : 0); - } - - if (typeof options.top === 'undefined') { - this.top = calcDim.top + (this.originY === 'center' - ? this.height / 2 - : this.originY === 'bottom' - ? this.height - : 0); - } - - this.pathOffset = this.pathOffset || { - x: this.minX + this.width / 2, - y: this.minY + this.height / 2 - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render path on - */ - _renderPathCommands: function(ctx) { - var current, // current instruction - previous = null, - subpathStartX = 0, - subpathStartY = 0, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - l = -this.pathOffset.x, - t = -this.pathOffset.y; - - if (this.group && this.group.type === 'path-group') { - l = 0; - t = 0; - } - - ctx.beginPath(); - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - ctx.lineTo(x + l, y + t); - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'v': // vertical lineto, relative - y += current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'V': // verical lineto, absolute - y = current[1]; - ctx.lineTo(x + l, y + t); - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - subpathStartX = x; - subpathStartY = y; - ctx.moveTo(x + l, y + t); - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - ctx.bezierCurveTo( - x + current[1] + l, // x1 - y + current[2] + t, // y1 - controlX + l, // x2 - controlY + t, // y2 - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - ctx.bezierCurveTo( - current[1] + l, - current[2] + t, - controlX + l, - controlY + t, - x + l, - y + t - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - ctx.bezierCurveTo( - controlX + l, - controlY + t, - x + current[1] + l, - y + current[2] + t, - tempX + l, - tempY + t - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - ctx.bezierCurveTo( - controlX + l, - controlY + t, - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - controlX = x + current[1]; - controlY = y + current[2]; - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - - ctx.quadraticCurveTo( - current[1] + l, - current[2] + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - controlX = current[1]; - controlY = current[2]; - break; - - case 't': // shorthand quadraticCurveTo, relative - - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - - break; - - case 'T': - tempX = current[1]; - tempY = current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - ctx.quadraticCurveTo( - controlX + l, - controlY + t, - tempX + l, - tempY + t - ); - x = tempX; - y = tempY; - break; - - case 'a': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x + l, - current[7] + y + t - ]); - x += current[6]; - y += current[7]; - break; - - case 'A': - // TODO: optimize this - drawArc(ctx, x + l, y + t, [ - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + l, - current[7] + t - ]); - x = current[6]; - y = current[7]; - break; - - case 'z': - case 'Z': - x = subpathStartX; - y = subpathStartY; - ctx.closePath(); - break; - } - previous = current; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx context to render path on - */ - _render: function(ctx) { - this._renderPathCommands(ctx); - this._renderFill(ctx); - this._renderStroke(ctx); - }, - - /** - * Returns string representation of an instance - * @return {String} string representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(this.callSuper('toObject', ['sourcePath', 'pathOffset'].concat(propertiesToInclude)), { - path: this.path.map(function(item) { return item.slice() }) - }); - return o; - }, - - /** - * Returns dataless object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.path = this.sourcePath; - } - delete o.sourcePath; - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var chunks = [], - markup = this._createBaseSVGMarkup(), addTransform = ''; - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - if (!(this.group && this.group.type === 'path-group')) { - addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; - } - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns number representation of an instance complexity - * @return {Number} complexity of this instance - */ - complexity: function() { - return this.path.length; - }, - - /** - * @private - */ - _parsePath: function() { - var result = [], - coords = [], - currentPath, - parsed, - re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, - match, - coordsStr; - - for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { - currentPath = this.path[i]; - - coordsStr = currentPath.slice(1).trim(); - coords.length = 0; - - while ((match = re.exec(coordsStr))) { - coords.push(match[0]); - } - - coordsParsed = [currentPath.charAt(0)]; - - for (var j = 0, jlen = coords.length; j < jlen; j++) { - parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - coordsParsed.push(parsed); - } - } - - var command = coordsParsed[0], - commandLength = commandLengths[command.toLowerCase()], - repeatedCommand = repeatedCommands[command] || command; - - if (coordsParsed.length - 1 > commandLength) { - for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { - result.push([command].concat(coordsParsed.slice(k, k + commandLength))); - command = repeatedCommand; - } - } - else { - result.push(coordsParsed); - } - } - - return result; - }, - - /** - * @private - */ - _parseDimensions: function() { - - var aX = [], - aY = [], - current, // current instruction - previous = null, - subpathStartX = 0, - subpathStartY = 0, - x = 0, // current x - y = 0, // current y - controlX = 0, // current control point x - controlY = 0, // current control point y - tempX, - tempY, - bounds; - - for (var i = 0, len = this.path.length; i < len; ++i) { - - current = this.path[i]; - - switch (current[0]) { // first letter - - case 'l': // lineto, relative - x += current[1]; - y += current[2]; - bounds = []; - break; - - case 'L': // lineto, absolute - x = current[1]; - y = current[2]; - bounds = []; - break; - - case 'h': // horizontal lineto, relative - x += current[1]; - bounds = []; - break; - - case 'H': // horizontal lineto, absolute - x = current[1]; - bounds = []; - break; - - case 'v': // vertical lineto, relative - y += current[1]; - bounds = []; - break; - - case 'V': // verical lineto, absolute - y = current[1]; - bounds = []; - break; - - case 'm': // moveTo, relative - x += current[1]; - y += current[2]; - subpathStartX = x; - subpathStartY = y; - bounds = []; - break; - - case 'M': // moveTo, absolute - x = current[1]; - y = current[2]; - subpathStartX = x; - subpathStartY = y; - bounds = []; - break; - - case 'c': // bezierCurveTo, relative - tempX = x + current[5]; - tempY = y + current[6]; - controlX = x + current[3]; - controlY = y + current[4]; - bounds = fabric.util.getBoundsOfCurve(x, y, - x + current[1], // x1 - y + current[2], // y1 - controlX, // x2 - controlY, // y2 - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'C': // bezierCurveTo, absolute - x = current[5]; - y = current[6]; - controlX = current[3]; - controlY = current[4]; - bounds = fabric.util.getBoundsOfCurve(x, y, - current[1], - current[2], - controlX, - controlY, - x, - y - ); - break; - - case 's': // shorthand cubic bezierCurveTo, relative - - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - x + current[1], - y + current[2], - tempX, - tempY - ); - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = x + current[1]; - controlY = y + current[2]; - x = tempX; - y = tempY; - break; - - case 'S': // shorthand cubic bezierCurveTo, absolute - tempX = current[3]; - tempY = current[4]; - if (previous[0].match(/[CcSs]/) === null) { - // If there is no previous command or if the previous command was not a C, c, S, or s, - // the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control points - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - current[1], - current[2], - tempX, - tempY - ); - x = tempX; - y = tempY; - // set control point to 2nd one of this command - // "... the first control point is assumed to be - // the reflection of the second control point on - // the previous command relative to the current point." - controlX = current[1]; - controlY = current[2]; - break; - - case 'q': // quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[3]; - tempY = y + current[4]; - controlX = x + current[1]; - controlY = y + current[2]; - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'Q': // quadraticCurveTo, absolute - controlX = current[1]; - controlY = current[2]; - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - current[3], - current[4] - ); - x = current[3]; - y = current[4]; - break; - - case 't': // shorthand quadraticCurveTo, relative - // transform to absolute x,y - tempX = x + current[1]; - tempY = y + current[2]; - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - - break; - - case 'T': - tempX = current[1]; - tempY = current[2]; - - if (previous[0].match(/[QqTt]/) === null) { - // If there is no previous command or if the previous command was not a Q, q, T or t, - // assume the control point is coincident with the current point - controlX = x; - controlY = y; - } - else { - // calculate reflection of previous control point - controlX = 2 * x - controlX; - controlY = 2 * y - controlY; - } - bounds = fabric.util.getBoundsOfCurve(x, y, - controlX, - controlY, - controlX, - controlY, - tempX, - tempY - ); - x = tempX; - y = tempY; - break; - - case 'a': - // TODO: optimize this - bounds = fabric.util.getBoundsOfArc(x, y, - current[1], - current[2], - current[3], - current[4], - current[5], - current[6] + x, - current[7] + y - ); - x += current[6]; - y += current[7]; - break; - - case 'A': - // TODO: optimize this - bounds = fabric.util.getBoundsOfArc(x, y, - current[1], - current[2], - current[3], - current[4], - current[5], - current[6], - current[7] - ); - x = current[6]; - y = current[7]; - break; - - case 'z': - case 'Z': - x = subpathStartX; - y = subpathStartY; - break; - } - previous = current; - bounds.forEach(function (point) { - aX.push(point.x); - aY.push(point.y); - }); - aX.push(x); - aY.push(y); - } - - var minX = min(aX) || 0, - minY = min(aY) || 0, - maxX = max(aX) || 0, - maxY = max(aY) || 0, - deltaX = maxX - minX, - deltaY = maxY - minY, - - o = { - left: minX, - top: minY, - width: deltaX, - height: deltaY - }; - - return o; - } - }); - - /** - * Creates an instance of fabric.Path from an object - * @static - * @memberOf fabric.Path - * @param {Object} object - * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created - */ - fabric.Path.fromObject = function(object, callback) { - // remove this pattern rom 2.0, accept just object. - var path; - if (typeof object.path === 'string') { - fabric.loadSVGFromURL(object.path, function (elements) { - var pathUrl = object.path; - path = elements[0]; - delete object.path; - - fabric.util.object.extend(path, object); - path.setSourcePath(pathUrl); - - callback && callback(path); - }); - } - else { - path = new fabric.Path(object.path, object); - callback && callback(path); - return path; - } - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) - * @static - * @memberOf fabric.Path - * @see http://www.w3.org/TR/SVG/paths.html#PathElement - */ - fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); - - /** - * Creates an instance of fabric.Path from an SVG element - * @static - * @memberOf fabric.Path - * @param {SVGElement} element to parse - * @param {Function} callback Callback to invoke when an fabric.Path instance is created - * @param {Object} [options] Options object - */ - fabric.Path.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); - callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Path - * @type Boolean - * @default - */ - fabric.Path.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - invoke = fabric.util.array.invoke, - parentToObject = fabric.Object.prototype.toObject; - - if (fabric.PathGroup) { - fabric.warn('fabric.PathGroup is already defined'); - return; - } - - /** - * Path group class - * @class fabric.PathGroup - * @extends fabric.Path - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} - * @see {@link fabric.PathGroup#initialize} for constructor definition - */ - fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'path-group', - - /** - * Fill value - * @type String - * @default - */ - fill: '', - - /** - * Constructor - * @param {Array} paths - * @param {Object} [options] Options object - * @return {fabric.PathGroup} thisArg - */ - initialize: function(paths, options) { - - options = options || { }; - this.paths = paths || []; - - for (var i = this.paths.length; i--;) { - this.paths[i].group = this; - } - - if (options.toBeParsed) { - this.parseDimensionsFromPaths(options); - delete options.toBeParsed; - } - this.setOptions(options); - this.setCoords(); - - if (options.sourcePath) { - this.setSourcePath(options.sourcePath); - } - }, - - /** - * Calculate width and height based on paths contained - */ - parseDimensionsFromPaths: function(options) { - var points, p, xC = [], yC = [], path, height, width, - m; - for (var j = this.paths.length; j--;) { - path = this.paths[j]; - height = path.height + path.strokeWidth; - width = path.width + path.strokeWidth; - points = [ - { x: path.left, y: path.top }, - { x: path.left + width, y: path.top }, - { x: path.left, y: path.top + height }, - { x: path.left + width, y: path.top + height } - ]; - m = this.paths[j].transformMatrix; - for (var i = 0; i < points.length; i++) { - p = points[i]; - if (m) { - p = fabric.util.transformPoint(p, m, false); - } - xC.push(p.x); - yC.push(p.y); - } - } - options.width = Math.max.apply(null, xC); - options.height = Math.max.apply(null, yC); - }, - - /** - * Renders this group on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render this instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - this.transform(ctx); - - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - ctx.translate(-this.width / 2, -this.height / 2); - for (var i = 0, l = this.paths.length; i < l; ++i) { - this.paths[i].render(ctx, true); - } - this.clipTo && ctx.restore(); - ctx.restore(); - }, - - /** - * Sets certain property to a certain value - * @param {String} prop - * @param {*} value - * @return {fabric.PathGroup} thisArg - */ - _set: function(prop, value) { - - if (prop === 'fill' && value && this.isSameColor()) { - var i = this.paths.length; - while (i--) { - this.paths[i]._set(prop, value); - } - } - - return this.callSuper('_set', prop, value); - }, - - /** - * Returns object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - var o = extend(parentToObject.call(this, ['sourcePath'].concat(propertiesToInclude)), { - paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) - }); - return o; - }, - - /** - * Returns dataless object representation of this path group - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} dataless object representation of an instance - */ - toDatalessObject: function(propertiesToInclude) { - var o = this.toObject(propertiesToInclude); - if (this.sourcePath) { - o.paths = this.sourcePath; - } - return o; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var objects = this.getObjects(), - p = this.getPointByOrigin('left', 'top'), - translatePart = 'translate(' + p.x + ' ' + p.y + ')', - markup = this._createBaseSVGMarkup(); - markup.push( - '\n' - ); - - for (var i = 0, len = objects.length; i < len; i++) { - markup.push('\t', objects[i].toSVG(reviver)); - } - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns a string representation of this path group - * @return {String} string representation of an object - */ - toString: function() { - return '#'; - }, - - /** - * Returns true if all paths in this group are of same color - * @return {Boolean} true if all paths are of the same color (`fill`) - */ - isSameColor: function() { - var firstPathFill = this.getObjects()[0].get('fill') || ''; - if (typeof firstPathFill !== 'string') { - return false; - } - firstPathFill = firstPathFill.toLowerCase(); - return this.getObjects().every(function(path) { - var pathFill = path.get('fill') || ''; - return typeof pathFill === 'string' && (pathFill).toLowerCase() === firstPathFill; - }); - }, - - /** - * Returns number representation of object's complexity - * @return {Number} complexity - */ - complexity: function() { - return this.paths.reduce(function(total, path) { - return total + ((path && path.complexity) ? path.complexity() : 0); - }, 0); - }, - - /** - * Returns all paths in this path group - * @return {Array} array of path objects included in this path group - */ - getObjects: function() { - return this.paths; - } - }); - - /** - * Creates fabric.PathGroup instance from an object representation - * @static - * @memberOf fabric.PathGroup - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.PathGroup instance is created - */ - fabric.PathGroup.fromObject = function(object, callback) { - // remove this pattern from 2.0 accepts only object - if (typeof object.paths === 'string') { - fabric.loadSVGFromURL(object.paths, function (elements) { - - var pathUrl = object.paths; - delete object.paths; - - var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); - - callback(pathGroup); - }); - } - else { - fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { - delete object.paths; - callback(new fabric.PathGroup(enlivenedObjects, object)); - }); - } - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.PathGroup - * @type Boolean - * @default - */ - fabric.PathGroup.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - min = fabric.util.array.min, - max = fabric.util.array.max, - invoke = fabric.util.array.invoke; - - if (fabric.Group) { - return; - } - - // lock-related properties, for use in fabric.Group#get - // to enable locking behavior on group - // when one of its objects has lock-related properties set - var _lockProperties = { - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockUniScaling: true - }; - - /** - * Group class - * @class fabric.Group - * @extends fabric.Object - * @mixes fabric.Collection - * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} - * @see {@link fabric.Group#initialize} for constructor definition - */ - fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'group', - - /** - * Width of stroke - * @type Number - * @default - */ - strokeWidth: 0, - - /** - * Indicates if click events should also check for subtargets - * @type Boolean - * @default - */ - subTargetCheck: false, - - /** - * Constructor - * @param {Object} objects Group objects - * @param {Object} [options] Options object - * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. - * @return {Object} thisArg - */ - initialize: function(objects, options, isAlreadyGrouped) { - options = options || { }; - - this._objects = []; - // if objects enclosed in a group have been grouped already, - // we cannot change properties of objects. - // Thus we need to set options to group without objects, - // because delegatedProperties propagate to objects. - isAlreadyGrouped && this.callSuper('initialize', options); - - this._objects = objects || []; - for (var i = this._objects.length; i--; ) { - this._objects[i].group = this; - } - - this.originalState = { }; - - if (options.originX) { - this.originX = options.originX; - } - if (options.originY) { - this.originY = options.originY; - } - - if (isAlreadyGrouped) { - // do not change coordinate of objects enclosed in a group, - // because objects coordinate system have been group coodinate system already. - this._updateObjectsCoords(true); - } - else { - this._calcBounds(); - this._updateObjectsCoords(); - this.callSuper('initialize', options); - } - - this.setCoords(); - this.saveCoords(); - }, - - /** - * @private - * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change - */ - _updateObjectsCoords: function(skipCoordsChange) { - for (var i = this._objects.length; i--; ){ - this._updateObjectCoords(this._objects[i], skipCoordsChange); - } - }, - - /** - * @private - * @param {Object} object - * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change - */ - _updateObjectCoords: function(object, skipCoordsChange) { - // do not display corners of objects enclosed in a group - object.__origHasControls = object.hasControls; - object.hasControls = false; - - if (skipCoordsChange) { - return; - } - - var objectLeft = object.getLeft(), - objectTop = object.getTop(), - center = this.getCenterPoint(); - - object.set({ - originalLeft: objectLeft, - originalTop: objectTop, - left: objectLeft - center.x, - top: objectTop - center.y - }); - object.setCoords(); - }, - - /** - * Returns string represenation of a group - * @return {String} - */ - toString: function() { - return '#'; - }, - - /** - * Adds an object to a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - addWithUpdate: function(object) { - this._restoreObjectsState(); - fabric.util.resetObjectTransform(this); - if (object) { - this._objects.push(object); - object.group = this; - object._set('canvas', this.canvas); - } - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - this._calcBounds(); - this._updateObjectsCoords(); - return this; - }, - - /** - * @private - */ - _setObjectActive: function(object) { - object.set('active', true); - object.group = this; - }, - - /** - * Removes an object from a group; Then recalculates group's dimension, position. - * @param {Object} object - * @return {fabric.Group} thisArg - * @chainable - */ - removeWithUpdate: function(object) { - this._restoreObjectsState(); - fabric.util.resetObjectTransform(this); - // since _restoreObjectsState set objects inactive - this.forEachObject(this._setObjectActive, this); - - this.remove(object); - this._calcBounds(); - this._updateObjectsCoords(); - - return this; - }, - - /** - * @private - */ - _onObjectAdded: function(object) { - object.group = this; - object._set('canvas', this.canvas); - }, - - /** - * @private - */ - _onObjectRemoved: function(object) { - delete object.group; - object.set('active', false); - }, - - /** - * Properties that are delegated to group objects when reading/writing - * @param {Object} delegatedProperties - */ - delegatedProperties: { - fill: true, - stroke: true, - strokeWidth: true, - fontFamily: true, - fontWeight: true, - fontSize: true, - fontStyle: true, - lineHeight: true, - textDecoration: true, - textAlign: true, - backgroundColor: true - }, - - /** - * @private - */ - _set: function(key, value) { - var i = this._objects.length; - - if (this.delegatedProperties[key] || key === 'canvas') { - while (i--) { - this._objects[i].set(key, value); - } - } - else { - while (i--) { - this._objects[i].setOnGroup(key, value); - } - } - - this.callSuper('_set', key, value); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return extend(this.callSuper('toObject', propertiesToInclude), { - objects: invoke(this._objects, 'toObject', propertiesToInclude) - }); - }, - - /** - * Renders instance on a given context - * @param {CanvasRenderingContext2D} ctx context to render instance on - */ - render: function(ctx) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - this.transform(ctx); - this._setShadow(ctx); - this.clipTo && fabric.util.clipContext(this, ctx); - this._transformDone = true; - // the array is now sorted in order of highest first, so start from end - for (var i = 0, len = this._objects.length; i < len; i++) { - this._renderObject(this._objects[i], ctx); - } - - this.clipTo && ctx.restore(); - ctx.restore(); - this._transformDone = false; - }, - - /** - * Renders controls and borders for the object - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} [noTransform] When true, context is not transformed - */ - _renderControls: function(ctx, noTransform) { - this.callSuper('_renderControls', ctx, noTransform); - for (var i = 0, len = this._objects.length; i < len; i++) { - this._objects[i]._renderControls(ctx); - } - }, - - /** - * @private - */ - _renderObject: function(object, ctx) { - // do not render if object is not visible - if (!object.visible) { - return; - } - - var originalHasRotatingPoint = object.hasRotatingPoint; - object.hasRotatingPoint = false; - object.render(ctx); - object.hasRotatingPoint = originalHasRotatingPoint; - }, - - /** - * Retores original state of each of group objects (original state is that which was before group was created). - * @private - * @return {fabric.Group} thisArg - * @chainable - */ - _restoreObjectsState: function() { - this._objects.forEach(this._restoreObjectState, this); - return this; - }, - - /** - * Realises the transform from this group onto the supplied object - * i.e. it tells you what would happen if the supplied object was in - * the group, and then the group was destroyed. It mutates the supplied - * object. - * @param {fabric.Object} object - * @return {fabric.Object} transformedObject - */ - realizeTransform: function(object) { - var matrix = object.calcTransformMatrix(), - options = fabric.util.qrDecompose(matrix), - center = new fabric.Point(options.translateX, options.translateY); - object.scaleX = options.scaleX; - object.scaleY = options.scaleY; - object.skewX = options.skewX; - object.skewY = options.skewY; - object.angle = options.angle; - object.flipX = false; - object.flipY = false; - object.setPositionByOrigin(center, 'center', 'center'); - return object; - }, - - /** - * Restores original state of a specified object in group - * @private - * @param {fabric.Object} object - * @return {fabric.Group} thisArg - */ - _restoreObjectState: function(object) { - this.realizeTransform(object); - object.setCoords(); - object.hasControls = object.__origHasControls; - delete object.__origHasControls; - object.set('active', false); - delete object.group; - - return this; - }, - - /** - * Destroys a group (restoring state of its objects) - * @return {fabric.Group} thisArg - * @chainable - */ - destroy: function() { - return this._restoreObjectsState(); - }, - - /** - * Saves coordinates of this instance (to be used together with `hasMoved`) - * @saveCoords - * @return {fabric.Group} thisArg - * @chainable - */ - saveCoords: function() { - this._originalLeft = this.get('left'); - this._originalTop = this.get('top'); - return this; - }, - - /** - * Checks whether this group was moved (since `saveCoords` was called last) - * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) - */ - hasMoved: function() { - return this._originalLeft !== this.get('left') || - this._originalTop !== this.get('top'); - }, - - /** - * Sets coordinates of all group objects - * @return {fabric.Group} thisArg - * @chainable - */ - setObjectsCoords: function() { - this.forEachObject(function(object) { - object.setCoords(); - }); - return this; - }, - - /** - * @private - */ - _calcBounds: function(onlyWidthHeight) { - var aX = [], - aY = [], - o, prop, - props = ['tr', 'br', 'bl', 'tl'], - i = 0, iLen = this._objects.length, - j, jLen = props.length; - - for ( ; i < iLen; ++i) { - o = this._objects[i]; - o.setCoords(); - for (j = 0; j < jLen; j++) { - prop = props[j]; - aX.push(o.oCoords[prop].x); - aY.push(o.oCoords[prop].y); - } - } - - this.set(this._getBounds(aX, aY, onlyWidthHeight)); - }, - - /** - * @private - */ - _getBounds: function(aX, aY, onlyWidthHeight) { - var ivt = fabric.util.invertTransform(this.getViewportTransform()), - minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), - obj = { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0 - }; - - if (!onlyWidthHeight) { - obj.left = minXY.x || 0; - obj.top = minXY.y || 0; - if (this.originX === 'center') { - obj.left += obj.width / 2; - } - if (this.originX === 'right') { - obj.left += obj.width; - } - if (this.originY === 'center') { - obj.top += obj.height / 2; - } - if (this.originY === 'bottom') { - obj.top += obj.height; - } - } - return obj; - }, - - /* _TO_SVG_START_ */ - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - markup.push( - '\n' - ); - - for (var i = 0, len = this._objects.length; i < len; i++) { - markup.push('\t', this._objects[i].toSVG(reviver)); - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns requested property - * @param {String} prop Property to get - * @return {*} - */ - get: function(prop) { - if (prop in _lockProperties) { - if (this[prop]) { - return this[prop]; - } - else { - for (var i = 0, len = this._objects.length; i < len; i++) { - if (this._objects[i][prop]) { - return true; - } - } - return false; - } - } - else { - if (prop in this.delegatedProperties) { - return this._objects[0] && this._objects[0].get(prop); - } - return this[prop]; - } - } - }); - - /** - * Returns {@link fabric.Group} instance from an object representation - * @static - * @memberOf fabric.Group - * @param {Object} object Object to create a group from - * @param {Function} [callback] Callback to invoke when an group instance is created - */ - fabric.Group.fromObject = function(object, callback) { - fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { - delete object.objects; - callback && callback(new fabric.Group(enlivenedObjects, object, true)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @memberOf fabric.Group - * @type Boolean - * @default - */ - fabric.Group.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var extend = fabric.util.object.extend; - - if (!global.fabric) { - global.fabric = { }; - } - - if (global.fabric.Image) { - fabric.warn('fabric.Image is already defined.'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push( - 'alignX', - 'alignY', - 'meetOrSlice' - ); - - /** - * Image class - * @class fabric.Image - * @extends fabric.Object - * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images} - * @see {@link fabric.Image#initialize} for constructor definition - */ - fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'image', - - /** - * crossOrigin value (one of "", "anonymous", "use-credentials") - * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes - * @type String - * @default - */ - crossOrigin: '', - - /** - * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max") - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * This parameter defines how the picture is aligned to its viewport when image element width differs from image width. - * @type String - * @default - */ - alignX: 'none', - - /** - * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max") - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * This parameter defines how the picture is aligned to its viewport when image element height differs from image height. - * @type String - * @default - */ - alignY: 'none', - - /** - * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice"). - * if meet the image is always fully visibile, if slice the viewport is always filled with image. - * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute - * @type String - * @default - */ - meetOrSlice: 'meet', - - /** - * Width of a stroke. - * For image quality a stroke multiple of 2 gives better results. - * @type Number - * @default - */ - strokeWidth: 0, - - /** - * private - * contains last value of scaleX to detect - * if the Image got resized after the last Render - * @type Number - */ - _lastScaleX: 1, - - /** - * private - * contains last value of scaleY to detect - * if the Image got resized after the last Render - * @type Number - */ - _lastScaleY: 1, - - /** - * minimum scale factor under which any resizeFilter is triggered to resize the image - * 0 will disable the automatic resize. 1 will trigger automatically always. - * number bigger than 1 can be used in case we want to scale with some filter above - * the natural image dimensions - * @type Number - */ - minimumScaleTrigger: 0.5, - - /** - * List of properties to consider when checking if - * state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * Constructor - * @param {HTMLImageElement | String} element Image element - * @param {Object} [options] Options object - * @param {function} [callback] callback function to call after eventual filters applied. - * @return {fabric.Image} thisArg - */ - initialize: function(element, options, callback) { - options || (options = { }); - this.filters = []; - this.resizeFilters = []; - this.callSuper('initialize', options); - this._initElement(element, options, callback); - }, - - /** - * Returns image element which this instance if based on - * @return {HTMLImageElement} Image element - */ - getElement: function() { - return this._element; - }, - - /** - * Sets image element for this instance to a specified one. - * If filters defined they are applied to new image. - * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. - * @param {HTMLImageElement} element - * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - * @chainable - */ - setElement: function(element, callback, options) { - - var _callback, _this; - - this._element = element; - this._originalElement = element; - this._initConfig(options); - - if (this.resizeFilters.length === 0) { - _callback = callback; - } - else { - _this = this; - _callback = function() { - _this.applyFilters(callback, _this.resizeFilters, _this._filteredEl || _this._originalElement, true); - }; - } - - if (this.filters.length !== 0) { - this.applyFilters(_callback); - } - else if (_callback) { - _callback(this); - } - - return this; - }, - - /** - * Sets crossOrigin value (on an instance and corresponding image element) - * @return {fabric.Image} thisArg - * @chainable - */ - setCrossOrigin: function(value) { - this.crossOrigin = value; - this._element.crossOrigin = value; - - return this; - }, - - /** - * Returns original size of an image - * @return {Object} Object with "width" and "height" properties - */ - getOriginalSize: function() { - var element = this.getElement(); - return { - width: element.width, - height: element.height - }; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _stroke: function(ctx) { - if (!this.stroke || this.strokeWidth === 0) { - return; - } - var w = this.width / 2, h = this.height / 2; - ctx.beginPath(); - ctx.moveTo(-w, -h); - ctx.lineTo(w, -h); - ctx.lineTo(w, h); - ctx.lineTo(-w, h); - ctx.lineTo(-w, -h); - ctx.closePath(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderDashedStroke: function(ctx) { - var x = -this.width / 2, - y = -this.height / 2, - w = this.width, - h = this.height; - - ctx.save(); - this._setStrokeStyles(ctx); - - ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); - ctx.closePath(); - ctx.restore(); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var filters = [], resizeFilters = [], - scaleX = 1, scaleY = 1; - - this.filters.forEach(function(filterObj) { - if (filterObj) { - if (filterObj.type === 'Resize') { - scaleX *= filterObj.scaleX; - scaleY *= filterObj.scaleY; - } - filters.push(filterObj.toObject()); - } - }); - - this.resizeFilters.forEach(function(filterObj) { - filterObj && resizeFilters.push(filterObj.toObject()); - }); - var object = extend( - this.callSuper( - 'toObject', - ['crossOrigin', 'alignX', 'alignY', 'meetOrSlice'].concat(propertiesToInclude) - ), { - src: this.getSrc(), - filters: filters, - resizeFilters: resizeFilters, - }); - - object.width /= scaleX; - object.height /= scaleY; - - return object; - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2, - preserveAspectRatio = 'none', filtered = true; - if (this.group && this.group.type === 'path-group') { - x = this.left; - y = this.top; - } - if (this.alignX !== 'none' && this.alignY !== 'none') { - preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice; - } - markup.push( - '\n', - '\n' - ); - - if (this.stroke || this.strokeDashArray) { - var origFill = this.fill; - this.fill = null; - markup.push( - '\n' - ); - this.fill = origFill; - } - - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - /* _TO_SVG_END_ */ - - /** - * Returns source of an image - * @param {Boolean} filtered indicates if the src is needed for svg - * @return {String} Source of an image - */ - getSrc: function(filtered) { - var element = filtered ? this._element : this._originalElement; - if (element) { - return fabric.isLikelyNode ? element._src : element.src; - } - else { - return this.src || ''; - } - }, - - /** - * Sets source of an image - * @param {String} src Source string (URL) - * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) - * @param {Object} [options] Options object - * @return {fabric.Image} thisArg - * @chainable - */ - setSrc: function(src, callback, options) { - fabric.util.loadImage(src, function(img) { - return this.setElement(img, callback, options); - }, this, options && options.crossOrigin); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of an instance - */ - toString: function() { - return '#'; - }, - - /** - * Applies filters assigned to this image (from "filters" array) - * @method applyFilters - * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated - * @param {Array} filters to be applied - * @param {fabric.Image} imgElement image to filter ( default to this._element ) - * @param {Boolean} forResizing - * @return {CanvasElement} canvasEl to be drawn immediately - * @chainable - */ - applyFilters: function(callback, filters, imgElement, forResizing) { - - filters = filters || this.filters; - imgElement = imgElement || this._originalElement; - - if (!imgElement) { - return; - } - - var replacement = fabric.util.createImage(), - retinaScaling = this.canvas ? this.canvas.getRetinaScaling() : fabric.devicePixelRatio, - minimumScale = this.minimumScaleTrigger / retinaScaling, - _this = this, scaleX, scaleY; - - if (filters.length === 0) { - this._element = imgElement; - callback && callback(this); - return imgElement; - } - - var canvasEl = fabric.util.createCanvasElement(); - canvasEl.width = imgElement.width; - canvasEl.height = imgElement.height; - canvasEl.getContext('2d').drawImage(imgElement, 0, 0, imgElement.width, imgElement.height); - - filters.forEach(function(filter) { - if (!filter) { - return; - } - if (forResizing) { - scaleX = _this.scaleX < minimumScale ? _this.scaleX : 1; - scaleY = _this.scaleY < minimumScale ? _this.scaleY : 1; - if (scaleX * retinaScaling < 1) { - scaleX *= retinaScaling; - } - if (scaleY * retinaScaling < 1) { - scaleY *= retinaScaling; - } - } - else { - scaleX = filter.scaleX; - scaleY = filter.scaleY; - } - filter.applyTo(canvasEl, scaleX, scaleY); - if (!forResizing && filter.type === 'Resize') { - _this.width *= filter.scaleX; - _this.height *= filter.scaleY; - } - }); - - /** @ignore */ - replacement.width = canvasEl.width; - replacement.height = canvasEl.height; - if (fabric.isLikelyNode) { - replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); - // onload doesn't fire in some node versions, so we invoke callback manually - _this._element = replacement; - !forResizing && (_this._filteredEl = replacement); - callback && callback(_this); - } - else { - replacement.onload = function() { - _this._element = replacement; - !forResizing && (_this._filteredEl = replacement); - callback && callback(_this); - replacement.onload = canvasEl = null; - }; - replacement.src = canvasEl.toDataURL('image/png'); - } - return canvasEl; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - _render: function(ctx, noTransform) { - var x, y, imageMargins = this._findMargins(), elementToDraw; - - x = (noTransform ? this.left : -this.width / 2); - y = (noTransform ? this.top : -this.height / 2); - - if (this.meetOrSlice === 'slice') { - ctx.beginPath(); - ctx.rect(x, y, this.width, this.height); - ctx.clip(); - } - - if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) { - this._lastScaleX = this.scaleX; - this._lastScaleY = this.scaleY; - elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true); - } - else { - elementToDraw = this._element; - } - elementToDraw && ctx.drawImage(elementToDraw, - x + imageMargins.marginX, - y + imageMargins.marginY, - imageMargins.width, - imageMargins.height - ); - - this._stroke(ctx); - this._renderStroke(ctx); - }, - - /** - * @private, needed to check if image needs resize - */ - _needsResize: function() { - return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY); - }, - - /** - * @private - */ - _findMargins: function() { - var width = this.width, height = this.height, scales, - scale, marginX = 0, marginY = 0; - - if (this.alignX !== 'none' || this.alignY !== 'none') { - scales = [this.width / this._element.width, this.height / this._element.height]; - scale = this.meetOrSlice === 'meet' - ? Math.min.apply(null, scales) : Math.max.apply(null, scales); - width = this._element.width * scale; - height = this._element.height * scale; - if (this.alignX === 'Mid') { - marginX = (this.width - width) / 2; - } - if (this.alignX === 'Max') { - marginX = this.width - width; - } - if (this.alignY === 'Mid') { - marginY = (this.height - height) / 2; - } - if (this.alignY === 'Max') { - marginY = this.height - height; - } - } - return { - width: width, - height: height, - marginX: marginX, - marginY: marginY - }; - }, - - /** - * @private - */ - _resetWidthHeight: function() { - var element = this.getElement(); - - this.set('width', element.width); - this.set('height', element.height); - }, - - /** - * The Image class's initialization method. This method is automatically - * called by the constructor. - * @private - * @param {HTMLImageElement|String} element The element representing the image - * @param {Object} [options] Options object - */ - _initElement: function(element, options, callback) { - this.setElement(fabric.util.getById(element), callback, options); - fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); - }, - - /** - * @private - * @param {Object} [options] Options object - */ - _initConfig: function(options) { - options || (options = { }); - this.setOptions(options); - this._setWidthHeight(options); - if (this._element && this.crossOrigin) { - this._element.crossOrigin = this.crossOrigin; - } - }, - - /** - * @private - * @param {Array} filters to be initialized - * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created - */ - _initFilters: function(filters, callback) { - if (filters && filters.length) { - fabric.util.enlivenObjects(filters, function(enlivenedObjects) { - callback && callback(enlivenedObjects); - }, 'fabric.Image.filters'); - } - else { - callback && callback(); - } - }, - - /** - * @private - * @param {Object} [options] Object with width/height properties - */ - _setWidthHeight: function(options) { - this.width = 'width' in options - ? options.width - : (this.getElement() - ? this.getElement().width || 0 - : 0); - - this.height = 'height' in options - ? options.height - : (this.getElement() - ? this.getElement().height || 0 - : 0); - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity of this instance - */ - complexity: function() { - return 1; - } - }); - - /** - * Default CSS class name for canvas - * @static - * @type String - * @default - */ - fabric.Image.CSS_CANVAS = 'canvas-img'; - - /** - * Alias for getSrc - * @static - */ - fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; - - /** - * Creates an instance of fabric.Image from its object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} callback Callback to invoke when an image instance is created - */ - fabric.Image.fromObject = function(object, callback) { - fabric.util.loadImage(object.src, function(img) { - fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) { - object.filters = filters || []; - fabric.Image.prototype._initFilters.call(object, object.resizeFilters, function(resizeFilters) { - object.resizeFilters = resizeFilters || []; - return new fabric.Image(img, object, callback); - }); - }); - }, null, object.crossOrigin); - }; - - /** - * Creates an instance of fabric.Image from an URL string - * @static - * @param {String} url URL to create an image from - * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) - * @param {Object} [imgOptions] Options object - */ - fabric.Image.fromURL = function(url, callback, imgOptions) { - fabric.util.loadImage(url, function(img) { - callback && callback(new fabric.Image(img, imgOptions)); - }, null, imgOptions && imgOptions.crossOrigin); - }; - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) - * @static - * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} - */ - fabric.Image.ATTRIBUTE_NAMES = - fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' ')); - - /** - * Returns {@link fabric.Image} instance from an SVG element - * @static - * @param {SVGElement} element Element to parse - * @param {Function} callback Callback to execute when fabric.Image object is created - * @param {Object} [options] Options object - * @return {fabric.Image} Instance of fabric.Image - */ - fabric.Image.fromElement = function(element, callback, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES), - preserveAR; - - if (parsedAttributes.preserveAspectRatio) { - preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio); - extend(parsedAttributes, preserveAR); - } - - fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, - extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); - }; - /* _FROM_SVG_END_ */ - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.async = true; - - /** - * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 - * @static - * @type Number - * @default - */ - fabric.Image.pngCompression = 1; - -})(typeof exports !== 'undefined' ? exports : this); - - -fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { - - /** - * @private - * @return {Number} angle value - */ - _getAngleValueForStraighten: function() { - var angle = this.getAngle() % 360; - if (angle > 0) { - return Math.round((angle - 1) / 90) * 90; - } - return Math.round(angle / 90) * 90; - }, - - /** - * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) - * @return {fabric.Object} thisArg - * @chainable - */ - straighten: function() { - this.setAngle(this._getAngleValueForStraighten()); - return this; - }, - - /** - * Same as {@link fabric.Object.prototype.straighten} but with animation - * @param {Object} callbacks Object with callback functions - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.Object} thisArg - * @chainable - */ - fxStraighten: function(callbacks) { - callbacks = callbacks || { }; - - var empty = function() { }, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - fabric.util.animate({ - startValue: this.get('angle'), - endValue: this._getAngleValueForStraighten(), - duration: this.FX_DURATION, - onChange: function(value) { - _this.setAngle(value); - onChange(); - }, - onComplete: function() { - _this.setCoords(); - onComplete(); - }, - onStart: function() { - _this.set('active', false); - } - }); - - return this; - } -}); - -fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { - - /** - * Straightens object, then rerenders canvas - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - straightenObject: function (object) { - object.straighten(); - this.renderAll(); - return this; - }, - - /** - * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - * @chainable - */ - fxStraightenObject: function (object) { - object.fxStraighten({ - onChange: this.renderAll.bind(this) - }); - return this; - } -}); - - -/** - * @namespace fabric.Image.filters - * @memberOf fabric.Image - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters} - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - */ -fabric.Image.filters = fabric.Image.filters || { }; - -/** - * Root filter class from which all filter classes inherit from - * @class fabric.Image.filters.BaseFilter - * @memberOf fabric.Image.filters - */ -fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'BaseFilter', - - /** - * Constructor - * @param {Object} [options] Options object - */ - initialize: function(options) { - if (options) { - this.setOptions(options); - } - }, - - /** - * Sets filter's properties from options - * @param {Object} [options] Options object - */ - setOptions: function(options) { - for (var prop in options) { - this[prop] = options[prop]; - } - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { type: this.type }; - }, - - /** - * Returns a JSON representation of an instance - * @return {Object} JSON - */ - toJSON: function() { - // delegate, not alias - return this.toObject(); - } -}); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Brightness filter class - * @class fabric.Image.filters.Brightness - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Brightness({ - * brightness: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Brightness', - - /** - * Constructor - * @memberOf fabric.Image.filters.Brightness.prototype - * @param {Object} [options] Options object - * @param {Number} [options.brightness=0] Value to brighten the image up (-255..255) - */ - initialize: function(options) { - options = options || { }; - this.brightness = options.brightness || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - brightness = this.brightness; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i] += brightness; - data[i + 1] += brightness; - data[i + 2] += brightness; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - brightness: this.brightness - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness - */ - fabric.Image.filters.Brightness.fromObject = function(object) { - return new fabric.Image.filters.Brightness(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Adapted from html5rocks article - * @class fabric.Image.filters.Convolute - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example Sharpen filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 0, -1, 0, - * -1, 5, -1, - * 0, -1, 0 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Blur filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9, - * 1/9, 1/9, 1/9 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter - * var filter = new fabric.Image.filters.Convolute({ - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Emboss filter with opaqueness - * var filter = new fabric.Image.filters.Convolute({ - * opaque: true, - * matrix: [ 1, 1, 1, - * 1, 0.7, -1, - * -1, -1, -1 ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Convolute', - - /** - * Constructor - * @memberOf fabric.Image.filters.Convolute.prototype - * @param {Object} [options] Options object - * @param {Boolean} [options.opaque=false] Opaque value (true/false) - * @param {Array} [options.matrix] Filter matrix - */ - initialize: function(options) { - options = options || { }; - - this.opaque = options.opaque; - this.matrix = options.matrix || [ - 0, 0, 0, - 0, 1, 0, - 0, 0, 0 - ]; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - - var weights = this.matrix, - context = canvasEl.getContext('2d'), - pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - - side = Math.round(Math.sqrt(weights.length)), - halfSide = Math.floor(side / 2), - src = pixels.data, - sw = pixels.width, - sh = pixels.height, - output = context.createImageData(sw, sh), - dst = output.data, - // go through the destination image pixels - alphaFac = this.opaque ? 1 : 0, - r, g, b, a, dstOff, - scx, scy, srcOff, wt; - - for (var y = 0; y < sh; y++) { - for (var x = 0; x < sw; x++) { - dstOff = (y * sw + x) * 4; - // calculate the weighed sum of the source image pixels that - // fall under the convolution matrix - r = 0; g = 0; b = 0; a = 0; - - for (var cy = 0; cy < side; cy++) { - for (var cx = 0; cx < side; cx++) { - scy = y + cy - halfSide; - scx = x + cx - halfSide; - - // eslint-disable-next-line max-depth - if (scy < 0 || scy > sh || scx < 0 || scx > sw) { - continue; - } - - srcOff = (scy * sw + scx) * 4; - wt = weights[cy * side + cx]; - - r += src[srcOff] * wt; - g += src[srcOff + 1] * wt; - b += src[srcOff + 2] * wt; - a += src[srcOff + 3] * wt; - } - } - dst[dstOff] = r; - dst[dstOff + 1] = g; - dst[dstOff + 2] = b; - dst[dstOff + 3] = a + alphaFac * (255 - a); - } - } - - context.putImageData(output, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - opaque: this.opaque, - matrix: this.matrix - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute - */ - fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * GradientTransparency filter class - * @class fabric.Image.filters.GradientTransparency - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.GradientTransparency({ - * threshold: 200 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - // eslint-disable-next-line max-len - filters.GradientTransparency = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'GradientTransparency', - - /** - * Constructor - * @memberOf fabric.Image.filters.GradientTransparency.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=100] Threshold value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 100; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - total = data.length; - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i + 3] = threshold + 255 * (total - i) / total; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency - */ - fabric.Image.filters.GradientTransparency.fromObject = function(object) { - return new fabric.Image.filters.GradientTransparency(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Grayscale image filter class - * @class fabric.Image.filters.Grayscale - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Grayscale(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Grayscale', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Grayscale.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - len = imageData.width * imageData.height * 4, - index = 0, - average; - - while (index < len) { - average = (data[index] + data[index + 1] + data[index + 2]) / 3; - data[index] = average; - data[index + 1] = average; - data[index + 2] = average; - index += 4; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale - */ - fabric.Image.filters.Grayscale.fromObject = function() { - return new fabric.Image.filters.Grayscale(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Invert filter class - * @class fabric.Image.filters.Invert - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Invert(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Invert', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Invert.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i; - - for (i = 0; i < iLen; i += 4) { - data[i] = 255 - data[i]; - data[i + 1] = 255 - data[i + 1]; - data[i + 2] = 255 - data[i + 2]; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert - */ - fabric.Image.filters.Invert.fromObject = function() { - return new fabric.Image.filters.Invert(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Mask filter class - * See http://resources.aleph-1.com/mask/ - * @class fabric.Image.filters.Mask - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition - */ - filters.Mask = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Mask', - - /** - * Constructor - * @memberOf fabric.Image.filters.Mask.prototype - * @param {Object} [options] Options object - * @param {fabric.Image} [options.mask] Mask image object - * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) - */ - initialize: function(options) { - options = options || { }; - - this.mask = options.mask; - this.channel = [0, 1, 2, 3].indexOf(options.channel) > -1 ? options.channel : 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - if (!this.mask) { - return; - } - - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - maskEl = this.mask.getElement(), - maskCanvasEl = fabric.util.createCanvasElement(), - channel = this.channel, - i, - iLen = imageData.width * imageData.height * 4; - - maskCanvasEl.width = canvasEl.width; - maskCanvasEl.height = canvasEl.height; - - maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height); - - var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.height), - maskData = maskImageData.data; - - for (i = 0; i < iLen; i += 4) { - data[i + 3] = maskData[i + channel]; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - mask: this.mask.toObject(), - channel: this.channel - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when a mask filter instance is created - */ - fabric.Image.filters.Mask.fromObject = function(object, callback) { - fabric.util.loadImage(object.mask.src, function(img) { - object.mask = new fabric.Image(img, object.mask); - callback && callback(new fabric.Image.filters.Mask(object)); - }); - }; - - /** - * Indicates that instances of this type are async - * @static - * @type Boolean - * @default - */ - fabric.Image.filters.Mask.async = true; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Noise filter class - * @class fabric.Image.filters.Noise - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Noise({ - * noise: 700 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Noise', - - /** - * Constructor - * @memberOf fabric.Image.filters.Noise.prototype - * @param {Object} [options] Options object - * @param {Number} [options.noise=0] Noise value - */ - initialize: function(options) { - options = options || { }; - this.noise = options.noise || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - noise = this.noise, rand; - - for (var i = 0, len = data.length; i < len; i += 4) { - - rand = (0.5 - Math.random()) * noise; - - data[i] += rand; - data[i + 1] += rand; - data[i + 2] += rand; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - noise: this.noise - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise - */ - fabric.Image.filters.Noise.fromObject = function(object) { - return new fabric.Image.filters.Noise(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Pixelate filter class - * @class fabric.Image.filters.Pixelate - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Pixelate({ - * blocksize: 8 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Pixelate', - - /** - * Constructor - * @memberOf fabric.Image.filters.Pixelate.prototype - * @param {Object} [options] Options object - * @param {Number} [options.blocksize=4] Blocksize for pixelate - */ - initialize: function(options) { - options = options || { }; - this.blocksize = options.blocksize || 4; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = imageData.height, - jLen = imageData.width, - index, i, j, r, g, b, a; - - for (i = 0; i < iLen; i += this.blocksize) { - for (j = 0; j < jLen; j += this.blocksize) { - - index = (i * 4) * jLen + (j * 4); - - r = data[index]; - g = data[index + 1]; - b = data[index + 2]; - a = data[index + 3]; - - /* - blocksize: 4 - - [1,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [x,x,x,x,1] - [1,1,1,1,1] - */ - - for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { - for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { - index = (_i * 4) * jLen + (_j * 4); - data[index] = r; - data[index + 1] = g; - data[index + 2] = b; - data[index + 3] = a; - } - } - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - blocksize: this.blocksize - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate - */ - fabric.Image.filters.Pixelate.fromObject = function(object) { - return new fabric.Image.filters.Pixelate(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Remove white filter class - * @class fabric.Image.filters.RemoveWhite - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.RemoveWhite({ - * threshold: 40, - * distance: 140 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.RemoveWhite = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'RemoveWhite', - - /** - * Constructor - * @memberOf fabric.Image.filters.RemoveWhite.prototype - * @param {Object} [options] Options object - * @param {Number} [options.threshold=30] Threshold value - * @param {Number} [options.distance=20] Distance value - */ - initialize: function(options) { - options = options || { }; - this.threshold = options.threshold || 30; - this.distance = options.distance || 20; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - threshold = this.threshold, - distance = this.distance, - limit = 255 - threshold, - abs = Math.abs, - r, g, b; - - for (var i = 0, len = data.length; i < len; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (r > limit && - g > limit && - b > limit && - abs(r - g) < distance && - abs(r - b) < distance && - abs(g - b) < distance - ) { - data[i + 3] = 0; - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - threshold: this.threshold, - distance: this.distance - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite - */ - fabric.Image.filters.RemoveWhite.fromObject = function(object) { - return new fabric.Image.filters.RemoveWhite(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Sepia filter class - * @class fabric.Image.filters.Sepia - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Sepia = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, avg; - - for (i = 0; i < iLen; i += 4) { - avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; - data[i] = avg + 100; - data[i + 1] = avg + 50; - data[i + 2] = avg + 255; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia - */ - fabric.Image.filters.Sepia.fromObject = function() { - return new fabric.Image.filters.Sepia(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Sepia2 filter class - * @class fabric.Image.filters.Sepia2 - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Sepia2(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Sepia2 = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Sepia2', - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Sepia.prototype - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, r, g, b; - - for (i = 0; i < iLen; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; - data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; - data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; - } - - context.putImageData(imageData, 0, 0); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 - */ - fabric.Image.filters.Sepia2.fromObject = function() { - return new fabric.Image.filters.Sepia2(); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Tint filter class - * Adapted from https://github.com/mezzoblue/PaintbrushJS - * @class fabric.Image.filters.Tint - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example Tint filter with hex color and opacity - * var filter = new fabric.Image.filters.Tint({ - * color: '#3513B0', - * opacity: 0.5 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Tint filter with rgba color - * var filter = new fabric.Image.filters.Tint({ - * color: 'rgba(53, 21, 176, 0.5)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Tint = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Tint', - - /** - * Constructor - * @memberOf fabric.Image.filters.Tint.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to tint the image with - * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - this.opacity = typeof options.opacity !== 'undefined' - ? options.opacity - : new fabric.Color(this.color).getAlpha(); - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - tintR, tintG, tintB, - r, g, b, alpha1, - source; - - source = new fabric.Color(this.color).getSource(); - - tintR = source[0] * this.opacity; - tintG = source[1] * this.opacity; - tintB = source[2] * this.opacity; - - alpha1 = 1 - this.opacity; - - for (i = 0; i < iLen; i += 4) { - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - // alpha compositing - data[i] = tintR + r * alpha1; - data[i + 1] = tintG + g * alpha1; - data[i + 2] = tintB + b * alpha1; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color, - opacity: this.opacity - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint - */ - fabric.Image.filters.Tint.fromObject = function(object) { - return new fabric.Image.filters.Tint(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Multiply filter class - * Adapted from http://www.laurenscorijn.com/articles/colormath-basics - * @class fabric.Image.filters.Multiply - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example Multiply filter with hex color - * var filter = new fabric.Image.filters.Multiply({ - * color: '#F0F' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - * @example Multiply filter with rgb color - * var filter = new fabric.Image.filters.Multiply({ - * color: 'rgb(53, 21, 176)' - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Multiply = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Multiply', - - /** - * Constructor - * @memberOf fabric.Image.filters.Multiply.prototype - * @param {Object} [options] Options object - * @param {String} [options.color=#000000] Color to multiply the image pixels with - */ - initialize: function(options) { - options = options || { }; - - this.color = options.color || '#000000'; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - iLen = data.length, i, - source; - - source = new fabric.Color(this.color).getSource(); - - for (i = 0; i < iLen; i += 4) { - data[i] *= source[0] / 255; - data[i + 1] *= source[1] / 255; - data[i + 2] *= source[2] / 255; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - color: this.color - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply - */ - fabric.Image.filters.Multiply.fromObject = function(object) { - return new fabric.Image.filters.Multiply(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - 'use strict'; - - var fabric = global.fabric, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Color Blend filter class - * @class fabric.Image.filter.Blend - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @example - * var filter = new fabric.Image.filters.Blend({ - * color: '#000', - * mode: 'multiply' - * }); - * - * var filter = new fabric.Image.filters.Blend({ - * image: fabricImageObject, - * mode: 'multiply', - * alpha: 0.5 - * }); - - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - - filters.Blend = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ { - type: 'Blend', - - initialize: function(options) { - options = options || {}; - this.color = options.color || '#000'; - this.image = options.image || false; - this.mode = options.mode || 'multiply'; - this.alpha = options.alpha || 1; - }, - - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - tr, tg, tb, - r, g, b, - _r, _g, _b, - source, - isImage = false; - - if (this.image) { - // Blend images - isImage = true; - - var _el = fabric.util.createCanvasElement(); - _el.width = this.image.width; - _el.height = this.image.height; - - var tmpCanvas = new fabric.StaticCanvas(_el); - tmpCanvas.add(this.image); - var context2 = tmpCanvas.getContext('2d'); - source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; - } - else { - // Blend color - source = new fabric.Color(this.color).getSource(); - - tr = source[0] * this.alpha; - tg = source[1] * this.alpha; - tb = source[2] * this.alpha; - } - - for (var i = 0, len = data.length; i < len; i += 4) { - - r = data[i]; - g = data[i + 1]; - b = data[i + 2]; - - if (isImage) { - tr = source[i] * this.alpha; - tg = source[i + 1] * this.alpha; - tb = source[i + 2] * this.alpha; - } - - switch (this.mode) { - case 'multiply': - data[i] = r * tr / 255; - data[i + 1] = g * tg / 255; - data[i + 2] = b * tb / 255; - break; - case 'screen': - data[i] = 1 - (1 - r) * (1 - tr); - data[i + 1] = 1 - (1 - g) * (1 - tg); - data[i + 2] = 1 - (1 - b) * (1 - tb); - break; - case 'add': - data[i] = Math.min(255, r + tr); - data[i + 1] = Math.min(255, g + tg); - data[i + 2] = Math.min(255, b + tb); - break; - case 'diff': - case 'difference': - data[i] = Math.abs(r - tr); - data[i + 1] = Math.abs(g - tg); - data[i + 2] = Math.abs(b - tb); - break; - case 'subtract': - _r = r - tr; - _g = g - tg; - _b = b - tb; - - data[i] = (_r < 0) ? 0 : _r; - data[i + 1] = (_g < 0) ? 0 : _g; - data[i + 2] = (_b < 0) ? 0 : _b; - break; - case 'darken': - data[i] = Math.min(r, tr); - data[i + 1] = Math.min(g, tg); - data[i + 2] = Math.min(b, tb); - break; - case 'lighten': - data[i] = Math.max(r, tr); - data[i + 1] = Math.max(g, tg); - data[i + 2] = Math.max(b, tb); - break; - } - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { - color: this.color, - image: this.image, - mode: this.mode, - alpha: this.alpha - }; - } - }); - - fabric.Image.filters.Blend.fromObject = function(object) { - return new fabric.Image.filters.Blend(object); - }; -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, - sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin, - ceil = Math.ceil, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Resize image filter class - * @class fabric.Image.filters.Resize - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Resize(); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Resize', - - /** - * Resize type - * @param {String} resizeType - * @default - */ - resizeType: 'hermite', - - /** - * Scale factor for resizing, x axis - * @param {Number} scaleX - * @default - */ - scaleX: 0, - - /** - * Scale factor for resizing, y axis - * @param {Number} scaleY - * @default - */ - scaleY: 0, - - /** - * LanczosLobes parameter for lanczos filter - * @param {Number} lanczosLobes - * @default - */ - lanczosLobes: 3, - - /** - * Applies filter to canvas element - * @memberOf fabric.Image.filters.Resize.prototype - * @param {Object} canvasEl Canvas element to apply filter to - * @param {Number} scaleX - * @param {Number} scaleY - */ - applyTo: function(canvasEl, scaleX, scaleY) { - if (scaleX === 1 && scaleY === 1) { - return; - } - - this.rcpScaleX = 1 / scaleX; - this.rcpScaleY = 1 / scaleY; - - var oW = canvasEl.width, oH = canvasEl.height, - dW = round(oW * scaleX), dH = round(oH * scaleY), - imageData; - - if (this.resizeType === 'sliceHack') { - imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'hermite') { - imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'bilinear') { - imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH); - } - if (this.resizeType === 'lanczos') { - imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH); - } - canvasEl.width = dW; - canvasEl.height = dH; - canvasEl.getContext('2d').putImageData(imageData, 0, 0); - }, - - /** - * Filter sliceByTwo - * @param {Object} canvasEl Canvas element to apply filter to - * @param {Number} oW Original Width - * @param {Number} oH Original Height - * @param {Number} dW Destination Width - * @param {Number} dH Destination Height - * @returns {ImageData} - */ - sliceByTwo: function(canvasEl, oW, oH, dW, dH) { - var context = canvasEl.getContext('2d'), imageData, - multW = 0.5, multH = 0.5, signW = 1, signH = 1, - doneW = false, doneH = false, stepW = oW, stepH = oH, - tmpCanvas = fabric.util.createCanvasElement(), - tmpCtx = tmpCanvas.getContext('2d'); - dW = floor(dW); - dH = floor(dH); - tmpCanvas.width = max(dW, oW); - tmpCanvas.height = max(dH, oH); - - if (dW > oW) { - multW = 2; - signW = -1; - } - if (dH > oH) { - multH = 2; - signH = -1; - } - imageData = context.getImageData(0, 0, oW, oH); - canvasEl.width = max(dW, oW); - canvasEl.height = max(dH, oH); - context.putImageData(imageData, 0, 0); - - while (!doneW || !doneH) { - oW = stepW; - oH = stepH; - if (dW * signW < floor(stepW * multW * signW)) { - stepW = floor(stepW * multW); - } - else { - stepW = dW; - doneW = true; - } - if (dH * signH < floor(stepH * multH * signH)) { - stepH = floor(stepH * multH); - } - else { - stepH = dH; - doneH = true; - } - imageData = context.getImageData(0, 0, oW, oH); - tmpCtx.putImageData(imageData, 0, 0); - context.clearRect(0, 0, stepW, stepH); - context.drawImage(tmpCanvas, 0, 0, oW, oH, 0, 0, stepW, stepH); - } - return context.getImageData(0, 0, dW, dH); - }, - - /** - * Filter lanczosResize - * @param {Object} canvasEl Canvas element to apply filter to - * @param {Number} oW Original Width - * @param {Number} oH Original Height - * @param {Number} dW Destination Width - * @param {Number} dH Destination Height - * @returns {ImageData} - */ - lanczosResize: function(canvasEl, oW, oH, dW, dH) { - - function lanczosCreate(lobes) { - return function(x) { - if (x > lobes) { - return 0; - } - x *= Math.PI; - if (abs(x) < 1e-16) { - return 1; - } - var xx = x / lobes; - return sin(x) * sin(xx) / x / xx; - }; - } - - function process(u) { - var v, i, weight, idx, a, red, green, - blue, alpha, fX, fY; - center.x = (u + 0.5) * ratioX; - icenter.x = floor(center.x); - for (v = 0; v < dH; v++) { - center.y = (v + 0.5) * ratioY; - icenter.y = floor(center.y); - a = 0; red = 0; green = 0; blue = 0; alpha = 0; - for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { - if (i < 0 || i >= oW) { - continue; - } - fX = floor(1000 * abs(i - center.x)); - if (!cacheLanc[fX]) { - cacheLanc[fX] = { }; - } - for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { - if (j < 0 || j >= oH) { - continue; - } - fY = floor(1000 * abs(j - center.y)); - if (!cacheLanc[fX][fY]) { - cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); - } - weight = cacheLanc[fX][fY]; - if (weight > 0) { - idx = (j * oW + i) * 4; - a += weight; - red += weight * srcData[idx]; - green += weight * srcData[idx + 1]; - blue += weight * srcData[idx + 2]; - alpha += weight * srcData[idx + 3]; - } - } - } - idx = (v * dW + u) * 4; - destData[idx] = red / a; - destData[idx + 1] = green / a; - destData[idx + 2] = blue / a; - destData[idx + 3] = alpha / a; - } - - if (++u < dW) { - return process(u); - } - else { - return destImg; - } - } - - var context = canvasEl.getContext('2d'), - srcImg = context.getImageData(0, 0, oW, oH), - destImg = context.getImageData(0, 0, dW, dH), - srcData = srcImg.data, destData = destImg.data, - lanczos = lanczosCreate(this.lanczosLobes), - ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, - rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, - range2X = ceil(ratioX * this.lanczosLobes / 2), - range2Y = ceil(ratioY * this.lanczosLobes / 2), - cacheLanc = { }, center = { }, icenter = { }; - - return process(0); - }, - - /** - * bilinearFiltering - * @param {Object} canvasEl Canvas element to apply filter to - * @param {Number} oW Original Width - * @param {Number} oH Original Height - * @param {Number} dW Destination Width - * @param {Number} dH Destination Height - * @returns {ImageData} - */ - bilinearFiltering: function(canvasEl, oW, oH, dW, dH) { - var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, - color, offset = 0, origPix, ratioX = this.rcpScaleX, - ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'), - w4 = 4 * (oW - 1), img = context.getImageData(0, 0, oW, oH), - pixels = img.data, destImage = context.getImageData(0, 0, dW, dH), - destPixels = destImage.data; - for (i = 0; i < dH; i++) { - for (j = 0; j < dW; j++) { - x = floor(ratioX * j); - y = floor(ratioY * i); - xDiff = ratioX * j - x; - yDiff = ratioY * i - y; - origPix = 4 * (y * oW + x); - - for (chnl = 0; chnl < 4; chnl++) { - a = pixels[origPix + chnl]; - b = pixels[origPix + 4 + chnl]; - c = pixels[origPix + w4 + chnl]; - d = pixels[origPix + w4 + 4 + chnl]; - color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + - c * yDiff * (1 - xDiff) + d * xDiff * yDiff; - destPixels[offset++] = color; - } - } - } - return destImage; - }, - - /** - * hermiteFastResize - * @param {Object} canvasEl Canvas element to apply filter to - * @param {Number} oW Original Width - * @param {Number} oH Original Height - * @param {Number} dW Destination Width - * @param {Number} dH Destination Height - * @returns {ImageData} - */ - hermiteFastResize: function(canvasEl, oW, oH, dW, dH) { - var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, - ratioWHalf = ceil(ratioW / 2), - ratioHHalf = ceil(ratioH / 2), - context = canvasEl.getContext('2d'), - img = context.getImageData(0, 0, oW, oH), data = img.data, - img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data; - for (var j = 0; j < dH; j++) { - for (var i = 0; i < dW; i++) { - var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, - gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; - for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { - var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, - centerX = (i + 0.5) * ratioW, w0 = dy * dy; - for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { - var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, - w = sqrt(w0 + dx * dx); - /* eslint-disable max-depth */ - if (w > 1 && w < -1) { - continue; - } - //hermite filter - weight = 2 * w * w * w - 3 * w * w + 1; - if (weight > 0) { - dx = 4 * (xx + yy * oW); - //alpha - gxA += weight * data[dx + 3]; - weightsAlpha += weight; - //colors - if (data[dx + 3] < 255) { - weight = weight * data[dx + 3] / 250; - } - gxR += weight * data[dx]; - gxG += weight * data[dx + 1]; - gxB += weight * data[dx + 2]; - weights += weight; - } - /* eslint-enable max-depth */ - } - } - data2[x2] = gxR / weights; - data2[x2 + 1] = gxG / weights; - data2[x2 + 2] = gxB / weights; - data2[x2 + 3] = gxA / weightsAlpha; - } - } - return img2; - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return { - type: this.type, - scaleX: this.scaleX, - scaleY: this.scaleY, - resizeType: this.resizeType, - lanczosLobes: this.lanczosLobes - }; - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize - */ - fabric.Image.filters.Resize.fromObject = function(object) { - return new fabric.Image.filters.Resize(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Color Matrix filter class - * @class fabric.Image.filters.ColorMatrix - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php} - * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl} - * @example Kodachrome filter - * var filter = new fabric.Image.filters.ColorMatrix({ - * matrix: [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ] - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'ColorMatrix', - - /** - * Constructor - * @memberOf fabric.Image.filters.ColorMatrix.prototype - * @param {Object} [options] Options object - * @param {Array} [options.matrix] Color Matrix to modify the image data with - */ - initialize: function( options ) { - options || ( options = {} ); - this.matrix = options.matrix || [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function( canvasEl ) { - var context = canvasEl.getContext( '2d' ), - imageData = context.getImageData( 0, 0, canvasEl.width, canvasEl.height ), - data = imageData.data, - iLen = data.length, - i, - r, - g, - b, - a, - m = this.matrix; - - for ( i = 0; i < iLen; i += 4 ) { - r = data[ i ]; - g = data[ i + 1 ]; - b = data[ i + 2 ]; - a = data[ i + 3 ]; - - data[ i ] = r * m[ 0 ] + g * m[ 1 ] + b * m[ 2 ] + a * m[ 3 ] + m[ 4 ]; - data[ i + 1 ] = r * m[ 5 ] + g * m[ 6 ] + b * m[ 7 ] + a * m[ 8 ] + m[ 9 ]; - data[ i + 2 ] = r * m[ 10 ] + g * m[ 11 ] + b * m[ 12 ] + a * m[ 13 ] + m[ 14 ]; - data[ i + 3 ] = r * m[ 15 ] + g * m[ 16 ] + b * m[ 17 ] + a * m[ 18 ] + m[ 19 ]; - } - - context.putImageData( imageData, 0, 0 ); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - type: this.type, - matrix: this.matrix - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix - */ - fabric.Image.filters.ColorMatrix.fromObject = function( object ) { - return new fabric.Image.filters.ColorMatrix( object ); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Contrast filter class - * @class fabric.Image.filters.Contrast - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Contrast({ - * contrast: 40 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Contrast', - - /** - * Constructor - * @memberOf fabric.Image.filters.Contrast.prototype - * @param {Object} [options] Options object - * @param {Number} [options.contrast=0] Value to contrast the image up (-255...255) - */ - initialize: function(options) { - options = options || { }; - this.contrast = options.contrast || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - contrastF = 259 * (this.contrast + 255) / (255 * (259 - this.contrast)); - - for (var i = 0, len = data.length; i < len; i += 4) { - data[i] = contrastF * (data[i] - 128) + 128; - data[i + 1] = contrastF * (data[i + 1] - 128) + 128; - data[i + 2] = contrastF * (data[i + 2] - 128) + 128; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - contrast: this.contrast - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast - */ - fabric.Image.filters.Contrast.fromObject = function(object) { - return new fabric.Image.filters.Contrast(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - extend = fabric.util.object.extend, - filters = fabric.Image.filters, - createClass = fabric.util.createClass; - - /** - * Saturate filter class - * @class fabric.Image.filters.Saturate - * @memberOf fabric.Image.filters - * @extends fabric.Image.filters.BaseFilter - * @see {@link fabric.Image.filters.Saturate#initialize} for constructor definition - * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} - * @example - * var filter = new fabric.Image.filters.Saturate({ - * saturate: 100 - * }); - * object.filters.push(filter); - * object.applyFilters(canvas.renderAll.bind(canvas)); - */ - filters.Saturate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturate.prototype */ { - - /** - * Filter type - * @param {String} type - * @default - */ - type: 'Saturate', - - /** - * Constructor - * @memberOf fabric.Image.filters.Saturate.prototype - * @param {Object} [options] Options object - * @param {Number} [options.saturate=0] Value to saturate the image (-100...100) - */ - initialize: function(options) { - options = options || { }; - this.saturate = options.saturate || 0; - }, - - /** - * Applies filter to canvas element - * @param {Object} canvasEl Canvas element to apply filter to - */ - applyTo: function(canvasEl) { - var context = canvasEl.getContext('2d'), - imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), - data = imageData.data, - max, adjust = -this.saturate * 0.01; - - for (var i = 0, len = data.length; i < len; i += 4) { - max = Math.max(data[i], data[i + 1], data[i + 2]); - data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; - data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; - data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; - } - - context.putImageData(imageData, 0, 0); - }, - - /** - * Returns object representation of an instance - * @return {Object} Object representation of an instance - */ - toObject: function() { - return extend(this.callSuper('toObject'), { - saturate: this.saturate - }); - } - }); - - /** - * Returns filter instance from an object representation - * @static - * @param {Object} object Object to create an instance from - * @return {fabric.Image.filters.Saturate} Instance of fabric.Image.filters.Saturate - */ - fabric.Image.filters.Saturate.fromObject = function(object) { - return new fabric.Image.filters.Saturate(object); - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = { }), - clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed, - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - MIN_TEXT_WIDTH = 2; - - if (fabric.Text) { - fabric.warn('fabric.Text is already defined'); - return; - } - - var stateProperties = fabric.Object.prototype.stateProperties.concat(); - stateProperties.push( - 'fontFamily', - 'fontWeight', - 'fontSize', - 'text', - 'textDecoration', - 'textAlign', - 'fontStyle', - 'lineHeight', - 'textBackgroundColor' - ); - - /** - * Text class - * @class fabric.Text - * @extends fabric.Object - * @return {fabric.Text} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} - * @see {@link fabric.Text#initialize} for constructor definition - */ - fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { - - /** - * Properties which when set cause object to change dimensions - * @type Object - * @private - */ - _dimensionAffectingProps: { - fontSize: true, - fontWeight: true, - fontFamily: true, - fontStyle: true, - lineHeight: true, - text: true, - charSpacing: true, - textAlign: true, - strokeWidth: false, - }, - - /** - * @private - */ - _reNewline: /\r?\n/, - - /** - * Use this regular expression to filter for whitespace that is not a new line. - * Mostly used when text is 'justify' aligned. - * @private - */ - _reSpacesAndTabs: /[ \t\r]+/g, - - /** - * Retrieves object's fontSize - * @method getFontSize - * @memberOf fabric.Text.prototype - * @return {String} Font size (in pixels) - */ - - /** - * Sets object's fontSize - * Does not update the object .width and .height, - * call ._initDimensions() to update the values. - * @method setFontSize - * @memberOf fabric.Text.prototype - * @param {Number} fontSize Font size (in pixels) - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontWeight - * @method getFontWeight - * @memberOf fabric.Text.prototype - * @return {(String|Number)} Font weight - */ - - /** - * Sets object's fontWeight - * Does not update the object .width and .height, - * call ._initDimensions() to update the values. - * @method setFontWeight - * @memberOf fabric.Text.prototype - * @param {(Number|String)} fontWeight Font weight - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontFamily - * @method getFontFamily - * @memberOf fabric.Text.prototype - * @return {String} Font family - */ - - /** - * Sets object's fontFamily - * Does not update the object .width and .height, - * call ._initDimensions() to update the values. - * @method setFontFamily - * @memberOf fabric.Text.prototype - * @param {String} fontFamily Font family - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's text - * @method getText - * @memberOf fabric.Text.prototype - * @return {String} text - */ - - /** - * Sets object's text - * Does not update the object .width and .height, - * call ._initDimensions() to update the values. - * @method setText - * @memberOf fabric.Text.prototype - * @param {String} text Text - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textDecoration - * @method getTextDecoration - * @memberOf fabric.Text.prototype - * @return {String} Text decoration - */ - - /** - * Sets object's textDecoration - * @method setTextDecoration - * @memberOf fabric.Text.prototype - * @param {String} textDecoration Text decoration - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's fontStyle - * @method getFontStyle - * @memberOf fabric.Text.prototype - * @return {String} Font style - */ - - /** - * Sets object's fontStyle - * Does not update the object .width and .height, - * call ._initDimensions() to update the values. - * @method setFontStyle - * @memberOf fabric.Text.prototype - * @param {String} fontStyle Font style - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's lineHeight - * @method getLineHeight - * @memberOf fabric.Text.prototype - * @return {Number} Line height - */ - - /** - * Sets object's lineHeight - * @method setLineHeight - * @memberOf fabric.Text.prototype - * @param {Number} lineHeight Line height - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textAlign - * @method getTextAlign - * @memberOf fabric.Text.prototype - * @return {String} Text alignment - */ - - /** - * Sets object's textAlign - * @method setTextAlign - * @memberOf fabric.Text.prototype - * @param {String} textAlign Text alignment - * @return {fabric.Text} - * @chainable - */ - - /** - * Retrieves object's textBackgroundColor - * @method getTextBackgroundColor - * @memberOf fabric.Text.prototype - * @return {String} Text background color - */ - - /** - * Sets object's textBackgroundColor - * @method setTextBackgroundColor - * @memberOf fabric.Text.prototype - * @param {String} textBackgroundColor Text background color - * @return {fabric.Text} - * @chainable - */ - - /** - * Type of an object - * @type String - * @default - */ - type: 'text', - - /** - * Font size (in pixels) - * @type Number - * @default - */ - fontSize: 40, - - /** - * Font weight (e.g. bold, normal, 400, 600, 800) - * @type {(Number|String)} - * @default - */ - fontWeight: 'normal', - - /** - * Font family - * @type String - * @default - */ - fontFamily: 'Times New Roman', - - /** - * Text decoration Possible values: "", "underline", "overline" or "line-through". - * @type String - * @default - */ - textDecoration: '', - - /** - * Text alignment. Possible values: "left", "center", "right" or "justify". - * @type String - * @default - */ - textAlign: 'left', - - /** - * Font style . Possible values: "", "normal", "italic" or "oblique". - * @type String - * @default - */ - fontStyle: '', - - /** - * Line height - * @type Number - * @default - */ - lineHeight: 1.16, - - /** - * Background color of text lines - * @type String - * @default - */ - textBackgroundColor: '', - - /** - * List of properties to consider when checking if - * state of an object is changed ({@link fabric.Object#hasStateChanged}) - * as well as for history (undo/redo) purposes - * @type Array - */ - stateProperties: stateProperties, - - /** - * When defined, an object is rendered via stroke and this property specifies its color. - * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 - * @type String - * @default - */ - stroke: null, - - /** - * Shadow object representing shadow of this shape. - * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 - * @type fabric.Shadow - * @default - */ - shadow: null, - - /** - * @private - */ - _fontSizeFraction: 0.25, - - /** - * Text Line proportion to font Size (in pixels) - * @type Number - * @default - */ - _fontSizeMult: 1.13, - - /** - * additional space between characters - * expressed in thousands of em unit - * @type Number - * @default - */ - charSpacing: 0, - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Text} thisArg - */ - initialize: function(text, options) { - options = options || { }; - this.text = text; - this.__skipDimension = true; - this.setOptions(options); - this.__skipDimension = false; - this._initDimensions(); - }, - - /** - * Initialize text dimensions. Render all text on given context - * or on a offscreen canvas to get the text width with measureText. - * Updates this.width and this.height with the proper values. - * Does not return dimensions. - * @param {CanvasRenderingContext2D} [ctx] Context to render on - * @private - */ - _initDimensions: function(ctx) { - if (this.__skipDimension) { - return; - } - if (!ctx) { - ctx = fabric.util.createCanvasElement().getContext('2d'); - this._setTextStyles(ctx); - } - this._textLines = this._splitTextIntoLines(); - this._clearCache(); - this.width = this._getTextWidth(ctx) || this.cursorWidth || MIN_TEXT_WIDTH; - this.height = this._getTextHeight(ctx); - }, - - /** - * Returns string representation of an instance - * @return {String} String representation of text object - */ - toString: function() { - return '#'; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - this.clipTo && fabric.util.clipContext(this, ctx); - this._setOpacity(ctx); - this._setShadow(ctx); - this._setupCompositeOperation(ctx); - this._renderTextBackground(ctx); - this._setStrokeStyles(ctx); - this._setFillStyles(ctx); - this._renderText(ctx); - this._renderTextDecoration(ctx); - this.clipTo && ctx.restore(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderText: function(ctx) { - this._renderTextFill(ctx); - this._renderTextStroke(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _setTextStyles: function(ctx) { - ctx.textBaseline = 'alphabetic'; - ctx.font = this._getFontDeclaration(); - }, - - /** - * @private - * @return {Number} Height of fabric.Text object - */ - _getTextHeight: function() { - return this._getHeightOfSingleLine() + (this._textLines.length - 1) * this._getHeightOfLine(); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @return {Number} Maximum width of fabric.Text object - */ - _getTextWidth: function(ctx) { - var maxWidth = this._getLineWidth(ctx, 0); - - for (var i = 1, len = this._textLines.length; i < len; i++) { - var currentLineWidth = this._getLineWidth(ctx, i); - if (currentLineWidth > maxWidth) { - maxWidth = currentLineWidth; - } - } - return maxWidth; - }, - - /* - * Calculate object dimensions from its properties - * @override - * @private - */ - _getNonTransformedDimensions: function() { - return { x: this.width, y: this.height }; - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} chars Chars to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - */ - _renderChars: function(method, ctx, chars, left, top) { - // remove Text word from method var - var shortM = method.slice(0, -4), char, width; - if (this[shortM].toLive) { - var offsetX = -this.width / 2 + this[shortM].offsetX || 0, - offsetY = -this.height / 2 + this[shortM].offsetY || 0; - ctx.save(); - ctx.translate(offsetX, offsetY); - left -= offsetX; - top -= offsetY; - } - if (this.charSpacing !== 0) { - var additionalSpace = this._getWidthOfCharSpacing(); - chars = chars.split(''); - for (var i = 0, len = chars.length; i < len; i++) { - char = chars[i]; - width = ctx.measureText(char).width + additionalSpace; - ctx[method](char, left, top); - left += width > 0 ? width : 0; - } - } - else { - ctx[method](chars, left, top); - } - this[shortM].toLive && ctx.restore(); - }, - - /** - * @private - * @param {String} method Method name ("fillText" or "strokeText") - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Text to render - * @param {Number} left Left position of text - * @param {Number} top Top position of text - * @param {Number} lineIndex Index of a line in a text - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // lift the line by quarter of fontSize - top -= this.fontSize * this._fontSizeFraction; - - // short-circuit - var lineWidth = this._getLineWidth(ctx, lineIndex); - if (this.textAlign !== 'justify' || this.width < lineWidth) { - this._renderChars(method, ctx, line, left, top, lineIndex); - return; - } - - // stretch the line - var words = line.split(/\s+/), - charOffset = 0, - wordsWidth = this._getWidthOfWords(ctx, words.join(' '), lineIndex, 0), - widthDiff = this.width - wordsWidth, - numSpaces = words.length - 1, - spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0, - leftOffset = 0, word; - - for (var i = 0, len = words.length; i < len; i++) { - while (line[charOffset] === ' ' && charOffset < line.length) { - charOffset++; - } - word = words[i]; - this._renderChars(method, ctx, word, left + leftOffset, top, lineIndex, charOffset); - leftOffset += this._getWidthOfWords(ctx, word, lineIndex, charOffset) + spaceWidth; - charOffset += word.length; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} word - */ - _getWidthOfWords: function (ctx, word) { - var width = ctx.measureText(word).width, charCount, additionalSpace; - if (this.charSpacing !== 0) { - charCount = word.split('').length; - additionalSpace = charCount * this._getWidthOfCharSpacing(); - width += additionalSpace; - } - return width > 0 ? width : 0; - }, - - /** - * @private - * @return {Number} Left offset - */ - _getLeftOffset: function() { - return -this.width / 2; - }, - - /** - * @private - * @return {Number} Top offset - */ - _getTopOffset: function() { - return -this.height / 2; - }, - - /** - * Returns true because text has no style - */ - isEmptyStyles: function() { - return true; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} method Method name ("fillText" or "strokeText") - */ - _renderTextCommon: function(ctx, method) { - - var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset(); - - for (var i = 0, len = this._textLines.length; i < len; i++) { - var heightOfLine = this._getHeightOfLine(ctx, i), - maxHeight = heightOfLine / this.lineHeight, - lineWidth = this._getLineWidth(ctx, i), - leftOffset = this._getLineLeftOffset(lineWidth); - this._renderTextLine( - method, - ctx, - this._textLines[i], - left + leftOffset, - top + lineHeights + maxHeight, - i - ); - lineHeights += heightOfLine; - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextFill: function(ctx) { - if (!this.fill && this.isEmptyStyles()) { - return; - } - - this._renderTextCommon(ctx, 'fillText'); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextStroke: function(ctx) { - if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { - return; - } - - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - - ctx.save(); - this._setLineDash(ctx, this.strokedashArray); - ctx.beginPath(); - this._renderTextCommon(ctx, 'strokeText'); - ctx.closePath(); - ctx.restore(); - }, - - /** - * @private - * @return {Number} height of line - */ - _getHeightOfLine: function() { - return this._getHeightOfSingleLine() * this.lineHeight; - }, - - /** - * @private - * @return {Number} height of line without lineHeight - */ - _getHeightOfSingleLine: function() { - return this.fontSize * this._fontSizeMult; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextBackground: function(ctx) { - this._renderBackground(ctx); - this._renderTextLinesBackground(ctx); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextLinesBackground: function(ctx) { - if (!this.textBackgroundColor) { - return; - } - var lineTopOffset = 0, heightOfLine, - lineWidth, lineLeftOffset; - - ctx.fillStyle = this.textBackgroundColor; - for (var i = 0, len = this._textLines.length; i < len; i++) { - heightOfLine = this._getHeightOfLine(ctx, i); - lineWidth = this._getLineWidth(ctx, i); - if (lineWidth > 0) { - lineLeftOffset = this._getLineLeftOffset(lineWidth); - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + lineTopOffset, - lineWidth, - heightOfLine / this.lineHeight - ); - } - lineTopOffset += heightOfLine; - } - // if there is text background color no - // other shadows should be casted - this._removeShadow(ctx); - }, - - /** - * @private - * @param {Number} lineWidth Width of text line - * @return {Number} Line left offset - */ - _getLineLeftOffset: function(lineWidth) { - if (this.textAlign === 'center') { - return (this.width - lineWidth) / 2; - } - if (this.textAlign === 'right') { - return this.width - lineWidth; - } - return 0; - }, - - /** - * @private - */ - _clearCache: function() { - this.__lineWidths = []; - this.__lineHeights = []; - }, - - /** - * @private - */ - _shouldClearCache: function() { - var shouldClear = false; - if (this._forceClearCache) { - this._forceClearCache = false; - return true; - } - for (var prop in this._dimensionAffectingProps) { - if (this['__' + prop] !== this[prop]) { - this['__' + prop] = this[prop]; - shouldClear = true; - } - } - return shouldClear; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex line number - * @return {Number} Line width - */ - _getLineWidth: function(ctx, lineIndex) { - if (this.__lineWidths[lineIndex]) { - return this.__lineWidths[lineIndex] === -1 ? this.width : this.__lineWidths[lineIndex]; - } - - var width, wordCount, line = this._textLines[lineIndex]; - - if (line === '') { - width = 0; - } - else { - width = this._measureLine(ctx, lineIndex); - } - this.__lineWidths[lineIndex] = width; - - if (width && this.textAlign === 'justify') { - wordCount = line.split(/\s+/); - if (wordCount.length > 1) { - this.__lineWidths[lineIndex] = -1; - } - } - return width; - }, - - _getWidthOfCharSpacing: function() { - if (this.charSpacing !== 0) { - return this.fontSize * this.charSpacing / 1000; - } - return 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex line number - * @return {Number} Line width - */ - _measureLine: function(ctx, lineIndex) { - var line = this._textLines[lineIndex], - width = ctx.measureText(line).width, - additionalSpace = 0, charCount, finalWidth; - if (this.charSpacing !== 0) { - charCount = line.split('').length; - additionalSpace = (charCount - 1) * this._getWidthOfCharSpacing(); - } - finalWidth = width + additionalSpace; - return finalWidth > 0 ? finalWidth : 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextDecoration: function(ctx) { - if (!this.textDecoration) { - return; - } - var halfOfVerticalBox = this.height / 2, - _this = this, offsets = []; - - /** @ignore */ - function renderLinesAtOffset(offsets) { - var i, lineHeight = 0, len, j, oLen, lineWidth, - lineLeftOffset, heightOfLine; - - for (i = 0, len = _this._textLines.length; i < len; i++) { - - lineWidth = _this._getLineWidth(ctx, i); - lineLeftOffset = _this._getLineLeftOffset(lineWidth); - heightOfLine = _this._getHeightOfLine(ctx, i); - - for (j = 0, oLen = offsets.length; j < oLen; j++) { - ctx.fillRect( - _this._getLeftOffset() + lineLeftOffset, - lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox, - lineWidth, - _this.fontSize / 15); - } - lineHeight += heightOfLine; - } - } - - if (this.textDecoration.indexOf('underline') > -1) { - offsets.push(0.85); // 1 - 3/16 - } - if (this.textDecoration.indexOf('line-through') > -1) { - offsets.push(0.43); - } - if (this.textDecoration.indexOf('overline') > -1) { - offsets.push(-0.12); - } - if (offsets.length > 0) { - renderLinesAtOffset(offsets); - } - }, - - /** - * return font declaration string for canvas context - * @returns {String} font declaration formatted for canvas context. - */ - _getFontDeclaration: function() { - return [ - // node-canvas needs "weight style", while browsers need "style weight" - (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), - (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), - this.fontSize + 'px', - (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) - ].join(' '); - }, - - /** - * Renders text instance on a specified context - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - render: function(ctx, noTransform) { - // do not render if object is not visible - if (!this.visible) { - return; - } - - ctx.save(); - this._setTextStyles(ctx); - - if (this._shouldClearCache()) { - this._initDimensions(ctx); - } - this.drawSelectionBackground(ctx); - if (!noTransform) { - this.transform(ctx); - } - if (this.transformMatrix) { - ctx.transform.apply(ctx, this.transformMatrix); - } - if (this.group && this.group.type === 'path-group') { - ctx.translate(this.left, this.top); - } - this._render(ctx); - ctx.restore(); - }, - - /** - * Returns the text as an array of lines. - * @returns {Array} Lines in the text - */ - _splitTextIntoLines: function() { - return this.text.split(this._reNewline); - }, - - /** - * Returns object representation of an instance - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} Object representation of an instance - */ - toObject: function(propertiesToInclude) { - var additionalProperties = [ - 'text', - 'fontSize', - 'fontWeight', - 'fontFamily', - 'fontStyle', - 'lineHeight', - 'textDecoration', - 'textAlign', - 'textBackgroundColor', - 'charSpacing' - ].concat(propertiesToInclude); - return this.callSuper('toObject', additionalProperties); - }, - - /* _TO_SVG_START_ */ - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function(reviver) { - if (!this.ctx) { - this.ctx = fabric.util.createCanvasElement().getContext('2d'); - } - var markup = this._createBaseSVGMarkup(), - offsets = this._getSVGLeftTopOffsets(this.ctx), - textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - this._wrapSVGTextAndBg(markup, textAndBg); - - return reviver ? reviver(markup.join('')) : markup.join(''); - }, - - /** - * @private - */ - _getSVGLeftTopOffsets: function(ctx) { - var lineTop = this._getHeightOfLine(ctx, 0), - textLeft = -this.width / 2, - textTop = 0; - - return { - textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), - textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0), - lineTop: lineTop - }; - }, - - /** - * @private - */ - _wrapSVGTextAndBg: function(markup, textAndBg) { - var noShadow = true, filter = this.getSvgFilter(), - style = filter === '' ? '' : ' style="' + filter + '"'; - - markup.push( - '\t\n', - textAndBg.textBgRects.join(''), - '\t\t\n', - textAndBg.textSpans.join(''), - '\t\t\n', - '\t\n' - ); - }, - - /** - * @private - * @param {Number} textTopOffset Text top offset - * @param {Number} textLeftOffset Text left offset - * @return {Object} - */ - _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { - var textSpans = [], - textBgRects = [], - height = 0; - // bounding-box background - this._setSVGBg(textBgRects); - - // text and text-background - for (var i = 0, len = this._textLines.length; i < len; i++) { - if (this.textBackgroundColor) { - this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height); - } - this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects); - height += this._getHeightOfLine(this.ctx, i); - } - - return { - textSpans: textSpans, - textBgRects: textBgRects - }; - }, - - _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) { - var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction) - - textTopOffset + height - this.height / 2; - if (this.textAlign === 'justify') { - // i call from here to do not intefere with IText - this._setSVGTextLineJustifed(i, textSpans, yPos, textLeftOffset); - return; - } - textSpans.push( - '\t\t\t elements since setting opacity - // on containing one doesn't work in Illustrator - this._getFillAttributes(this.fill), '>', - fabric.util.string.escapeXml(this._textLines[i]), - '\n' - ); - }, - - _setSVGTextLineJustifed: function(i, textSpans, yPos, textLeftOffset) { - var ctx = fabric.util.createCanvasElement().getContext('2d'); - - this._setTextStyles(ctx); - - var line = this._textLines[i], - words = line.split(/\s+/), - wordsWidth = this._getWidthOfWords(ctx, words.join('')), - widthDiff = this.width - wordsWidth, - numSpaces = words.length - 1, - spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0, - word, attributes = this._getFillAttributes(this.fill), - len; - - textLeftOffset += this._getLineLeftOffset(this._getLineWidth(ctx, i)); - - for (i = 0, len = words.length; i < len; i++) { - word = words[i]; - textSpans.push( - '\t\t\t elements since setting opacity - // on containing one doesn't work in Illustrator - attributes, '>', - fabric.util.string.escapeXml(word), - '\n' - ); - textLeftOffset += this._getWidthOfWords(ctx, word) + spaceWidth; - } - }, - - _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) { - textBgRects.push( - '\t\t\n'); - }, - - _setSVGBg: function(textBgRects) { - if (this.backgroundColor) { - textBgRects.push( - '\t\t\n'); - } - }, - - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {*} value - * @return {String} - */ - _getFillAttributes: function(value) { - var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; - }, - /* _TO_SVG_END_ */ - - /** - * Sets specified property to a specified value - * @param {String} key - * @param {*} value - * @return {fabric.Text} thisArg - * @chainable - */ - _set: function(key, value) { - this.callSuper('_set', key, value); - - if (key in this._dimensionAffectingProps) { - this._initDimensions(); - this.setCoords(); - } - }, - - /** - * Returns complexity of an instance - * @return {Number} complexity - */ - complexity: function() { - return 1; - } - }); - - /* _FROM_SVG_START_ */ - /** - * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) - * @static - * @memberOf fabric.Text - * @see: http://www.w3.org/TR/SVG/text.html#TextElement - */ - fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); - - /** - * Default SVG font size - * @static - * @memberOf fabric.Text - */ - fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; - - /** - * Returns fabric.Text instance from an SVG element (not yet implemented) - * @static - * @memberOf fabric.Text - * @param {SVGElement} element Element to parse - * @param {Object} [options] Options object - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromElement = function(element, options) { - if (!element) { - return null; - } - - var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); - options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); - - options.top = options.top || 0; - options.left = options.left || 0; - if ('dx' in parsedAttributes) { - options.left += parsedAttributes.dx; - } - if ('dy' in parsedAttributes) { - options.top += parsedAttributes.dy; - } - if (!('fontSize' in options)) { - options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; - } - - if (!options.originX) { - options.originX = 'left'; - } - - var textContent = ''; - - // The XML is not properly parsed in IE9 so a workaround to get - // textContent is through firstChild.data. Another workaround would be - // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) - if (!('textContent' in element)) { - if ('firstChild' in element && element.firstChild !== null) { - if ('data' in element.firstChild && element.firstChild.data !== null) { - textContent = element.firstChild.data; - } - } - } - else { - textContent = element.textContent; - } - - textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '); - - var text = new fabric.Text(textContent, options), - textHeightScaleFactor = text.getHeight() / text.height, - lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height, - scaledDiff = lineHeightDiff * textHeightScaleFactor, - textHeight = text.getHeight() + scaledDiff, - offX = 0; - /* - Adjust positioning: - x/y attributes in SVG correspond to the bottom-left corner of text bounding box - top/left properties in Fabric correspond to center point of text bounding box - */ - if (text.originX === 'left') { - offX = text.getWidth() / 2; - } - if (text.originX === 'right') { - offX = -text.getWidth() / 2; - } - text.set({ - left: text.getLeft() + offX, - top: text.getTop() - textHeight / 2 + text.fontSize * (0.18 + text._fontSizeFraction) / text.lineHeight /* 0.3 is the old lineHeight */ - }); - - return text; - }; - /* _FROM_SVG_END_ */ - - /** - * Returns fabric.Text instance from an object representation - * @static - * @memberOf fabric.Text - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created - * @return {fabric.Text} Instance of fabric.Text - */ - fabric.Text.fromObject = function(object, callback) { - var text = new fabric.Text(object.text, clone(object)); - callback && callback(text); - return text; - }; - - fabric.util.createAccessors(fabric.Text); - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - var clone = fabric.util.object.clone; - - /** - * IText class (introduced in v1.4) Events are also fired with "text:" - * prefix when observing canvas. - * @class fabric.IText - * @extends fabric.Text - * @mixes fabric.Observable - * - * @fires changed - * @fires selection:changed - * @fires editing:entered - * @fires editing:exited - * - * @return {fabric.IText} thisArg - * @see {@link fabric.IText#initialize} for constructor definition - * - *

Supported key combinations:

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

Supported mouse/touch combination

- *
-   *   Position cursor:                click/touch
-   *   Create selection:               click/touch & drag
-   *   Create selection:               click & shift + click
-   *   Select word:                    double click
-   *   Select line:                    triple click
-   * 
- */ - fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { - - /** - * Type of an object - * @type String - * @default - */ - type: 'i-text', - - /** - * Index where text selection starts (or where cursor is when there is no selection) - * @type Number - * @default - */ - selectionStart: 0, - - /** - * Index where text selection ends - * @type Number - * @default - */ - selectionEnd: 0, - - /** - * Color of text selection - * @type String - * @default - */ - selectionColor: 'rgba(17,119,255,0.3)', - - /** - * Indicates whether text is in editing mode - * @type Boolean - * @default - */ - isEditing: false, - - /** - * Indicates whether a text can be edited - * @type Boolean - * @default - */ - editable: true, - - /** - * Border color of text object while it's in editing mode - * @type String - * @default - */ - editingBorderColor: 'rgba(102,153,255,0.25)', - - /** - * Width of cursor (in px) - * @type Number - * @default - */ - cursorWidth: 2, - - /** - * Color of default cursor (when not overwritten by character style) - * @type String - * @default - */ - cursorColor: '#333', - - /** - * Delay between cursor blink (in ms) - * @type Number - * @default - */ - cursorDelay: 1000, - - /** - * Duration of cursor fadein (in ms) - * @type Number - * @default - */ - cursorDuration: 600, - - /** - * Object containing character styles - * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) - * @type Object - * @default - */ - styles: null, - - /** - * Indicates whether internal text char widths can be cached - * @type Boolean - * @default - */ - caching: true, - - /** - * @private - */ - _reSpace: /\s|\n/, - - /** - * @private - */ - _currentCursorOpacity: 0, - - /** - * @private - */ - _selectionDirection: null, - - /** - * @private - */ - _abortCursorAnimation: false, - - /** - * @private - */ - __widthOfSpace: [], - - /** - * Constructor - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.IText} thisArg - */ - initialize: function(text, options) { - this.styles = options ? (options.styles || { }) : { }; - this.callSuper('initialize', text, options); - this.initBehavior(); - }, - - /** - * @private - */ - _clearCache: function() { - this.callSuper('_clearCache'); - this.__widthOfSpace = []; - }, - - /** - * Returns true if object has no styling - */ - isEmptyStyles: function() { - if (!this.styles) { - return true; - } - var obj = this.styles; - - for (var p1 in obj) { - for (var p2 in obj[p1]) { - // eslint-disable-next-line no-unused-vars - for (var p3 in obj[p1][p2]) { - return false; - } - } - } - return true; - }, - - /** - * Sets selection start (left boundary of a selection) - * @param {Number} index Index to set selection start to - */ - setSelectionStart: function(index) { - index = Math.max(index, 0); - this._updateAndFire('selectionStart', index); - }, - - /** - * Sets selection end (right boundary of a selection) - * @param {Number} index Index to set selection end to - */ - setSelectionEnd: function(index) { - index = Math.min(index, this.text.length); - this._updateAndFire('selectionEnd', index); - }, - - /** - * @private - * @param {String} property 'selectionStart' or 'selectionEnd' - * @param {Number} index new position of property - */ - _updateAndFire: function(property, index) { - if (this[property] !== index) { - this._fireSelectionChanged(); - this[property] = index; - } - this._updateTextarea(); - }, - - /** - * Fires the even of selection changed - * @private - */ - _fireSelectionChanged: function() { - this.fire('selection:changed'); - this.canvas && this.canvas.fire('text:selection:changed', { target: this }); - }, - - /** - * Gets style of a current selection/cursor (at the start position) - * @param {Number} [startIndex] Start index to get styles at - * @param {Number} [endIndex] End index to get styles at - * @return {Object} styles Style object at a specified (or current) index - */ - getSelectionStyles: function(startIndex, endIndex) { - - if (arguments.length === 2) { - var styles = []; - for (var i = startIndex; i < endIndex; i++) { - styles.push(this.getSelectionStyles(i)); - } - return styles; - } - - var loc = this.get2DCursorLocation(startIndex), - style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex); - - return style || {}; - }, - - /** - * Sets style of a current selection - * @param {Object} [styles] Styles object - * @return {fabric.IText} thisArg - * @chainable - */ - setSelectionStyles: function(styles) { - if (this.selectionStart === this.selectionEnd) { - this._extendStyles(this.selectionStart, styles); - } - else { - for (var i = this.selectionStart; i < this.selectionEnd; i++) { - this._extendStyles(i, styles); - } - } - /* not included in _extendStyles to avoid clearing cache more than once */ - this._forceClearCache = true; - return this; - }, - - /** - * @private - */ - _extendStyles: function(index, styles) { - var loc = this.get2DCursorLocation(index); - - if (!this._getLineStyle(loc.lineIndex)) { - this._setLineStyle(loc.lineIndex, {}); - } - - if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { - this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); - } - - fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Boolean} noTransform - */ - render: function(ctx, noTransform) { - this.clearContextTop(); - this.callSuper('render', ctx, noTransform); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _render: function(ctx) { - this.callSuper('_render', ctx); - this.ctx = ctx; - // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor - // the correct position but not at every cursor animation. - this.cursorOffsetCache = { }; - this.renderCursorOrSelection(); - }, - - /** - * Prepare and clean the contextTop - */ - clearContextTop: function() { - if (!this.active || !this.isEditing) { - return; - } - if (this.canvas && this.canvas.contextTop) { - var ctx = this.canvas.contextTop; - ctx.save(); - ctx.transform.apply(ctx, this.canvas.viewportTransform); - this.transform(ctx); - this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix); - this._clearTextArea(ctx); - ctx.restore(); - } - }, - - /** - * Renders cursor or selection (depending on what exists) - */ - renderCursorOrSelection: function() { - if (!this.active || !this.isEditing) { - return; - } - var chars = this.text.split(''), - boundaries, ctx; - if (this.canvas && this.canvas.contextTop) { - ctx = this.canvas.contextTop; - ctx.save(); - ctx.transform.apply(ctx, this.canvas.viewportTransform); - this.transform(ctx); - this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix); - this._clearTextArea(ctx); - } - else { - ctx = this.ctx; - ctx.save(); - } - if (this.selectionStart === this.selectionEnd) { - boundaries = this._getCursorBoundaries(chars, 'cursor'); - this.renderCursor(boundaries, ctx); - } - else { - boundaries = this._getCursorBoundaries(chars, 'selection'); - this.renderSelection(chars, boundaries, ctx); - } - ctx.restore(); - }, - - _clearTextArea: function(ctx) { - // we add 4 pixel, to be sure to do not leave any pixel out - var width = this.width + 4, height = this.height + 4; - ctx.clearRect(-width / 2, -height / 2, width, height); - }, - /** - * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) - * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. - */ - get2DCursorLocation: function(selectionStart) { - if (typeof selectionStart === 'undefined') { - selectionStart = this.selectionStart; - } - var len = this._textLines.length; - for (var i = 0; i < len; i++) { - if (selectionStart <= this._textLines[i].length) { - return { - lineIndex: i, - charIndex: selectionStart - }; - } - selectionStart -= this._textLines[i].length + 1; - } - return { - lineIndex: i - 1, - charIndex: this._textLines[i - 1].length < selectionStart ? this._textLines[i - 1].length : selectionStart - }; - }, - - /** - * Returns complete style of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Object} Character style - */ - getCurrentCharStyle: function(lineIndex, charIndex) { - var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); - - return { - fontSize: style && style.fontSize || this.fontSize, - fill: style && style.fill || this.fill, - textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, - textDecoration: style && style.textDecoration || this.textDecoration, - fontFamily: style && style.fontFamily || this.fontFamily, - fontWeight: style && style.fontWeight || this.fontWeight, - fontStyle: style && style.fontStyle || this.fontStyle, - stroke: style && style.stroke || this.stroke, - strokeWidth: style && style.strokeWidth || this.strokeWidth - }; - }, - - /** - * Returns fontSize of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {Number} Character font size - */ - getCurrentCharFontSize: function(lineIndex, charIndex) { - var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); - return style && style.fontSize ? style.fontSize : this.fontSize; - }, - - /** - * Returns color (fill) of char at the current cursor - * @param {Number} lineIndex Line index - * @param {Number} charIndex Char index - * @return {String} Character color (fill) - */ - getCurrentCharColor: function(lineIndex, charIndex) { - var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1); - return style && style.fill ? style.fill : this.cursorColor; - }, - - /** - * Returns cursor boundaries (left, top, leftOffset, topOffset) - * @private - * @param {Array} chars Array of characters - * @param {String} typeOfBoundaries - */ - _getCursorBoundaries: function(chars, typeOfBoundaries) { - - // left/top are left/top of entire text box - // leftOffset/topOffset are offset from that left/top point of a text box - - var left = Math.round(this._getLeftOffset()), - top = this._getTopOffset(), - - offsets = this._getCursorBoundariesOffsets( - chars, typeOfBoundaries); - - return { - left: left, - top: top, - leftOffset: offsets.left + offsets.lineLeft, - topOffset: offsets.top - }; - }, - - /** - * @private - */ - _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { - if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) { - return this.cursorOffsetCache; - } - var lineLeftOffset = 0, - lineIndex = 0, - charIndex = 0, - topOffset = 0, - leftOffset = 0, - boundaries; - - for (var i = 0; i < this.selectionStart; i++) { - if (chars[i] === '\n') { - leftOffset = 0; - topOffset += this._getHeightOfLine(this.ctx, lineIndex); - - lineIndex++; - charIndex = 0; - } - else { - leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); - charIndex++; - } - - lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)); - } - if (typeOfBoundaries === 'cursor') { - topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight - - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction); - } - if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) { - leftOffset -= this._getWidthOfCharSpacing(); - } - boundaries = { - top: topOffset, - left: leftOffset > 0 ? leftOffset : 0, - lineLeft: lineLeftOffset - }; - this.cursorOffsetCache = boundaries; - return this.cursorOffsetCache; - }, - - /** - * Renders cursor - * @param {Object} boundaries - * @param {CanvasRenderingContext2D} ctx transformed context to draw on - */ - renderCursor: function(boundaries, ctx) { - - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), - leftOffset = (lineIndex === 0 && charIndex === 0) - ? this._getLineLeftOffset(this._getLineWidth(ctx, lineIndex)) - : boundaries.leftOffset, - multiplier = this.scaleX * this.canvas.getZoom(), - cursorWidth = this.cursorWidth / multiplier; - - ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; - - ctx.fillRect( - boundaries.left + leftOffset - cursorWidth / 2, - boundaries.top + boundaries.topOffset, - cursorWidth, - charHeight); - }, - - /** - * Renders text selection - * @param {Array} chars Array of characters - * @param {Object} boundaries Object with left/top/leftOffset/topOffset - * @param {CanvasRenderingContext2D} ctx transformed context to draw on - */ - renderSelection: function(chars, boundaries, ctx) { - - ctx.fillStyle = this.selectionColor; - - var start = this.get2DCursorLocation(this.selectionStart), - end = this.get2DCursorLocation(this.selectionEnd), - startLine = start.lineIndex, - endLine = end.lineIndex; - for (var i = startLine; i <= endLine; i++) { - var lineOffset = this._getLineLeftOffset(this._getLineWidth(ctx, i)) || 0, - lineHeight = this._getHeightOfLine(this.ctx, i), - realLineHeight = 0, boxWidth = 0, line = this._textLines[i]; - - if (i === startLine) { - for (var j = 0, len = line.length; j < len; j++) { - if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { - boxWidth += this._getWidthOfChar(ctx, line[j], i, j); - } - if (j < start.charIndex) { - lineOffset += this._getWidthOfChar(ctx, line[j], i, j); - } - } - if (j === line.length) { - boxWidth -= this._getWidthOfCharSpacing(); - } - } - else if (i > startLine && i < endLine) { - boxWidth += this._getLineWidth(ctx, i) || 5; - } - else if (i === endLine) { - for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { - boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2); - } - if (end.charIndex === line.length) { - boxWidth -= this._getWidthOfCharSpacing(); - } - } - realLineHeight = lineHeight; - if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) { - lineHeight /= this.lineHeight; - } - ctx.fillRect( - boundaries.left + lineOffset, - boundaries.top + boundaries.topOffset, - boxWidth > 0 ? boxWidth : 0, - lineHeight); - - boundaries.topOffset += realLineHeight; - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Content of the line - * @param {Number} left - * @param {Number} top - * @param {Number} lineIndex - * @param {Number} charOffset - */ - _renderChars: function(method, ctx, line, left, top, lineIndex, charOffset) { - - if (this.isEmptyStyles()) { - return this._renderCharsFast(method, ctx, line, left, top); - } - - charOffset = charOffset || 0; - - // set proper line offset - var lineHeight = this._getHeightOfLine(ctx, lineIndex), - prevStyle, - thisStyle, - charsToRender = ''; - - ctx.save(); - top -= lineHeight / this.lineHeight * this._fontSizeFraction; - for (var i = charOffset, len = line.length + charOffset; i <= len; i++) { - prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); - thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); - - if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { - this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); - charsToRender = ''; - prevStyle = thisStyle; - } - charsToRender += line[i - charOffset]; - } - ctx.restore(); - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line Content of the line - * @param {Number} left Left coordinate - * @param {Number} top Top coordinate - */ - _renderCharsFast: function(method, ctx, line, left, top) { - - if (method === 'fillText' && this.fill) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) { - this.callSuper('_renderChars', method, ctx, line, left, top); - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - * @param {Number} i - * @param {String} _char - * @param {Number} left Left coordinate - * @param {Number} top Top coordinate - * @param {Number} lineHeight Height of the line - */ - _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { - var charWidth, charHeight, shouldFill, shouldStroke, - decl = this._getStyleDeclaration(lineIndex, i), - offset, textDecoration, chars, additionalSpace, _charWidth; - - if (decl) { - charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); - shouldStroke = decl.stroke; - shouldFill = decl.fill; - textDecoration = decl.textDecoration; - } - else { - charHeight = this.fontSize; - } - - shouldStroke = (shouldStroke || this.stroke) && method === 'strokeText'; - shouldFill = (shouldFill || this.fill) && method === 'fillText'; - - decl && ctx.save(); - - charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl || null); - textDecoration = textDecoration || this.textDecoration; - - if (decl && decl.textBackgroundColor) { - this._removeShadow(ctx); - } - if (this.charSpacing !== 0) { - additionalSpace = this._getWidthOfCharSpacing(); - chars = _char.split(''); - charWidth = 0; - for (var j = 0, len = chars.length, char; j < len; j++) { - char = chars[j]; - shouldFill && ctx.fillText(char, left + charWidth, top); - shouldStroke && ctx.strokeText(char, left + charWidth, top); - _charWidth = ctx.measureText(char).width + additionalSpace; - charWidth += _charWidth > 0 ? _charWidth : 0; - } - } - else { - shouldFill && ctx.fillText(_char, left, top); - shouldStroke && ctx.strokeText(_char, left, top); - } - - if (textDecoration || textDecoration !== '') { - offset = this._fontSizeFraction * lineHeight / this.lineHeight; - this._renderCharDecoration(ctx, textDecoration, left, top, offset, charWidth, charHeight); - } - - decl && ctx.restore(); - ctx.translate(charWidth, 0); - }, - - /** - * @private - * @param {Object} prevStyle - * @param {Object} thisStyle - */ - _hasStyleChanged: function(prevStyle, thisStyle) { - return (prevStyle.fill !== thisStyle.fill || - prevStyle.fontSize !== thisStyle.fontSize || - prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || - prevStyle.textDecoration !== thisStyle.textDecoration || - prevStyle.fontFamily !== thisStyle.fontFamily || - prevStyle.fontWeight !== thisStyle.fontWeight || - prevStyle.fontStyle !== thisStyle.fontStyle || - prevStyle.stroke !== thisStyle.stroke || - prevStyle.strokeWidth !== thisStyle.strokeWidth - ); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderCharDecoration: function(ctx, textDecoration, left, top, offset, charWidth, charHeight) { - - if (!textDecoration) { - return; - } - - var decorationWeight = charHeight / 15, - positions = { - underline: top + charHeight / 10, - 'line-through': top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + decorationWeight, - overline: top - (this._fontSizeMult - this._fontSizeFraction) * charHeight - }, - decorations = ['underline', 'line-through', 'overline'], i, decoration; - - for (i = 0; i < decorations.length; i++) { - decoration = decorations[i]; - if (textDecoration.indexOf(decoration) > -1) { - ctx.fillRect(left, positions[decoration], charWidth , decorationWeight); - } - } - }, - - /** - * @private - * @param {String} method - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line - * @param {Number} left - * @param {Number} top - * @param {Number} lineIndex - */ - _renderTextLine: function(method, ctx, line, left, top, lineIndex) { - // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine - // the adding 0.03 is just to align text with itext by overlap test - if (!this.isEmptyStyles()) { - top += this.fontSize * (this._fontSizeFraction + 0.03); - } - this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextDecoration: function(ctx) { - if (this.isEmptyStyles()) { - return this.callSuper('_renderTextDecoration', ctx); - } - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderTextLinesBackground: function(ctx) { - this.callSuper('_renderTextLinesBackground', ctx); - - var lineTopOffset = 0, heightOfLine, - lineWidth, lineLeftOffset, - leftOffset = this._getLeftOffset(), - topOffset = this._getTopOffset(), - line, _char, style; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - heightOfLine = this._getHeightOfLine(ctx, i); - line = this._textLines[i]; - - if (line === '' || !this.styles || !this._getLineStyle(i)) { - lineTopOffset += heightOfLine; - continue; - } - - lineWidth = this._getLineWidth(ctx, i); - lineLeftOffset = this._getLineLeftOffset(lineWidth); - - for (var j = 0, jlen = line.length; j < jlen; j++) { - style = this._getStyleDeclaration(i, j); - if (!style || !style.textBackgroundColor) { - continue; - } - _char = line[j]; - - ctx.fillStyle = style.textBackgroundColor; - - ctx.fillRect( - leftOffset + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j), - topOffset + lineTopOffset, - this._getWidthOfChar(ctx, _char, i, j), - heightOfLine / this.lineHeight - ); - } - lineTopOffset += heightOfLine; - } - }, - - /** - * @private - */ - _getCacheProp: function(_char, styleDeclaration) { - return _char + - styleDeclaration.fontSize + - styleDeclaration.fontWeight + - styleDeclaration.fontStyle; - }, - - /** - * @private - * @param {String} fontFamily name - * @return {Object} reference to cache - */ - _getFontCache: function(fontFamily) { - if (!fabric.charWidthsCache[fontFamily]) { - fabric.charWidthsCache[fontFamily] = { }; - } - return fabric.charWidthsCache[fontFamily]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} _char - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Object} [decl] - */ - _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { - var charDecl = decl || this._getStyleDeclaration(lineIndex, charIndex), - styleDeclaration = clone(charDecl), - width, cacheProp, charWidthsCache; - - this._applyFontStyles(styleDeclaration); - charWidthsCache = this._getFontCache(styleDeclaration.fontFamily); - cacheProp = this._getCacheProp(_char, styleDeclaration); - - // short-circuit if no styles for this char - // global style from object is always applyed and handled by save and restore - if (!charDecl && charWidthsCache[cacheProp] && this.caching) { - return charWidthsCache[cacheProp]; - } - - if (typeof styleDeclaration.shadow === 'string') { - styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); - } - - var fill = styleDeclaration.fill || this.fill; - ctx.fillStyle = fill.toLive - ? fill.toLive(ctx, this) - : fill; - - if (styleDeclaration.stroke) { - ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) - ? styleDeclaration.stroke.toLive(ctx, this) - : styleDeclaration.stroke; - } - - ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; - ctx.font = this._getFontDeclaration.call(styleDeclaration); - - //if we want this._setShadow.call to work with styleDeclarion - //we have to add those references - if (styleDeclaration.shadow) { - styleDeclaration.scaleX = this.scaleX; - styleDeclaration.scaleY = this.scaleY; - styleDeclaration.canvas = this.canvas; - styleDeclaration.getObjectScaling = this.getObjectScaling; - this._setShadow.call(styleDeclaration, ctx); - } - - if (!this.caching || !charWidthsCache[cacheProp]) { - width = ctx.measureText(_char).width; - this.caching && (charWidthsCache[cacheProp] = width); - return width; - } - - return charWidthsCache[cacheProp]; - }, - - /** - * @private - * @param {Object} styleDeclaration - */ - _applyFontStyles: function(styleDeclaration) { - if (!styleDeclaration.fontFamily) { - styleDeclaration.fontFamily = this.fontFamily; - } - if (!styleDeclaration.fontSize) { - styleDeclaration.fontSize = this.fontSize; - } - if (!styleDeclaration.fontWeight) { - styleDeclaration.fontWeight = this.fontWeight; - } - if (!styleDeclaration.fontStyle) { - styleDeclaration.fontStyle = this.fontStyle; - } - }, - - /** - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Boolean} [returnCloneOrEmpty=false] - * @private - */ - _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) { - if (returnCloneOrEmpty) { - return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) - ? clone(this.styles[lineIndex][charIndex]) - : { }; - } - - return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null; - }, - - /** - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Object} style - * @private - */ - _setStyleDeclaration: function(lineIndex, charIndex, style) { - this.styles[lineIndex][charIndex] = style; - }, - - /** - * - * @param {Number} lineIndex - * @param {Number} charIndex - * @private - */ - _deleteStyleDeclaration: function(lineIndex, charIndex) { - delete this.styles[lineIndex][charIndex]; - }, - - /** - * @param {Number} lineIndex - * @private - */ - _getLineStyle: function(lineIndex) { - return this.styles[lineIndex]; - }, - - /** - * @param {Number} lineIndex - * @param {Object} style - * @private - */ - _setLineStyle: function(lineIndex, style) { - this.styles[lineIndex] = style; - }, - - /** - * @param {Number} lineIndex - * @private - */ - _deleteLineStyle: function(lineIndex) { - delete this.styles[lineIndex]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - if (!this._isMeasuring && this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) { - return this._getWidthOfSpace(ctx, lineIndex); - } - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - if (this.charSpacing !== 0) { - width += this._getWidthOfCharSpacing(); - } - ctx.restore(); - return width > 0 ? width : 0 - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - * @param {Number} charIndex - */ - _getHeightOfChar: function(ctx, lineIndex, charIndex) { - var style = this._getStyleDeclaration(lineIndex, charIndex); - return style && style.fontSize ? style.fontSize : this.fontSize; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - * @param {Number} charIndex - */ - _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) { - var width = 0, i, _char; - for (i = 0; i < charIndex; i++) { - _char = this._textLines[lineIndex][i]; - width += this._getWidthOfChar(ctx, _char, lineIndex, i); - } - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex line number - * @return {Number} Line width - */ - _measureLine: function(ctx, lineIndex) { - this._isMeasuring = true; - var width = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length); - if (this.charSpacing !== 0) { - width -= this._getWidthOfCharSpacing(); - } - this._isMeasuring = false; - return width > 0 ? width : 0; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {Number} lineIndex - */ - _getWidthOfSpace: function (ctx, lineIndex) { - if (this.__widthOfSpace[lineIndex]) { - return this.__widthOfSpace[lineIndex]; - } - var line = this._textLines[lineIndex], - wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0), - widthDiff = this.width - wordsWidth, - numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length, - width = Math.max(widthDiff / numSpaces, ctx.measureText(' ').width); - this.__widthOfSpace[lineIndex] = width; - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - * @param {String} line - * @param {Number} lineIndex - * @param {Number} charOffset - */ - _getWidthOfWords: function (ctx, line, lineIndex, charOffset) { - var width = 0; - - for (var charIndex = 0; charIndex < line.length; charIndex++) { - var _char = line[charIndex]; - - if (!_char.match(/\s/)) { - width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex + charOffset); - } - } - - return width; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getHeightOfLine: function(ctx, lineIndex) { - if (this.__lineHeights[lineIndex]) { - return this.__lineHeights[lineIndex]; - } - - var line = this._textLines[lineIndex], - maxHeight = this._getHeightOfChar(ctx, lineIndex, 0); - - for (var i = 1, len = line.length; i < len; i++) { - var currentCharHeight = this._getHeightOfChar(ctx, lineIndex, i); - if (currentCharHeight > maxHeight) { - maxHeight = currentCharHeight; - } - } - this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; - return this.__lineHeights[lineIndex]; - }, - - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _getTextHeight: function(ctx) { - var lineHeight, height = 0; - for (var i = 0, len = this._textLines.length; i < len; i++) { - lineHeight = this._getHeightOfLine(ctx, i); - height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); - } - return height; - }, - - /** - * Returns object representation of an instance - * @method toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { - styles: clone(this.styles, true) - }); - } - }); - - /** - * Returns fabric.IText instance from an object representation - * @static - * @memberOf fabric.IText - * @param {Object} object Object to create an instance from - * @param {function} [callback] invoked with new instance as argument - * @return {fabric.IText} instance of fabric.IText - */ - fabric.IText.fromObject = function(object, callback) { - var iText = new fabric.IText(object.text, clone(object)); - callback && callback(iText); - return iText; - }; -})(); - - -(function() { - - var clone = fabric.util.object.clone; - - fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * Initializes all the interactive behavior of IText - */ - initBehavior: function() { - this.initAddedHandler(); - this.initRemovedHandler(); - this.initCursorSelectionHandlers(); - this.initDoubleClickSimulation(); - this.mouseMoveHandler = this.mouseMoveHandler.bind(this); - }, - - /** - * Initializes "selected" event handler - */ - initSelectedHandler: function() { - this.on('selected', function() { - - var _this = this; - setTimeout(function() { - _this.selected = true; - }, 100); - }); - }, - - /** - * Initializes "added" event handler - */ - initAddedHandler: function() { - var _this = this; - this.on('added', function() { - var canvas = _this.canvas; - if (canvas) { - if (!canvas._hasITextHandlers) { - canvas._hasITextHandlers = true; - _this._initCanvasHandlers(canvas); - } - canvas._iTextInstances = canvas._iTextInstances || []; - canvas._iTextInstances.push(_this); - } - }); - }, - - initRemovedHandler: function() { - var _this = this; - this.on('removed', function() { - var canvas = _this.canvas; - if (canvas) { - canvas._iTextInstances = canvas._iTextInstances || []; - fabric.util.removeFromArray(canvas._iTextInstances, _this); - if (canvas._iTextInstances.length === 0) { - canvas._hasITextHandlers = false; - _this._removeCanvasHandlers(canvas); - } - } - }); - }, - - /** - * register canvas event to manage exiting on other instances - * @private - */ - _initCanvasHandlers: function(canvas) { - canvas._canvasITextSelectionClearedHanlder = (function() { - fabric.IText.prototype.exitEditingOnOthers(canvas); - }).bind(this); - canvas._mouseUpITextHandler = (function() { - if (canvas._iTextInstances) { - canvas._iTextInstances.forEach(function(obj) { - obj.__isMousedown = false; - }); - } - }).bind(this); - canvas.on('selection:cleared', canvas._canvasITextSelectionClearedHanlder); - canvas.on('object:selected', canvas._canvasITextSelectionClearedHanlder); - canvas.on('mouse:up', canvas._mouseUpITextHandler); - }, - - /** - * remove canvas event to manage exiting on other instances - * @private - */ - _removeCanvasHandlers: function(canvas) { - canvas.off('selection:cleared', canvas._canvasITextSelectionClearedHanlder); - canvas.off('object:selected', canvas._canvasITextSelectionClearedHanlder); - canvas.off('mouse:up', canvas._mouseUpITextHandler); - }, - - /** - * @private - */ - _tick: function() { - this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); - }, - - /** - * @private - */ - _animateCursor: function(obj, targetOpacity, duration, completeMethod) { - - var tickState; - - tickState = { - isAborted: false, - abort: function() { - this.isAborted = true; - }, - }; - - obj.animate('_currentCursorOpacity', targetOpacity, { - duration: duration, - onComplete: function() { - if (!tickState.isAborted) { - obj[completeMethod](); - } - }, - onChange: function() { - // we do not want to animate a selection, only cursor - if (obj.canvas && obj.selectionStart === obj.selectionEnd) { - obj.renderCursorOrSelection(); - } - }, - abort: function() { - return tickState.isAborted; - } - }); - return tickState; - }, - - /** - * @private - */ - _onTickComplete: function() { - - var _this = this; - - if (this._cursorTimeout1) { - clearTimeout(this._cursorTimeout1); - } - this._cursorTimeout1 = setTimeout(function() { - _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); - }, 100); - }, - - /** - * Initializes delayed cursor - */ - initDelayedCursor: function(restart) { - var _this = this, - delay = restart ? 0 : this.cursorDelay; - - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - this._cursorTimeout2 = setTimeout(function() { - _this._tick(); - }, delay); - }, - - /** - * Aborts cursor animation and clears all timeouts - */ - abortCursorAnimation: function() { - var shouldClear = this._currentTickState || this._currentTickCompleteState; - this._currentTickState && this._currentTickState.abort(); - this._currentTickCompleteState && this._currentTickCompleteState.abort(); - - clearTimeout(this._cursorTimeout1); - clearTimeout(this._cursorTimeout2); - - this._currentCursorOpacity = 0; - // to clear just itext area we need to transform the context - // it may not be worth it - if (shouldClear) { - this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx); - } - - }, - - /** - * Selects entire text - */ - selectAll: function() { - this.selectionStart = 0; - this.selectionEnd = this.text.length; - this._fireSelectionChanged(); - this._updateTextarea(); - }, - - /** - * Returns selected text - * @return {String} - */ - getSelectedText: function() { - return this.text.slice(this.selectionStart, this.selectionEnd); - }, - - /** - * Find new selection index representing start of current word according to current selection index - * @param {Number} startFrom Surrent selection index - * @return {Number} New selection index - */ - findWordBoundaryLeft: function(startFrom) { - var offset = 0, index = startFrom - 1; - - // remove space before cursor first - if (this._reSpace.test(this.text.charAt(index))) { - while (this._reSpace.test(this.text.charAt(index))) { - offset++; - index--; - } - } - while (/\S/.test(this.text.charAt(index)) && index > -1) { - offset++; - index--; - } - - return startFrom - offset; - }, - - /** - * Find new selection index representing end of current word according to current selection index - * @param {Number} startFrom Current selection index - * @return {Number} New selection index - */ - findWordBoundaryRight: function(startFrom) { - var offset = 0, index = startFrom; - - // remove space after cursor first - if (this._reSpace.test(this.text.charAt(index))) { - while (this._reSpace.test(this.text.charAt(index))) { - offset++; - index++; - } - } - while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { - offset++; - index++; - } - - return startFrom + offset; - }, - - /** - * Find new selection index representing start of current line according to current selection index - * @param {Number} startFrom Current selection index - * @return {Number} New selection index - */ - findLineBoundaryLeft: function(startFrom) { - var offset = 0, index = startFrom - 1; - - while (!/\n/.test(this.text.charAt(index)) && index > -1) { - offset++; - index--; - } - - return startFrom - offset; - }, - - /** - * Find new selection index representing end of current line according to current selection index - * @param {Number} startFrom Current selection index - * @return {Number} New selection index - */ - findLineBoundaryRight: function(startFrom) { - var offset = 0, index = startFrom; - - while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { - offset++; - index++; - } - - return startFrom + offset; - }, - - /** - * Returns number of newlines in selected text - * @return {Number} Number of newlines in selected text - */ - getNumNewLinesInSelectedText: function() { - var selectedText = this.getSelectedText(), - numNewLines = 0; - - for (var i = 0, len = selectedText.length; i < len; i++) { - if (selectedText[i] === '\n') { - numNewLines++; - } - } - return numNewLines; - }, - - /** - * Finds index corresponding to beginning or end of a word - * @param {Number} selectionStart Index of a character - * @param {Number} direction 1 or -1 - * @return {Number} Index of the beginning or end of a word - */ - searchWordBoundary: function(selectionStart, direction) { - var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, - _char = this.text.charAt(index), - reNonWord = /[ \n\.,;!\?\-]/; - - while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { - index += direction; - _char = this.text.charAt(index); - } - if (reNonWord.test(_char) && _char !== '\n') { - index += direction === 1 ? 0 : 1; - } - return index; - }, - - /** - * Selects a word based on the index - * @param {Number} selectionStart Index of a character - */ - selectWord: function(selectionStart) { - selectionStart = selectionStart || this.selectionStart; - var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ - newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ - - this.selectionStart = newSelectionStart; - this.selectionEnd = newSelectionEnd; - this._fireSelectionChanged(); - this._updateTextarea(); - this.renderCursorOrSelection(); - }, - - /** - * Selects a line based on the index - * @param {Number} selectionStart Index of a character - */ - selectLine: function(selectionStart) { - selectionStart = selectionStart || this.selectionStart; - var newSelectionStart = this.findLineBoundaryLeft(selectionStart), - newSelectionEnd = this.findLineBoundaryRight(selectionStart); - - this.selectionStart = newSelectionStart; - this.selectionEnd = newSelectionEnd; - this._fireSelectionChanged(); - this._updateTextarea(); - }, - - /** - * Enters editing state - * @return {fabric.IText} thisArg - * @chainable - */ - enterEditing: function(e) { - if (this.isEditing || !this.editable) { - return; - } - - if (this.canvas) { - this.exitEditingOnOthers(this.canvas); - } - - this.isEditing = true; - - this.initHiddenTextarea(e); - this.hiddenTextarea.focus(); - this._updateTextarea(); - this._saveEditingProps(); - this._setEditingProps(); - this._textBeforeEdit = this.text; - - this._tick(); - this.fire('editing:entered'); - - if (!this.canvas) { - return this; - } - this.canvas.fire('text:editing:entered', { target: this }); - this.initMouseMoveHandler(); - this.canvas.renderAll(); - return this; - }, - - exitEditingOnOthers: function(canvas) { - if (canvas._iTextInstances) { - canvas._iTextInstances.forEach(function(obj) { - obj.selected = false; - if (obj.isEditing) { - obj.exitEditing(); - } - }); - } - }, - - /** - * Initializes "mousemove" event handler - */ - initMouseMoveHandler: function() { - this.canvas.on('mouse:move', this.mouseMoveHandler); - }, - - /** - * @private - */ - mouseMoveHandler: function(options) { - if (!this.__isMousedown || !this.isEditing) { - return; - } - - var newSelectionStart = this.getSelectionStartFromPointer(options.e), - currentStart = this.selectionStart, - currentEnd = this.selectionEnd; - if (newSelectionStart === this.__selectionStartOnMouseDown) { - return; - } - if (newSelectionStart > this.__selectionStartOnMouseDown) { - this.selectionStart = this.__selectionStartOnMouseDown; - this.selectionEnd = newSelectionStart; - } - else { - this.selectionStart = newSelectionStart; - this.selectionEnd = this.__selectionStartOnMouseDown; - } - if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) { - this._fireSelectionChanged(); - this._updateTextarea(); - this.renderCursorOrSelection(); - } - }, - - /** - * @private - */ - _setEditingProps: function() { - this.hoverCursor = 'text'; - - if (this.canvas) { - this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; - } - - this.borderColor = this.editingBorderColor; - - this.hasControls = this.selectable = false; - this.lockMovementX = this.lockMovementY = true; - }, - - /** - * @private - */ - _updateTextarea: function() { - if (!this.hiddenTextarea || this.inCompositionMode) { - return; - } - this.cursorOffsetCache = { }; - this.hiddenTextarea.value = this.text; - this.hiddenTextarea.selectionStart = this.selectionStart; - this.hiddenTextarea.selectionEnd = this.selectionEnd; - if (this.selectionStart === this.selectionEnd) { - var style = this._calcTextareaPosition(); - this.hiddenTextarea.style.left = style.left; - this.hiddenTextarea.style.top = style.top; - this.hiddenTextarea.style.fontSize = style.fontSize; - } - }, - - /** - * @private - * @return {Object} style contains style for hiddenTextarea - */ - _calcTextareaPosition: function() { - if (!this.canvas) { - return { x: 1, y: 1 }; - } - var chars = this.text.split(''), - boundaries = this._getCursorBoundaries(chars, 'cursor'), - cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), - leftOffset = (lineIndex === 0 && charIndex === 0) - ? this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - : boundaries.leftOffset, - m = this.calcTransformMatrix(), - p = { - x: boundaries.left + leftOffset, - y: boundaries.top + boundaries.topOffset + charHeight - }, - upperCanvas = this.canvas.upperCanvasEl, - maxWidth = upperCanvas.width - charHeight, - maxHeight = upperCanvas.height - charHeight; - - p = fabric.util.transformPoint(p, m); - p = fabric.util.transformPoint(p, this.canvas.viewportTransform); - - if (p.x < 0) { - p.x = 0; - } - if (p.x > maxWidth) { - p.x = maxWidth; - } - if (p.y < 0) { - p.y = 0; - } - if (p.y > maxHeight) { - p.y = maxHeight; - } - - // add canvas offset on document - p.x += this.canvas._offset.left; - p.y += this.canvas._offset.top; - - return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight }; - }, - - /** - * @private - */ - _saveEditingProps: function() { - this._savedProps = { - hasControls: this.hasControls, - borderColor: this.borderColor, - lockMovementX: this.lockMovementX, - lockMovementY: this.lockMovementY, - hoverCursor: this.hoverCursor, - defaultCursor: this.canvas && this.canvas.defaultCursor, - moveCursor: this.canvas && this.canvas.moveCursor - }; - }, - - /** - * @private - */ - _restoreEditingProps: function() { - if (!this._savedProps) { - return; - } - - this.hoverCursor = this._savedProps.overCursor; - this.hasControls = this._savedProps.hasControls; - this.borderColor = this._savedProps.borderColor; - this.lockMovementX = this._savedProps.lockMovementX; - this.lockMovementY = this._savedProps.lockMovementY; - - if (this.canvas) { - this.canvas.defaultCursor = this._savedProps.defaultCursor; - this.canvas.moveCursor = this._savedProps.moveCursor; - } - }, - - /** - * Exits from editing state - * @return {fabric.IText} thisArg - * @chainable - */ - exitEditing: function() { - var isTextChanged = (this._textBeforeEdit !== this.text); - this.selected = false; - this.isEditing = false; - this.selectable = true; - - this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); - this.hiddenTextarea = null; - - this.abortCursorAnimation(); - this._restoreEditingProps(); - this._currentCursorOpacity = 0; - - this.fire('editing:exited'); - isTextChanged && this.fire('modified'); - if (this.canvas) { - this.canvas.off('mouse:move', this.mouseMoveHandler); - this.canvas.fire('text:editing:exited', { target: this }); - isTextChanged && this.canvas.fire('object:modified', { target: this }); - } - - return this; - }, - - /** - * @private - */ - _removeExtraneousStyles: function() { - for (var prop in this.styles) { - if (!this._textLines[prop]) { - delete this.styles[prop]; - } - } - }, - - /** - * @private - */ - _removeCharsFromTo: function(start, end) { - while (end !== start) { - this._removeSingleCharAndStyle(start + 1); - end--; - } - this.selectionStart = start; - this.selectionEnd = start; - }, - - _removeSingleCharAndStyle: function(index) { - var isBeginningOfLine = this.text[index - 1] === '\n', - indexStyle = isBeginningOfLine ? index : index - 1; - this.removeStyleObject(isBeginningOfLine, indexStyle); - this.text = this.text.slice(0, index - 1) + - this.text.slice(index); - - this._textLines = this._splitTextIntoLines(); - }, - - /** - * Inserts characters where cursor is (replacing selection if one exists) - * @param {String} _chars Characters to insert - * @param {Boolean} useCopiedStyle use fabric.copiedTextStyle - */ - insertChars: function(_chars, useCopiedStyle) { - var style; - - if (this.selectionEnd - this.selectionStart > 1) { - this._removeCharsFromTo(this.selectionStart, this.selectionEnd); - } - //short circuit for block paste - if (!useCopiedStyle && this.isEmptyStyles()) { - this.insertChar(_chars, false); - return; - } - for (var i = 0, len = _chars.length; i < len; i++) { - if (useCopiedStyle) { - style = fabric.copiedTextStyle[i]; - } - this.insertChar(_chars[i], i < len - 1, style); - } - }, - - /** - * Inserts a character where cursor is - * @param {String} _char Characters to insert - * @param {Boolean} skipUpdate trigger rendering and updates at the end of text insert - * @param {Object} styleObject Style to be inserted for the new char - */ - insertChar: function(_char, skipUpdate, styleObject) { - var isEndOfLine = this.text[this.selectionStart] === '\n'; - this.text = this.text.slice(0, this.selectionStart) + - _char + this.text.slice(this.selectionEnd); - this._textLines = this._splitTextIntoLines(); - this.insertStyleObjects(_char, isEndOfLine, styleObject); - this.selectionStart += _char.length; - this.selectionEnd = this.selectionStart; - if (skipUpdate) { - return; - } - this._updateTextarea(); - this.setCoords(); - this._fireSelectionChanged(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); - this.canvas && this.canvas.renderAll(); - }, - - /** - * Inserts new style object - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Boolean} isEndOfLine True if it's end of line - */ - insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { - - this.shiftLineStyles(lineIndex, +1); - - if (!this.styles[lineIndex + 1]) { - this.styles[lineIndex + 1] = {}; - } - - var currentCharStyle = {}, - newLineStyles = {}; - - if (this.styles[lineIndex] && this.styles[lineIndex][charIndex - 1]) { - currentCharStyle = this.styles[lineIndex][charIndex - 1]; - } - - // if there's nothing after cursor, - // we clone current char style onto the next (otherwise empty) line - if (isEndOfLine) { - newLineStyles[0] = clone(currentCharStyle); - this.styles[lineIndex + 1] = newLineStyles; - } - // otherwise we clone styles of all chars - // after cursor onto the next line, from the beginning - else { - for (var index in this.styles[lineIndex]) { - if (parseInt(index, 10) >= charIndex) { - newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; - // remove lines from the previous line since they're on a new line now - delete this.styles[lineIndex][index]; - } - } - this.styles[lineIndex + 1] = newLineStyles; - } - this._forceClearCache = true; - }, - - /** - * Inserts style object for a given line/char index - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Object} [style] Style object to insert, if given - */ - insertCharStyleObject: function(lineIndex, charIndex, style) { - - var currentLineStyles = this.styles[lineIndex], - currentLineStylesCloned = clone(currentLineStyles); - - if (charIndex === 0 && !style) { - charIndex = 1; - } - - // shift all char styles by 1 forward - // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 - for (var index in currentLineStylesCloned) { - var numericIndex = parseInt(index, 10); - - if (numericIndex >= charIndex) { - currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; - - // only delete the style if there was nothing moved there - if (!currentLineStylesCloned[numericIndex - 1]) { - delete currentLineStyles[numericIndex]; - } - } - } - - this.styles[lineIndex][charIndex] = - style || clone(currentLineStyles[charIndex - 1]); - this._forceClearCache = true; - }, - - /** - * Inserts style object(s) - * @param {String} _chars Characters at the location where style is inserted - * @param {Boolean} isEndOfLine True if it's end of line - * @param {Object} [styleObject] Style to insert - */ - insertStyleObjects: function(_chars, isEndOfLine, styleObject) { - // removed shortcircuit over isEmptyStyles - - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex; - - if (!this._getLineStyle(lineIndex)) { - this._setLineStyle(lineIndex, {}); - } - - if (_chars === '\n') { - this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); - } - else { - this.insertCharStyleObject(lineIndex, charIndex, styleObject); - } - }, - - /** - * Shifts line styles up or down - * @param {Number} lineIndex Index of a line - * @param {Number} offset Can be -1 or +1 - */ - shiftLineStyles: function(lineIndex, offset) { - // shift all line styles by 1 upward - var clonedStyles = clone(this.styles); - for (var line in this.styles) { - var numericLine = parseInt(line, 10); - if (numericLine > lineIndex) { - this.styles[numericLine + offset] = clonedStyles[numericLine]; - if (!clonedStyles[numericLine - offset]) { - delete this.styles[numericLine]; - } - } - } - //TODO: evaluate if delete old style lines with offset -1 - }, - - /** - * Removes style object - * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line - * @param {Number} [index] Optional index. When not given, current selectionStart is used. - */ - removeStyleObject: function(isBeginningOfLine, index) { - - var cursorLocation = this.get2DCursorLocation(index), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex; - - this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex); - }, - - _getTextOnPreviousLine: function(lIndex) { - return this._textLines[lIndex - 1]; - }, - - _removeStyleObject: function(isBeginningOfLine, cursorLocation, lineIndex, charIndex) { - - if (isBeginningOfLine) { - var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex), - newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0; - - if (!this.styles[lineIndex - 1]) { - this.styles[lineIndex - 1] = {}; - } - for (charIndex in this.styles[lineIndex]) { - this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] - = this.styles[lineIndex][charIndex]; - } - this.shiftLineStyles(cursorLocation.lineIndex, -1); - } - else { - var currentLineStyles = this.styles[lineIndex]; - - if (currentLineStyles) { - delete currentLineStyles[charIndex]; - } - var currentLineStylesCloned = clone(currentLineStyles); - // shift all styles by 1 backwards - for (var i in currentLineStylesCloned) { - var numericIndex = parseInt(i, 10); - if (numericIndex >= charIndex && numericIndex !== 0) { - currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; - delete currentLineStyles[numericIndex]; - } - } - } - }, - - /** - * Inserts new line - */ - insertNewline: function() { - this.insertChars('\n'); - }, - - /** - * Set the selectionStart and selectionEnd according to the ne postion of cursor - * mimic the key - mouse navigation when shift is pressed. - */ - setSelectionStartEndWithShift: function(start, end, newSelection) { - if (newSelection <= start) { - if (end === start) { - this._selectionDirection = 'left'; - } - else if (this._selectionDirection === 'right') { - this._selectionDirection = 'left'; - this.selectionEnd = start; - } - this.selectionStart = newSelection; - } - else if (newSelection > start && newSelection < end) { - if (this._selectionDirection === 'right') { - this.selectionEnd = newSelection; - } - else { - this.selectionStart = newSelection; - } - } - else { - // newSelection is > selection start and end - if (end === start) { - this._selectionDirection = 'right'; - } - else if (this._selectionDirection === 'left') { - this._selectionDirection = 'right'; - this.selectionStart = end; - } - this.selectionEnd = newSelection; - } - }, - - setSelectionInBoundaries: function() { - var length = this.text.length; - if (this.selectionStart > length) { - this.selectionStart = length; - } - else if (this.selectionStart < 0) { - this.selectionStart = 0; - } - if (this.selectionEnd > length) { - this.selectionEnd = length; - } - else if (this.selectionEnd < 0) { - this.selectionEnd = 0; - } - } - }); -})(); - - -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - /** - * Initializes "dbclick" event handler - */ - initDoubleClickSimulation: function() { - - // for double click - this.__lastClickTime = +new Date(); - - // for triple click - this.__lastLastClickTime = +new Date(); - - this.__lastPointer = { }; - - this.on('mousedown', this.onMouseDown.bind(this)); - }, - - onMouseDown: function(options) { - - this.__newClickTime = +new Date(); - var newPointer = this.canvas.getPointer(options.e); - - if (this.isTripleClick(newPointer)) { - this.fire('tripleclick', options); - this._stopEvent(options.e); - } - else if (this.isDoubleClick(newPointer)) { - this.fire('dblclick', options); - this._stopEvent(options.e); - } - - this.__lastLastClickTime = this.__lastClickTime; - this.__lastClickTime = this.__newClickTime; - this.__lastPointer = newPointer; - this.__lastIsEditing = this.isEditing; - this.__lastSelected = this.selected; - }, - - isDoubleClick: function(newPointer) { - return this.__newClickTime - this.__lastClickTime < 500 && - this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y && this.__lastIsEditing; - }, - - isTripleClick: function(newPointer) { - return this.__newClickTime - this.__lastClickTime < 500 && - this.__lastClickTime - this.__lastLastClickTime < 500 && - this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y; - }, - - /** - * @private - */ - _stopEvent: function(e) { - e.preventDefault && e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - }, - - /** - * Initializes event handlers related to cursor or selection - */ - initCursorSelectionHandlers: function() { - this.initSelectedHandler(); - this.initMousedownHandler(); - this.initMouseupHandler(); - this.initClicks(); - }, - - /** - * Initializes double and triple click event handlers - */ - initClicks: function() { - this.on('dblclick', function(options) { - this.selectWord(this.getSelectionStartFromPointer(options.e)); - }); - this.on('tripleclick', function(options) { - this.selectLine(this.getSelectionStartFromPointer(options.e)); - }); - }, - - /** - * Initializes "mousedown" event handler - */ - initMousedownHandler: function() { - this.on('mousedown', function(options) { - if (!this.editable) { - return; - } - var pointer = this.canvas.getPointer(options.e); - - this.__mousedownX = pointer.x; - this.__mousedownY = pointer.y; - this.__isMousedown = true; - - if (this.selected) { - this.setCursorByClick(options.e); - } - - if (this.isEditing) { - this.__selectionStartOnMouseDown = this.selectionStart; - if (this.selectionStart === this.selectionEnd) { - this.abortCursorAnimation(); - } - this.renderCursorOrSelection(); - } - }); - }, - - /** - * @private - */ - _isObjectMoved: function(e) { - var pointer = this.canvas.getPointer(e); - - return this.__mousedownX !== pointer.x || - this.__mousedownY !== pointer.y; - }, - - /** - * Initializes "mouseup" event handler - */ - initMouseupHandler: function() { - this.on('mouseup', function(options) { - this.__isMousedown = false; - if (!this.editable || this._isObjectMoved(options.e)) { - return; - } - - if (this.__lastSelected && !this.__corner) { - this.enterEditing(options.e); - if (this.selectionStart === this.selectionEnd) { - this.initDelayedCursor(true); - } - else { - this.renderCursorOrSelection(); - } - } - this.selected = true; - }); - }, - - /** - * Changes cursor location in a text depending on passed pointer (x/y) object - * @param {Event} e Event object - */ - setCursorByClick: function(e) { - var newSelection = this.getSelectionStartFromPointer(e), - start = this.selectionStart, end = this.selectionEnd; - if (e.shiftKey) { - this.setSelectionStartEndWithShift(start, end, newSelection); - } - else { - this.selectionStart = newSelection; - this.selectionEnd = newSelection; - } - this._fireSelectionChanged(); - this._updateTextarea(); - }, - - /** - * Returns index of a character corresponding to where an object was clicked - * @param {Event} e Event object - * @return {Number} Index of a character - */ - getSelectionStartFromPointer: function(e) { - var mouseOffset = this.getLocalPointer(e), - prevWidth = 0, - width = 0, - height = 0, - charIndex = 0, - newSelectionStart, - line; - - for (var i = 0, len = this._textLines.length; i < len; i++) { - line = this._textLines[i]; - height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - - var widthOfLine = this._getLineWidth(this.ctx, i), - lineLeftOffset = this._getLineLeftOffset(widthOfLine); - - width = lineLeftOffset * this.scaleX; - - for (var j = 0, jlen = line.length; j < jlen; j++) { - - prevWidth = width; - - width += this._getWidthOfChar(this.ctx, line[j], i, this.flipX ? jlen - j : j) * - this.scaleX; - - if (height <= mouseOffset.y || width <= mouseOffset.x) { - charIndex++; - continue; - } - - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i, jlen); - } - - if (mouseOffset.y < height) { - //this happens just on end of lines. - return this._getNewSelectionStartFromOffset( - mouseOffset, prevWidth, width, charIndex + i - 1, jlen); - } - } - - // clicked somewhere after all chars, so set at the end - if (typeof newSelectionStart === 'undefined') { - return this.text.length; - } - }, - - /** - * @private - */ - _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { - - var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, - distanceBtwNextCharAndCursor = width - mouseOffset.x, - offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, - newSelectionStart = index + offset; - - // if object is horizontally flipped, mirror cursor location from the end - if (this.flipX) { - newSelectionStart = jlen - newSelectionStart; - } - - if (newSelectionStart > this.text.length) { - newSelectionStart = this.text.length; - } - - return newSelectionStart; - } -}); - - -fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * Initializes hidden textarea (needed to bring up keyboard in iOS) - */ - initHiddenTextarea: function() { - this.hiddenTextarea = fabric.document.createElement('textarea'); - this.hiddenTextarea.setAttribute('autocapitalize', 'off'); - var style = this._calcTextareaPosition(); - this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + '; left: ' + style.left + ';' - + ' opacity: 0; width: 0px; height: 0px; z-index: -999;'; - fabric.document.body.appendChild(this.hiddenTextarea); - - fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'cut', this.cut.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this)); - fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this)); - - if (!this._clickHandlerInitialized && this.canvas) { - fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); - this._clickHandlerInitialized = true; - } - }, - - /** - * @private - */ - _keysMap: { - 8: 'removeChars', - 9: 'exitEditing', - 27: 'exitEditing', - 13: 'insertNewline', - 33: 'moveCursorUp', - 34: 'moveCursorDown', - 35: 'moveCursorRight', - 36: 'moveCursorLeft', - 37: 'moveCursorLeft', - 38: 'moveCursorUp', - 39: 'moveCursorRight', - 40: 'moveCursorDown', - 46: 'forwardDelete' - }, - - /** - * @private - */ - _ctrlKeysMapUp: { - 67: 'copy', - 88: 'cut' - }, - - /** - * @private - */ - _ctrlKeysMapDown: { - 65: 'selectAll' - }, - - onClick: function() { - // No need to trigger click event here, focus is enough to have the keyboard appear on Android - this.hiddenTextarea && this.hiddenTextarea.focus(); - }, - - /** - * Handles keyup event - * @param {Event} e Event object - */ - onKeyDown: function(e) { - if (!this.isEditing) { - return; - } - if (e.keyCode in this._keysMap) { - this[this._keysMap[e.keyCode]](e); - } - else if ((e.keyCode in this._ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) { - this[this._ctrlKeysMapDown[e.keyCode]](e); - } - else { - return; - } - e.stopImmediatePropagation(); - e.preventDefault(); - this.canvas && this.canvas.renderAll(); - }, - - /** - * Handles keyup event - * We handle KeyUp because ie11 and edge have difficulties copy/pasting - * if a copy/cut event fired, keyup is dismissed - * @param {Event} e Event object - */ - onKeyUp: function(e) { - if (!this.isEditing || this._copyDone) { - this._copyDone = false; - return; - } - if ((e.keyCode in this._ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) { - this[this._ctrlKeysMapUp[e.keyCode]](e); - } - else { - return; - } - e.stopImmediatePropagation(); - e.preventDefault(); - this.canvas && this.canvas.renderAll(); - }, - - /** - * Handles onInput event - * @param {Event} e Event object - */ - onInput: function(e) { - if (!this.isEditing || this.inCompositionMode) { - return; - } - var offset = this.selectionStart || 0, - offsetEnd = this.selectionEnd || 0, - textLength = this.text.length, - newTextLength = this.hiddenTextarea.value.length, - diff, charsToInsert, start; - if (newTextLength > textLength) { - //we added some character - start = this._selectionDirection === 'left' ? offsetEnd : offset; - diff = newTextLength - textLength; - charsToInsert = this.hiddenTextarea.value.slice(start, start + diff); - } - else { - //we selected a portion of text and then input something else. - //Internet explorer does not trigger this else - diff = newTextLength - textLength + offsetEnd - offset; - charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff); - } - this.insertChars(charsToInsert); - e.stopPropagation(); - }, - - /** - * Composition start - */ - onCompositionStart: function() { - this.inCompositionMode = true; - this.prevCompositionLength = 0; - this.compositionStart = this.selectionStart; - }, - - /** - * Composition end - */ - onCompositionEnd: function() { - this.inCompositionMode = false; - }, - - /** - * Composition update - */ - onCompositionUpdate: function(e) { - var data = e.data; - this.selectionStart = this.compositionStart; - this.selectionEnd = this.selectionEnd === this.selectionStart ? - this.compositionStart + this.prevCompositionLength : this.selectionEnd; - this.insertChars(data, false); - this.prevCompositionLength = data.length; - }, - - /** - * Forward delete - */ - forwardDelete: function(e) { - if (this.selectionStart === this.selectionEnd) { - if (this.selectionStart === this.text.length) { - return; - } - this.moveCursorRight(e); - } - this.removeChars(e); - }, - - /** - * Copies selected text - * @param {Event} e Event object - */ - copy: function(e) { - if (this.selectionStart === this.selectionEnd) { - //do not cut-copy if no selection - return; - } - var selectedText = this.getSelectedText(), - clipboardData = this._getClipboardData(e); - - // Check for backward compatibility with old browsers - if (clipboardData) { - clipboardData.setData('text', selectedText); - } - - fabric.copiedText = selectedText; - fabric.copiedTextStyle = this.getSelectionStyles( - this.selectionStart, - this.selectionEnd); - e.stopImmediatePropagation(); - e.preventDefault(); - this._copyDone = true; - }, - - /** - * Pastes text - * @param {Event} e Event object - */ - paste: function(e) { - var copiedText = null, - clipboardData = this._getClipboardData(e), - useCopiedStyle = true; - - // Check for backward compatibility with old browsers - if (clipboardData) { - copiedText = clipboardData.getData('text').replace(/\r/g, ''); - if (!fabric.copiedTextStyle || fabric.copiedText !== copiedText) { - useCopiedStyle = false; - } - } - else { - copiedText = fabric.copiedText; - } - - if (copiedText) { - this.insertChars(copiedText, useCopiedStyle); - } - e.stopImmediatePropagation(); - e.preventDefault(); - }, - - /** - * Cuts text - * @param {Event} e Event object - */ - cut: function(e) { - if (this.selectionStart === this.selectionEnd) { - return; - } - - this.copy(e); - this.removeChars(e); - }, - - /** - * @private - * @param {Event} e Event object - * @return {Object} Clipboard data object - */ - _getClipboardData: function(e) { - return (e && e.clipboardData) || fabric.window.clipboardData; - }, - - /** - * Finds the width in pixels before the cursor on the same line - * @private - * @param {Number} lineIndex - * @param {Number} charIndex - * @return {Number} widthBeforeCursor width before cursor - */ - _getWidthBeforeCursor: function(lineIndex, charIndex) { - var textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex), - widthOfLine = this._getLineWidth(this.ctx, lineIndex), - widthBeforeCursor = this._getLineLeftOffset(widthOfLine), _char; - - for (var i = 0, len = textBeforeCursor.length; i < len; i++) { - _char = textBeforeCursor[i]; - widthBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); - } - return widthBeforeCursor; - }, - - /** - * Gets start offset of a selection - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - getDownCursorOffset: function(e, isRight) { - var selectionProp = this._getSelectionForOffset(e, isRight), - cursorLocation = this.get2DCursorLocation(selectionProp), - lineIndex = cursorLocation.lineIndex; - // if on last line, down cursor goes to end of line - if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { - // move to the end of a text - return this.text.length - selectionProp; - } - var charIndex = cursorLocation.charIndex, - widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), - indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor), - textAfterCursor = this._textLines[lineIndex].slice(charIndex); - - return textAfterCursor.length + indexOnOtherLine + 2; - }, - - /** - * private - * Helps finding if the offset should be counted from Start or End - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - _getSelectionForOffset: function(e, isRight) { - if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { - return this.selectionEnd; - } - else { - return this.selectionStart; - } - }, - - /** - * @param {Event} e Event object - * @param {Boolean} isRight - * @return {Number} - */ - getUpCursorOffset: function(e, isRight) { - var selectionProp = this._getSelectionForOffset(e, isRight), - cursorLocation = this.get2DCursorLocation(selectionProp), - lineIndex = cursorLocation.lineIndex; - if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { - // if on first line, up cursor goes to start of line - return -selectionProp; - } - var charIndex = cursorLocation.charIndex, - widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), - indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor), - textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex); - // return a negative offset - return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length; - }, - - /** - * find for a given width it founds the matching character. - * @private - */ - _getIndexOnLine: function(lineIndex, width) { - - var widthOfLine = this._getLineWidth(this.ctx, lineIndex), - textOnLine = this._textLines[lineIndex], - lineLeftOffset = this._getLineLeftOffset(widthOfLine), - widthOfCharsOnLine = lineLeftOffset, - indexOnLine = 0, - foundMatch; - - for (var j = 0, jlen = textOnLine.length; j < jlen; j++) { - - var _char = textOnLine[j], - widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); - - widthOfCharsOnLine += widthOfChar; - - if (widthOfCharsOnLine > width) { - - foundMatch = true; - - var leftEdge = widthOfCharsOnLine - widthOfChar, - rightEdge = widthOfCharsOnLine, - offsetFromLeftEdge = Math.abs(leftEdge - width), - offsetFromRightEdge = Math.abs(rightEdge - width); - - indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); - - break; - } - } - - // reached end - if (!foundMatch) { - indexOnLine = textOnLine.length - 1; - } - - return indexOnLine; - }, - - - /** - * Moves cursor down - * @param {Event} e Event object - */ - moveCursorDown: function(e) { - if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { - return; - } - this._moveCursorUpOrDown('Down', e); - }, - - /** - * Moves cursor up - * @param {Event} e Event object - */ - moveCursorUp: function(e) { - if (this.selectionStart === 0 && this.selectionEnd === 0) { - return; - } - this._moveCursorUpOrDown('Up', e); - }, - - /** - * Moves cursor up or down, fires the events - * @param {String} direction 'Up' or 'Down' - * @param {Event} e Event object - */ - _moveCursorUpOrDown: function(direction, e) { - // getUpCursorOffset - // getDownCursorOffset - var action = 'get' + direction + 'CursorOffset', - offset = this[action](e, this._selectionDirection === 'right'); - if (e.shiftKey) { - this.moveCursorWithShift(offset); - } - else { - this.moveCursorWithoutShift(offset); - } - if (offset !== 0) { - this.setSelectionInBoundaries(); - this.abortCursorAnimation(); - this._currentCursorOpacity = 1; - this.initDelayedCursor(); - this._fireSelectionChanged(); - this._updateTextarea(); - } - }, - - /** - * Moves cursor with shift - * @param {Number} offset - */ - moveCursorWithShift: function(offset) { - var newSelection = this._selectionDirection === 'left' - ? this.selectionStart + offset - : this.selectionEnd + offset; - this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); - return offset !== 0; - }, - - /** - * Moves cursor up without shift - * @param {Number} offset - */ - moveCursorWithoutShift: function(offset) { - if (offset < 0) { - this.selectionStart += offset; - this.selectionEnd = this.selectionStart; - } - else { - this.selectionEnd += offset; - this.selectionStart = this.selectionEnd; - } - return offset !== 0; - }, - - /** - * Moves cursor left - * @param {Event} e Event object - */ - moveCursorLeft: function(e) { - if (this.selectionStart === 0 && this.selectionEnd === 0) { - return; - } - this._moveCursorLeftOrRight('Left', e); - }, - - /** - * @private - * @return {Boolean} true if a change happened - */ - _move: function(e, prop, direction) { - var newValue; - if (e.altKey) { - newValue = this['findWordBoundary' + direction](this[prop]); - } - else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { - newValue = this['findLineBoundary' + direction](this[prop]); - } - else { - this[prop] += direction === 'Left' ? -1 : 1; - return true; - } - if (typeof newValue !== undefined && this[prop] !== newValue) { - this[prop] = newValue; - return true; - } - }, - - /** - * @private - */ - _moveLeft: function(e, prop) { - return this._move(e, prop, 'Left'); - }, - - /** - * @private - */ - _moveRight: function(e, prop) { - return this._move(e, prop, 'Right'); - }, - - /** - * Moves cursor left without keeping selection - * @param {Event} e - */ - moveCursorLeftWithoutShift: function(e) { - var change = true; - this._selectionDirection = 'left'; - - // only move cursor when there is no selection, - // otherwise we discard it, and leave cursor on same place - if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) { - change = this._moveLeft(e, 'selectionStart'); - - } - this.selectionEnd = this.selectionStart; - return change; - }, - - /** - * Moves cursor left while keeping selection - * @param {Event} e - */ - moveCursorLeftWithShift: function(e) { - if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { - return this._moveLeft(e, 'selectionEnd'); - } - else if (this.selectionStart !== 0){ - this._selectionDirection = 'left'; - return this._moveLeft(e, 'selectionStart'); - } - }, - - /** - * Moves cursor right - * @param {Event} e Event object - */ - moveCursorRight: function(e) { - if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { - return; - } - this._moveCursorLeftOrRight('Right', e); - }, - - /** - * Moves cursor right or Left, fires event - * @param {String} direction 'Left', 'Right' - * @param {Event} e Event object - */ - _moveCursorLeftOrRight: function(direction, e) { - var actionName = 'moveCursor' + direction + 'With'; - this._currentCursorOpacity = 1; - - if (e.shiftKey) { - actionName += 'Shift'; - } - else { - actionName += 'outShift'; - } - if (this[actionName](e)) { - this.abortCursorAnimation(); - this.initDelayedCursor(); - this._fireSelectionChanged(); - this._updateTextarea(); - } - }, - - /** - * Moves cursor right while keeping selection - * @param {Event} e - */ - moveCursorRightWithShift: function(e) { - if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { - return this._moveRight(e, 'selectionStart'); - } - else if (this.selectionEnd !== this.text.length) { - this._selectionDirection = 'right'; - return this._moveRight(e, 'selectionEnd'); - } - }, - - /** - * Moves cursor right without keeping selection - * @param {Event} e Event object - */ - moveCursorRightWithoutShift: function(e) { - var changed = true; - this._selectionDirection = 'right'; - - if (this.selectionStart === this.selectionEnd) { - changed = this._moveRight(e, 'selectionStart'); - this.selectionEnd = this.selectionStart; - } - else { - this.selectionStart = this.selectionEnd; - } - return changed; - }, - - /** - * Removes characters selected by selection - * @param {Event} e Event object - */ - removeChars: function(e) { - if (this.selectionStart === this.selectionEnd) { - this._removeCharsNearCursor(e); - } - else { - this._removeCharsFromTo(this.selectionStart, this.selectionEnd); - } - - this.setSelectionEnd(this.selectionStart); - - this._removeExtraneousStyles(); - - this.canvas && this.canvas.renderAll(); - - this.setCoords(); - this.fire('changed'); - this.canvas && this.canvas.fire('text:changed', { target: this }); - }, - - /** - * @private - * @param {Event} e Event object - */ - _removeCharsNearCursor: function(e) { - if (this.selectionStart === 0) { - return; - } - if (e.metaKey) { - // remove all till the start of current line - var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); - - this._removeCharsFromTo(leftLineBoundary, this.selectionStart); - this.setSelectionStart(leftLineBoundary); - } - else if (e.altKey) { - // remove all till the start of current word - var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); - - this._removeCharsFromTo(leftWordBoundary, this.selectionStart); - this.setSelectionStart(leftWordBoundary); - } - else { - this._removeSingleCharAndStyle(this.selectionStart); - this.setSelectionStart(this.selectionStart - 1); - } - } -}); - - -/* _TO_SVG_START_ */ -(function() { - var toFixed = fabric.util.toFixed, - NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; - - fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - - /** - * @private - */ - _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) { - if (!this._getLineStyle(lineIndex)) { - fabric.Text.prototype._setSVGTextLineText.call(this, - lineIndex, textSpans, height, textLeftOffset, textTopOffset); - } - else { - this._setSVGTextLineChars( - lineIndex, textSpans, height, textLeftOffset, textBgRects); - } - }, - - /** - * @private - */ - _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) { - - var chars = this._textLines[lineIndex], - charOffset = 0, - lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - this.width / 2, - lineOffset = this._getSVGLineTopOffset(lineIndex), - heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); - - for (var i = 0, len = chars.length; i < len; i++) { - var styleDecl = this._getStyleDeclaration(lineIndex, i) || { }; - - textSpans.push( - this._createTextCharSpan( - chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset)); - - var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); - - if (styleDecl.textBackgroundColor) { - textBgRects.push( - this._createTextCharBg( - styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset)); - } - - charOffset += charWidth; - } - }, - - /** - * @private - */ - _getSVGLineTopOffset: function(lineIndex) { - var lineTopOffset = 0, lastHeight = 0; - for (var j = 0; j < lineIndex; j++) { - lineTopOffset += this._getHeightOfLine(this.ctx, j); - } - lastHeight = this._getHeightOfLine(this.ctx, j); - return { - lineTop: lineTopOffset, - offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) - }; - }, - - /** - * @private - */ - _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { - return [ - '\t\t\n' - ].join(''); - }, - - /** - * @private - */ - _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) { - - var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ - visible: true, - fill: this.fill, - stroke: this.stroke, - type: 'text', - getSvgFilter: fabric.Object.prototype.getSvgFilter - }, styleDecl)); - - return [ - '\t\t\t', - fabric.util.string.escapeXml(_char), - '\n' - ].join(''); - } - }); -})(); -/* _TO_SVG_END_ */ - - -(function(global) { - - 'use strict'; - - var fabric = global.fabric || (global.fabric = {}), - clone = fabric.util.object.clone; - - /** - * Textbox class, based on IText, allows the user to resize the text rectangle - * and wraps lines automatically. Textboxes have their Y scaling locked, the - * user can only change width. Height is adjusted automatically based on the - * wrapping of lines. - * @class fabric.Textbox - * @extends fabric.IText - * @mixes fabric.Observable - * @return {fabric.Textbox} thisArg - * @see {@link fabric.Textbox#initialize} for constructor definition - */ - fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { - - /** - * Type of an object - * @type String - * @default - */ - type: 'textbox', - - /** - * Minimum width of textbox, in pixels. - * @type Number - * @default - */ - minWidth: 20, - - /** - * Minimum calculated width of a textbox, in pixels. - * fixed to 2 so that an empty textbox cannot go to 0 - * and is still selectable without text. - * @type Number - * @default - */ - dynamicMinWidth: 2, - - /** - * Cached array of text wrapping. - * @type Array - */ - __cachedLines: null, - - /** - * Override standard Object class values - */ - lockScalingY: true, - - /** - * Override standard Object class values - */ - lockScalingFlip: true, - - /** - * Constructor. Some scaling related property values are forced. Visibility - * of controls is also fixed; only the rotation and width controls are - * made available. - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Textbox} thisArg - */ - initialize: function(text, options) { - this.ctx = fabric.util.createCanvasElement().getContext('2d'); - this.callSuper('initialize', text, options); - this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); - - // add width to this list of props that effect line wrapping. - this._dimensionAffectingProps.width = true; - }, - - /** - * Unlike superclass's version of this function, Textbox does not update - * its width. - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @private - * @override - */ - _initDimensions: function(ctx) { - if (this.__skipDimension) { - return; - } - - if (!ctx) { - ctx = fabric.util.createCanvasElement().getContext('2d'); - this._setTextStyles(ctx); - } - - // clear dynamicMinWidth as it will be different after we re-wrap line - this.dynamicMinWidth = 0; - - // wrap lines - this._textLines = this._splitTextIntoLines(); - // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap - if (this.dynamicMinWidth > this.width) { - this._set('width', this.dynamicMinWidth); - } - - // clear cache and re-calculate height - this._clearCache(); - this.height = this._getTextHeight(ctx); - }, - - /** - * Generate an object that translates the style object so that it is - * broken up by visual lines (new lines and automatic wrapping). - * The original text styles object is broken up by actual lines (new lines only), - * which is only sufficient for Text / IText - * @private - */ - _generateStyleMap: function() { - var realLineCount = 0, - realLineCharCount = 0, - charCount = 0, - map = {}; - - for (var i = 0; i < this._textLines.length; i++) { - if (this.text[charCount] === '\n' && i > 0) { - realLineCharCount = 0; - charCount++; - realLineCount++; - } - else if (this.text[charCount] === ' ' && i > 0) { - // this case deals with space's that are removed from end of lines when wrapping - realLineCharCount++; - charCount++; - } - - map[i] = { line: realLineCount, offset: realLineCharCount }; - - charCount += this._textLines[i].length; - realLineCharCount += this._textLines[i].length; - } - - return map; - }, - - /** - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Boolean} [returnCloneOrEmpty=false] - * @private - */ - _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) { - if (this._styleMap) { - var map = this._styleMap[lineIndex]; - if (!map) { - return returnCloneOrEmpty ? { } : null; - } - lineIndex = map.line; - charIndex = map.offset + charIndex; - } - return this.callSuper('_getStyleDeclaration', lineIndex, charIndex, returnCloneOrEmpty); - }, - - /** - * @param {Number} lineIndex - * @param {Number} charIndex - * @param {Object} style - * @private - */ - _setStyleDeclaration: function(lineIndex, charIndex, style) { - var map = this._styleMap[lineIndex]; - lineIndex = map.line; - charIndex = map.offset + charIndex; - - this.styles[lineIndex][charIndex] = style; - }, - - /** - * @param {Number} lineIndex - * @param {Number} charIndex - * @private - */ - _deleteStyleDeclaration: function(lineIndex, charIndex) { - var map = this._styleMap[lineIndex]; - lineIndex = map.line; - charIndex = map.offset + charIndex; - - delete this.styles[lineIndex][charIndex]; - }, - - /** - * @param {Number} lineIndex - * @private - */ - _getLineStyle: function(lineIndex) { - var map = this._styleMap[lineIndex]; - return this.styles[map.line]; - }, - - /** - * @param {Number} lineIndex - * @param {Object} style - * @private - */ - _setLineStyle: function(lineIndex, style) { - var map = this._styleMap[lineIndex]; - this.styles[map.line] = style; - }, - - /** - * @param {Number} lineIndex - * @private - */ - _deleteLineStyle: function(lineIndex) { - var map = this._styleMap[lineIndex]; - delete this.styles[map.line]; - }, - - /** - * Wraps text using the 'width' property of Textbox. First this function - * splits text on newlines, so we preserve newlines entered by the user. - * Then it wraps each line using the width of the Textbox by calling - * _wrapLine(). - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @param {String} text The string of text that is split into lines - * @returns {Array} Array of lines - */ - _wrapText: function(ctx, text) { - var lines = text.split(this._reNewline), wrapped = [], i; - - for (i = 0; i < lines.length; i++) { - wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i)); - } - - return wrapped; - }, - - /** - * Helper function to measure a string of text, given its lineIndex and charIndex offset - * - * @param {CanvasRenderingContext2D} ctx - * @param {String} text - * @param {number} lineIndex - * @param {number} charOffset - * @returns {number} - * @private - */ - _measureText: function(ctx, text, lineIndex, charOffset) { - var width = 0; - charOffset = charOffset || 0; - for (var i = 0, len = text.length; i < len; i++) { - width += this._getWidthOfChar(ctx, text[i], lineIndex, i + charOffset); - } - return width; - }, - - /** - * Wraps a line of text using the width of the Textbox and a context. - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @param {String} text The string of text to split into lines - * @param {Number} lineIndex - * @returns {Array} Array of line(s) into which the given text is wrapped - * to. - */ - _wrapLine: function(ctx, text, lineIndex) { - var lineWidth = 0, - lines = [], - line = '', - words = text.split(' '), - word = '', - offset = 0, - infix = ' ', - wordWidth = 0, - infixWidth = 0, - largestWordWidth = 0, - lineJustStarted = true, - additionalSpace = this._getWidthOfCharSpacing(); - - for (var i = 0; i < words.length; i++) { - word = words[i]; - wordWidth = this._measureText(ctx, word, lineIndex, offset); - - offset += word.length; - - lineWidth += infixWidth + wordWidth - additionalSpace; - - if (lineWidth >= this.width && !lineJustStarted) { - lines.push(line); - line = ''; - lineWidth = wordWidth; - lineJustStarted = true; - } - else { - lineWidth += additionalSpace; - } - - if (!lineJustStarted) { - line += infix; - } - line += word; - - infixWidth = this._measureText(ctx, infix, lineIndex, offset); - offset++; - lineJustStarted = false; - // keep track of largest word - if (wordWidth > largestWordWidth) { - largestWordWidth = wordWidth; - } - } - - i && lines.push(line); - - if (largestWordWidth > this.dynamicMinWidth) { - this.dynamicMinWidth = largestWordWidth - additionalSpace; - } - - return lines; - }, - /** - * Gets lines of text to render in the Textbox. This function calculates - * text wrapping on the fly everytime it is called. - * @returns {Array} Array of lines in the Textbox. - * @override - */ - _splitTextIntoLines: function() { - var originalAlign = this.textAlign; - this.ctx.save(); - this._setTextStyles(this.ctx); - this.textAlign = 'left'; - var lines = this._wrapText(this.ctx, this.text); - this.textAlign = originalAlign; - this.ctx.restore(); - this._textLines = lines; - this._styleMap = this._generateStyleMap(); - return lines; - }, - - /** - * When part of a group, we don't want the Textbox's scale to increase if - * the group's increases. That's why we reduce the scale of the Textbox by - * the amount that the group's increases. This is to maintain the effective - * scale of the Textbox at 1, so that font-size values make sense. Otherwise - * the same font-size value would result in different actual size depending - * on the value of the scale. - * @param {String} key - * @param {*} value - */ - setOnGroup: function(key, value) { - if (key === 'scaleX') { - this.set('scaleX', Math.abs(1 / value)); - this.set('width', (this.get('width') * value) / - (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX)); - this.__oldScaleX = value; - } - }, - - /** - * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start). - * Overrides the superclass function to take into account text wrapping. - * - * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. - */ - get2DCursorLocation: function(selectionStart) { - if (typeof selectionStart === 'undefined') { - selectionStart = this.selectionStart; - } - - var numLines = this._textLines.length, - removed = 0; - - for (var i = 0; i < numLines; i++) { - var line = this._textLines[i], - lineLen = line.length; - - if (selectionStart <= removed + lineLen) { - return { - lineIndex: i, - charIndex: selectionStart - removed - }; - } - - removed += lineLen; - - if (this.text[removed] === '\n' || this.text[removed] === ' ') { - removed++; - } - } - - return { - lineIndex: numLines - 1, - charIndex: this._textLines[numLines - 1].length - }; - }, - - /** - * Overrides superclass function and uses text wrapping data to get cursor - * boundary offsets instead of the array of chars. - * @param {Array} chars Unused - * @param {String} typeOfBoundaries Can be 'cursor' or 'selection' - * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set. - */ - _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { - var topOffset = 0, - leftOffset = 0, - cursorLocation = this.get2DCursorLocation(), - lineChars = this._textLines[cursorLocation.lineIndex].split(''), - lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, cursorLocation.lineIndex)); - - for (var i = 0; i < cursorLocation.charIndex; i++) { - leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i); - } - - for (i = 0; i < cursorLocation.lineIndex; i++) { - topOffset += this._getHeightOfLine(this.ctx, i); - } - - if (typeOfBoundaries === 'cursor') { - topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) - / this.lineHeight - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) - * (1 - this._fontSizeFraction); - } - - return { - top: topOffset, - left: leftOffset, - lineLeft: lineLeftOffset - }; - }, - - getMinWidth: function() { - return Math.max(this.minWidth, this.dynamicMinWidth); - }, - - /** - * Returns object representation of an instance - * @method toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function(propertiesToInclude) { - return this.callSuper('toObject', ['minWidth'].concat(propertiesToInclude)); - } - }); - /** - * Returns fabric.Textbox instance from an object representation - * @static - * @memberOf fabric.Textbox - * @param {Object} object Object to create an instance from - * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created - * @return {fabric.Textbox} instance of fabric.Textbox - */ - fabric.Textbox.fromObject = function(object, callback) { - var textbox = new fabric.Textbox(object.text, clone(object)); - callback && callback(textbox); - return textbox; - }; - /** - * Returns the default controls visibility required for Textboxes. - * @returns {Object} - */ - fabric.Textbox.getTextboxControlVisibility = function() { - return { - tl: false, - tr: false, - br: false, - bl: false, - ml: true, - mt: false, - mr: true, - mb: false, - mtr: true - }; - }; - -})(typeof exports !== 'undefined' ? exports : this); - - -(function() { - - /** - * Override _setObjectScale and add Textbox specific resizing behavior. Resizing - * a Textbox doesn't scale text, it only changes width and makes text wrap automatically. - */ - var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale; - - fabric.Canvas.prototype._setObjectScale = function(localMouse, transform, - lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { - - var t = transform.target; - if (t instanceof fabric.Textbox) { - var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth)); - if (w >= t.getMinWidth()) { - t.set('width', w); - return true; - } - } - else { - return setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform, - lockScalingX, lockScalingY, by, lockScalingFlip, _dim); - } - }; - - /** - * Sets controls of this group to the Textbox's special configuration if - * one is present in the group. Deletes _controlsVisibility otherwise, so that - * it gets initialized to default value at runtime. - */ - fabric.Group.prototype._refreshControlsVisibility = function() { - if (typeof fabric.Textbox === 'undefined') { - return; - } - for (var i = this._objects.length; i--;) { - if (this._objects[i] instanceof fabric.Textbox) { - this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); - return; - } - } - }; - - var clone = fabric.util.object.clone; - - fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ { - /** - * @private - */ - _removeExtraneousStyles: function() { - for (var prop in this._styleMap) { - if (!this._textLines[prop]) { - delete this.styles[this._styleMap[prop].line]; - } - } - }, - - /** - * Inserts style object for a given line/char index - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Object} [style] Style object to insert, if given - */ - insertCharStyleObject: function(lineIndex, charIndex, style) { - // adjust lineIndex and charIndex - var map = this._styleMap[lineIndex]; - lineIndex = map.line; - charIndex = map.offset + charIndex; - - fabric.IText.prototype.insertCharStyleObject.apply(this, [lineIndex, charIndex, style]); - }, - - /** - * Inserts new style object - * @param {Number} lineIndex Index of a line - * @param {Number} charIndex Index of a char - * @param {Boolean} isEndOfLine True if it's end of line - */ - insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { - // adjust lineIndex and charIndex - var map = this._styleMap[lineIndex]; - lineIndex = map.line; - charIndex = map.offset + charIndex; - - fabric.IText.prototype.insertNewlineStyleObject.apply(this, [lineIndex, charIndex, isEndOfLine]); - }, - - /** - * Shifts line styles up or down. This function is slightly different than the one in - * itext_behaviour as it takes into account the styleMap. - * - * @param {Number} lineIndex Index of a line - * @param {Number} offset Can be -1 or +1 - */ - shiftLineStyles: function(lineIndex, offset) { - // shift all line styles by 1 upward - var clonedStyles = clone(this.styles), - map = this._styleMap[lineIndex]; - - // adjust line index - lineIndex = map.line; - - for (var line in this.styles) { - var numericLine = parseInt(line, 10); - - if (numericLine > lineIndex) { - this.styles[numericLine + offset] = clonedStyles[numericLine]; - - if (!clonedStyles[numericLine - offset]) { - delete this.styles[numericLine]; - } - } - } - //TODO: evaluate if delete old style lines with offset -1 - }, - - /** - * Figure out programatically the text on previous actual line (actual = separated by \n); - * - * @param {Number} lIndex - * @returns {String} - * @private - */ - _getTextOnPreviousLine: function(lIndex) { - var textOnPreviousLine = this._textLines[lIndex - 1]; - - while (this._styleMap[lIndex - 2] && this._styleMap[lIndex - 2].line === this._styleMap[lIndex - 1].line) { - textOnPreviousLine = this._textLines[lIndex - 2] + textOnPreviousLine; - - lIndex--; - } - - return textOnPreviousLine; - }, - - /** - * Removes style object - * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line - * @param {Number} [index] Optional index. When not given, current selectionStart is used. - */ - removeStyleObject: function(isBeginningOfLine, index) { - - var cursorLocation = this.get2DCursorLocation(index), - map = this._styleMap[cursorLocation.lineIndex], - lineIndex = map.line, - charIndex = map.offset + cursorLocation.charIndex; - this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex); - } - }); -})(); - - -(function() { - var override = fabric.IText.prototype._getNewSelectionStartFromOffset; - /** - * Overrides the IText implementation and adjusts character index as there is not always a linebreak - * - * @param {Number} mouseOffset - * @param {Number} prevWidth - * @param {Number} width - * @param {Number} index - * @param {Number} jlen - * @returns {Number} - */ - fabric.IText.prototype._getNewSelectionStartFromOffset = function(mouseOffset, prevWidth, width, index, jlen) { - index = override.call(this, mouseOffset, prevWidth, width, index, jlen); - - // the index passed into the function is padded by the amount of lines from _textLines (to account for \n) - // we need to remove this padding, and pad it by actual lines, and / or spaces that are meant to be there - var tmp = 0, - removed = 0; - - // account for removed characters - for (var i = 0; i < this._textLines.length; i++) { - tmp += this._textLines[i].length; - - if (tmp + removed >= index) { - break; - } - - if (this.text[tmp + removed] === '\n' || this.text[tmp + removed] === ' ') { - removed++; - } - } - - return index - i + removed; - }; -})(); - - -(function() { - - if (typeof document !== 'undefined' && typeof window !== 'undefined') { - return; - } - - var DOMParser = require('xmldom').DOMParser, - URL = require('url'), - HTTP = require('http'), - HTTPS = require('https'), - - Canvas = require('canvas'), - Image = require('canvas').Image; - - /** @private */ - function request(url, encoding, callback) { - var oURL = URL.parse(url); - - // detect if http or https is used - if ( !oURL.port ) { - oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80; - } - - // assign request handler based on protocol - var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP, - req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' - }, function(response) { - var body = ''; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; - } - }); - }); - - req.on('error', function(err) { - if (err.errno === process.ECONNREFUSED) { - fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port); - } - else { - fabric.log(err.message); - } - callback(null); - }); - - req.end(); - } - - /** @private */ - function requestFs(path, callback) { - var fs = require('fs'); - fs.readFile(path, function (err, data) { - if (err) { - fabric.log(err); - throw err; - } - else { - callback(data); - } - }); - } - - fabric.util.loadImage = function(url, callback, context) { - function createImageAndCallBack(data) { - if (data) { - img.src = new Buffer(data, 'binary'); - // preserving original url, which seems to be lost in node-canvas - img._src = url; - callback && callback.call(context, img); - } - else { - img = null; - callback && callback.call(context, null, true); - } - } - var img = new Image(); - if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { - img.src = img._src = url; - callback && callback.call(context, img); - } - else if (url && url.indexOf('http') !== 0) { - requestFs(url, createImageAndCallBack); - } - else if (url) { - request(url, 'binary', createImageAndCallBack); - } - else { - callback && callback.call(context, url); - } - }; - - fabric.loadSVGFromURL = function(url, callback, reviver) { - url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); - if (url.indexOf('http') !== 0) { - requestFs(url, function(body) { - fabric.loadSVGFromString(body.toString(), callback, reviver); - }); - } - else { - request(url, '', function(body) { - fabric.loadSVGFromString(body, callback, reviver); - }); - } - }; - - fabric.loadSVGFromString = function(string, callback, reviver) { - var doc = new DOMParser().parseFromString(string); - fabric.parseSVGDocument(doc.documentElement, function(results, options) { - callback && callback(results, options); - }, reviver); - }; - - fabric.util.getScript = function(url, callback) { - request(url, '', function(body) { - // eslint-disable-next-line no-eval - eval(body); - callback && callback(); - }); - }; - - // fabric.util.createCanvasElement = function(_, width, height) { - // return new Canvas(width, height); - // } - - /** - * Only available when running fabric on node.js - * @param {Number} width Canvas width - * @param {Number} height Canvas height - * @param {Object} [options] Options to pass to FabricCanvas. - * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas. - * @return {Object} wrapped canvas instance - */ - fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { - nodeCanvasOptions = nodeCanvasOptions || options; - - var canvasEl = fabric.document.createElement('canvas'), - nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions), - nodeCacheCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); - - // jsdom doesn't create style on canvas element, so here be temp. workaround - canvasEl.style = { }; - - canvasEl.width = nodeCanvas.width; - canvasEl.height = nodeCanvas.height; - options = options || { }; - options.nodeCanvas = nodeCanvas; - options.nodeCacheCanvas = nodeCacheCanvas; - var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, - fabricCanvas = new FabricCanvas(canvasEl, options); - fabricCanvas.nodeCanvas = nodeCanvas; - fabricCanvas.nodeCacheCanvas = nodeCacheCanvas; - fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); - fabricCanvas.contextCache = nodeCacheCanvas.getContext('2d'); - fabricCanvas.Font = Canvas.Font; - return fabricCanvas; - }; - - var originaInitStatic = fabric.StaticCanvas.prototype._initStatic; - fabric.StaticCanvas.prototype._initStatic = function(el, options) { - el = el || fabric.document.createElement('canvas'); - this.nodeCanvas = new Canvas(el.width, el.height); - this.nodeCacheCanvas = new Canvas(el.width, el.height); - originaInitStatic.call(this, el, options); - this.contextContainer = this.nodeCanvas.getContext('2d'); - this.contextCache = this.nodeCacheCanvas.getContext('2d'); - this.Font = Canvas.Font; - } - - /** @ignore */ - fabric.StaticCanvas.prototype.createPNGStream = function() { - return this.nodeCanvas.createPNGStream(); - }; - - fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { - return this.nodeCanvas.createJPEGStream(opts); - }; - - fabric.StaticCanvas.prototype._initRetinaScaling = function() { - if (!this._isRetinaScaling()) { - return; - } - - this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio); - this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio); - this.nodeCanvas.width = this.width * fabric.devicePixelRatio; - this.nodeCanvas.height = this.height * fabric.devicePixelRatio; - this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio); - return this; - }; - if (fabric.Canvas) { - fabric.Canvas.prototype._initRetinaScaling = fabric.StaticCanvas.prototype._initRetinaScaling; - } - - var origSetBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension; - fabric.StaticCanvas.prototype._setBackstoreDimension = function(prop, value) { - origSetBackstoreDimension.call(this, prop, value); - this.nodeCanvas[prop] = value; - return this; - }; - if (fabric.Canvas) { - fabric.Canvas.prototype._setBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension; - } - -})(); -