http://git-wip-us.apache.org/repos/asf/openmeetings/blob/445e51e0/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js ---------------------------------------------------------------------- diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js index a8931f7..236a9ce 100644 --- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js @@ -1,8 +1,8 @@ /* Licensed MIT https://github.com/kangax/fabric.js/blob/master/LICENSE */ -/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ +/* build: `node build.js modules=ALL exclude=gestures,accessors minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.7.20" }; +var fabric = fabric || { version: '2.0.0-rc.1' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -10,28 +10,24 @@ if (typeof exports !== 'undefined') { 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") + fabric.document = require('jsdom') .jsdom( - decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E") - ); - - if (fabric.document.createWindow) { - fabric.window = fabric.document.createWindow(); - } else { - fabric.window = fabric.document.parentWindow; - } + decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'), + { features: { + FetchExternalResources: ['img'] + } + }); + fabric.window = fabric.document.defaultView; + DOMParser = require('xmldom').DOMParser; } /** * True when in environment that supports touch events * @type boolean */ - fabric.isTouchSupported = 'ontouchstart' in fabric.window; /** @@ -54,7 +50,8 @@ fabric.SHARED_ATTRIBUTES = [ "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", - "id" + "id", "paint-order", + "instantiated_by_use" ]; /* _FROM_SVG_END_ */ @@ -97,6 +94,25 @@ fabric.minCacheSideLimit = 256; fabric.charWidthsCache = { }; /** + * if webgl is enabled and available, textureSize will determine the size + * of the canvas backend + * @since 2.0.0 + * @type Number + * @default + */ +fabric.textureSize = 2048; + +/** + * Enable webgl for filtering picture is available + * A filtering backend will be initialized, this will both take memory and + * time since a default 2048x2048 canvas will be created for the gl context + * @since 2.0.0 + * @type Boolean + * @default + */ +fabric.enableGLFiltering = true; + +/** * Device Pixel Ratio * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html */ @@ -104,6 +120,37 @@ fabric.devicePixelRatio = fabric.window.devicePixelRatio || fabric.window.webkitDevicePixelRatio || fabric.window.mozDevicePixelRatio || 1; +/** + * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value, + * which is unitless and not rendered equally across browsers. + * + * Values that work quite well (as of October 2017) are: + * - Chrome: 1.5 + * - Edge: 1.75 + * - Firefox: 0.9 + * - Safari: 0.95 + * + * @since 2.0.0 + * @type Number + * @default 1 + */ +fabric.browserShadowBlurConstant = 1; + +fabric.initFilterBackend = function() { + if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) { + console.log('max texture size: ' + fabric.maxTextureSize); + return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize })); + } + else if (fabric.Canvas2dFilterBackend) { + return (new fabric.Canvas2dFilterBackend()); + } +}; + + +if (typeof document !== 'undefined' && typeof window !== 'undefined') { + // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) + window.fabric = fabric; +} (function() { @@ -261,7 +308,7 @@ fabric.Collection = { this._onObjectAdded(arguments[i]); } } - this.renderOnAddRemove && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -286,7 +333,7 @@ fabric.Collection = { objects.splice(index, 0, object); } this._onObjectAdded && this._onObjectAdded(object); - this.renderOnAddRemove && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -311,7 +358,7 @@ fabric.Collection = { } } - this.renderOnAddRemove && somethingRemoved && this.renderAll(); + this.renderOnAddRemove && somethingRemoved && this.requestRenderAll(); return this; }, @@ -642,11 +689,11 @@ fabric.CommonMethods = { 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), + width = maxX - minX, 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); + height = maxY - minY; return { left: minX, @@ -745,6 +792,33 @@ fabric.CommonMethods = { }, /** + * Returns array of attributes for given svg that fabric parses + * @memberOf fabric.util + * @param {String} type Type of svg element (eg. 'circle') + * @return {Array} string names of supported attributes + */ + getSvgAttributes: function(type) { + var attributes = [ + 'instantiated_by_use', + 'style', + 'id', + 'class' + ]; + switch (type) { + case 'linearGradient': + attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); + break; + case 'radialGradient': + attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); + break; + case 'stop': + attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']); + break; + } + return attributes; + }, + + /** * Returns object of given namespace * @memberOf fabric.util * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' @@ -783,11 +857,12 @@ fabric.CommonMethods = { var img = fabric.util.createImage(); /** @ignore */ - img.onload = function () { + var onLoadCallback = function () { callback && callback.call(context, img); img = img.onload = img.onerror = null; }; + img.onload = onLoadCallback; /** @ignore */ img.onerror = function() { fabric.log('Error loading ' + img.src); @@ -803,10 +878,44 @@ fabric.CommonMethods = { img.crossOrigin = crossOrigin; } + // IE10 / IE11-Fix: SVG contents from data: URI + // will only be available if the IMG is present + // in the DOM (and visible) + if (url.substring(0,14) === 'data:image/svg') { + img.onload = null; + fabric.util.loadImageInDom(img, onLoadCallback); + } + img.src = url; }, /** + * Attaches SVG image with data: URL to the dom + * @memberOf fabric.util + * @param {Object} img Image object with data:image/svg src + * @param {Function} callback Callback; invoked with loaded image + * @return {Object} DOM element (div containing the SVG image) + */ + loadImageInDom: function(img, onLoadCallback) { + var div = fabric.document.createElement('div'); + div.style.width = div.style.height = '1px'; + div.style.left = div.style.top = '-100%'; + div.style.position = 'absolute'; + div.appendChild(img); + fabric.document.querySelector('body').appendChild(div); + /** + * Wrap in function to: + * 1. Call existing callback + * 2. Cleanup DOM + */ + img.onload = function () { + onLoadCallback(); + div.parentNode.removeChild(div); + div = null; + }; + }, + + /** * Creates corresponding fabric instances from their object representations * @static * @memberOf fabric.util @@ -827,8 +936,7 @@ fabric.CommonMethods = { var enlivenedObjects = [], numLoadedObjects = 0, - numTotalObjects = objects.length, - forceAsync = true; + numTotalObjects = objects.length; if (!numTotalObjects) { callback && callback(enlivenedObjects); @@ -846,7 +954,7 @@ fabric.CommonMethods = { error || (enlivenedObjects[index] = obj); reviver && reviver(o, obj, error); onLoaded(); - }, forceAsync); + }); }); }, @@ -854,10 +962,8 @@ fabric.CommonMethods = { * Create and wait for loading of patterns * @static * @memberOf fabric.util - * @param {Array} objects Objects to enliven + * @param {Array} patterns 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. */ enlivenPatterns: function(patterns, callback) { @@ -899,13 +1005,26 @@ fabric.CommonMethods = { * @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} + * @return {fabric.Object|fabric.Group} */ groupSVGElements: function(elements, options, path) { var object; - - object = new fabric.PathGroup(elements, options); - + if (elements.length === 1) { + return elements[0]; + } + if (options) { + if (options.width && options.height) { + options.centerPoint = { + x: options.width / 2, + y: options.height / 2 + }; + } + else { + delete options.width; + delete options.height; + } + } + object = new fabric.Group(elements, options); if (typeof path !== 'undefined') { object.sourcePath = path; } @@ -918,7 +1037,7 @@ fabric.CommonMethods = { * @memberOf fabric.util * @param {Object} source Source object * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include + * @return {Array} properties Properties names to include */ populateWithProperties: function(source, destination, properties) { if (properties && Object.prototype.toString.call(properties) === '[object Array]') { @@ -971,21 +1090,13 @@ fabric.CommonMethods = { }, /** - * Creates canvas element and initializes it via excanvas if necessary + * Creates canvas element * @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; + createCanvasElement: function() { + return fabric.document.createElement('canvas'); }, /** @@ -995,45 +1106,13 @@ fabric.CommonMethods = { * @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) { - return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - return new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } + return fabric.document.createElement('img'); }, /** * @static * @memberOf fabric.util + * @deprecated since 2.0.0 * @param {fabric.Object} receiver Object implementing `clipTo` method * @param {CanvasRenderingContext2D} ctx Context to clip */ @@ -1102,7 +1181,7 @@ fabric.CommonMethods = { target.skewY = 0; target.flipX = false; target.flipY = false; - target.setAngle(0); + target.rotate(0); }, /** @@ -1207,7 +1286,6 @@ fabric.CommonMethods = { * @memberOf fabric.util * @param {Number} ar aspect ratio * @param {Number} maximumArea Maximum area you want to achieve - * @param {Number} maximumSide biggest side allowed * @return {Object.x} Limited dimensions by X * @return {Object.y} Limited dimensions by Y */ @@ -1219,9 +1297,16 @@ fabric.CommonMethods = { capValue: function(min, value, max) { return Math.max(min, Math.min(value, max)); + }, + + findScaleToFit: function(source, destination) { + return Math.min(destination.width / source.width, destination.height / source.height); + }, + + findScaleToCover: function(source, destination) { + return Math.max(destination.width / source.width, destination.height / source.height); } }; - })(typeof exports !== 'undefined' ? exports : this); @@ -1490,172 +1575,6 @@ fabric.CommonMethods = { 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 @@ -1796,6 +1715,7 @@ fabric.CommonMethods = { /** * Creates an empty object and copies all enumerable properties of another object to it * @memberOf fabric.util.object + * TODO: this function return an empty object if you try to clone null * @param {Object} object Object to clone * @return {Object} */ @@ -1808,26 +1728,12 @@ fabric.CommonMethods = { extend: extend, clone: clone }; - + fabric.util.object.extend(fabric.util, fabric.Observable); })(); (function() { - /* _ES5_COMPAT_START_ */ - if (!String.prototype.trim) { - /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a> - */ - 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 @@ -1862,61 +1768,80 @@ fabric.CommonMethods = { */ function escapeXml(string) { return string.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/</g, '<') - .replace(/>/g, '>'); + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>'); } /** - * String utilities - * @namespace fabric.util.string + * Divide a string in the user perceived single units + * @memberOf fabric.util.string + * @param {String} textstring String to escape + * @return {Array} array containing the graphemes */ - fabric.util.string = { - camelize: camelize, - capitalize: capitalize, - escapeXml: escapeXml - }; -})(); - + function graphemeSplit(textstring) { + var i = 0, chr, graphemes = []; + for (i = 0, chr; i < textstring.length; i++) { + if ((chr = getWholeChar(textstring, i)) === false) { + continue; + } + graphemes.push(chr); + } + return graphemes; + } -/* _ES5_COMPAT_START_ */ -(function() { + // taken from mdn in the charAt doc page. + function getWholeChar(str, i) { + var code = str.charCodeAt(i); - var slice = Array.prototype.slice, - apply = Function.prototype.apply, - Dummy = function() { }; + if (isNaN(code)) { + return ''; // Position not found + } + if (code < 0xD800 || code > 0xDFFF) { + return str.charAt(i); + } - if (!Function.prototype.bind) { - /** - * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) - * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a> - * @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))); - }; + // High surrogate (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 <= code && code <= 0xDBFF) { + if (str.length <= (i + 1)) { + throw 'High surrogate without following low surrogate'; } - else { - /** @ignore */ - bound = function() { - return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); - }; + var next = str.charCodeAt(i + 1); + if (0xDC00 > next || next > 0xDFFF) { + throw 'High surrogate without following low surrogate'; } - Dummy.prototype = this.prototype; - bound.prototype = new Dummy(); + return str.charAt(i) + str.charAt(i + 1); + } + // Low surrogate (0xDC00 <= code && code <= 0xDFFF) + if (i === 0) { + throw 'Low surrogate without preceding high surrogate'; + } + var prev = str.charCodeAt(i - 1); - return bound; - }; + // (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 > prev || prev > 0xDBFF) { + throw 'Low surrogate without preceding high surrogate'; + } + // We can pass over low surrogates now as the second component + // in a pair which we have already processed + return false; } + + /** + * String utilities + * @namespace fabric.util.string + */ + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml, + graphemeSplit: graphemeSplit + }; })(); -/* _ES5_COMPAT_END_ */ (function() { @@ -2241,14 +2166,11 @@ fabric.CommonMethods = { } 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); + return event.clientX; }, pointerY = function(event) { - return (typeof event.clientY !== unknown ? event.clientY : 0); + return event.clientY; }; function _getPointer(event, pageProp, clientProp) { @@ -2271,8 +2193,6 @@ fabric.CommonMethods = { fabric.util.getPointer = getPointer; - fabric.util.object.extend(fabric.util, fabric.Observable); - })(); @@ -2817,9 +2737,11 @@ if (typeof console !== 'undefined') { fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) { - fabric.window.setTimeout(callback, 1000 / 60); + return fabric.window.setTimeout(callback, 1000 / 60); }; + var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; + /** * 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 @@ -2831,9 +2753,13 @@ if (typeof console !== 'undefined') { return _requestAnimFrame.apply(fabric.window, arguments); } + function cancelAnimFrame() { + return _cancelAnimFrame.apply(fabric.window, arguments); + } + fabric.util.animate = animate; fabric.util.requestAnimFrame = requestAnimFrame; - + fabric.util.cancelAnimFrame = cancelAnimFrame; })(); @@ -2876,8 +2802,8 @@ if (typeof console !== 'undefined') { byValue: endColor, easing: function (currentTime, startValue, byValue, duration) { var posValue = options.colorEasing - ? options.colorEasing(currentTime, duration) - : 1 - Math.cos(currentTime / duration * (Math.PI / 2)); + ? options.colorEasing(currentTime, duration) + : 1 - Math.cos(currentTime / duration * (Math.PI / 2)); return calculateColor(startValue, byValue, posValue); } })); @@ -3304,10 +3230,11 @@ if (typeof console !== 'undefined') { 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|clipPath|mask)$/i, - reAllowedParents = /^(symbol|g|a|svg)$/i, + svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line', + 'image', 'text', 'linearGradient', 'radialGradient', 'stop'], + svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'], + svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'], + svgValidParents = ['symbol', 'g', 'a', 'svg'], attributesMap = { cx: 'left', @@ -3324,6 +3251,7 @@ if (typeof console !== 'undefined') { 'font-size': 'fontSize', 'font-style': 'fontStyle', 'font-weight': 'fontWeight', + 'paint-order': 'paintFirst', 'stroke-dasharray': 'strokeDashArray', 'stroke-linecap': 'strokeLineCap', 'stroke-linejoin': 'strokeLineJoin', @@ -3331,7 +3259,7 @@ if (typeof console !== 'undefined') { 'stroke-opacity': 'strokeOpacity', 'stroke-width': 'strokeWidth', 'text-decoration': 'textDecoration', - 'text-anchor': 'originX', + 'text-anchor': 'textAnchor', opacity: 'opacity' }, @@ -3340,6 +3268,11 @@ if (typeof console !== 'undefined') { fill: 'fillOpacity' }; + fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames); + fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements); + fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors); + fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents); + fabric.cssRules = { }; fabric.gradientDefs = { }; @@ -3378,7 +3311,7 @@ if (typeof console !== 'undefined') { } } else if (attr === 'visible') { - value = (value === 'none' || value === 'hidden') ? false : true; + value = value !== 'none' && value !== 'hidden'; // display=none on parent element always takes precedence over child element if (parentAttributes && parentAttributes.visible === false) { value = false; @@ -3390,9 +3323,20 @@ if (typeof console !== 'undefined') { value *= parentAttributes.opacity; } } - else if (attr === 'originX' /* text-anchor */) { + else if (attr === 'textAnchor' /* text-anchor */) { value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; } + else if (attr === 'paintFirst') { + var fillIndex = value.indexOf('fill'); + var strokeIndex = value.indexOf('stroke'); + var value = 'fill'; + if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) { + value = 'stroke'; + } + else if (fillIndex === -1 && strokeIndex > -1) { + value = 'stroke'; + } + } else { parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); } @@ -3401,6 +3345,13 @@ if (typeof console !== 'undefined') { } /** + * @private + */ + function getSvgRegex(arr) { + return new RegExp('^(' + arr.join('|') + ')\\b', 'i'); + } + + /** * @private * @param {Object} attributes Array of attributes to parse */ @@ -3432,8 +3383,8 @@ if (typeof console !== 'undefined') { * @private */ function _getMultipleNodes(doc, nodeNames) { - var nodeName, nodeArray = [], nodeList; - for (var i = 0; i < nodeNames.length; i++) { + var nodeName, nodeArray = [], nodeList, i, len; + for (i = 0, len = nodeNames.length; i < len; i++) { nodeName = nodeNames[i]; nodeList = doc.getElementsByTagName(nodeName); nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); @@ -3678,7 +3629,7 @@ if (typeof console !== 'undefined') { function selectorMatches(element, selector) { var nodeName = element.nodeName, classNames = element.getAttribute('class'), - id = element.getAttribute('id'), matcher; + id = element.getAttribute('id'), matcher, i; // i check if a selector matches slicing away part from it. // if i get empty string i should match matcher = new RegExp('^' + nodeName, 'i'); @@ -3689,7 +3640,7 @@ if (typeof console !== 'undefined') { } if (classNames && selector.length) { classNames = classNames.split(' '); - for (var i = classNames.length; i--;) { + for (i = classNames.length; i--;) { matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); selector = selector.replace(matcher, ''); } @@ -3707,8 +3658,8 @@ if (typeof console !== 'undefined') { if (el) { return el; } - var node, i, nodelist = doc.getElementsByTagName('*'); - for (i = 0; i < nodelist.length; i++) { + var node, i, len, nodelist = doc.getElementsByTagName('*'); + for (i = 0, len = nodelist.length; i < len; i++) { node = nodelist[i]; if (id === node.getAttribute('id')) { return node; @@ -3729,12 +3680,12 @@ if (typeof console !== 'undefined') { 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; + parentNode, oldLength = nodelist.length, attr, j, attrs, len; 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++) { + for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) { attr = attrs.item(j); el3.setAttribute(attr.nodeName, attr.nodeValue); } @@ -3745,7 +3696,7 @@ if (typeof console !== 'undefined') { el2 = el3; } - for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { + for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) { attr = attrs.item(j); if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { continue; @@ -3798,7 +3749,7 @@ if (typeof console !== 'undefined') { x = element.getAttribute('x') || 0, y = element.getAttribute('y') || 0, preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', - missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName) + missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName) || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), toBeParsed = missingViewBox && missingDimAttr, @@ -3902,7 +3853,7 @@ if (typeof console !== 'undefined') { parseUseDirectives(doc); - var svgUid = fabric.Object.__uid++, + var svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; @@ -3913,7 +3864,7 @@ if (typeof console !== 'undefined') { // 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++) { + for (i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; } descendants = arr; @@ -3921,8 +3872,8 @@ if (typeof console !== 'undefined') { 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 + return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) && + !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); if (!elements || (elements && !elements.length)) { @@ -3933,9 +3884,9 @@ if (typeof console !== 'undefined') { fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); fabric.cssRules[svgUid] = fabric.getCSSRules(doc); // Precedence of rules: style > class > attribute - fabric.parseElements(elements, function(instances) { + fabric.parseElements(elements, function(instances, elements) { if (callback) { - callback(instances, options); + callback(instances, options, elements, descendants); } }, clone(options), reviver, parsingOptions); }; @@ -4049,7 +4000,7 @@ if (typeof console !== '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)) { + if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) { parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); } fontSize = (parentAttributes && parentAttributes.fontSize ) || @@ -4077,7 +4028,7 @@ if (typeof console !== 'undefined') { fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle); } var mergedAttrs = extend(parentAttributes, normalizedStyle); - return reAllowedParents.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs); + return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs); }, /** @@ -4138,9 +4089,7 @@ if (typeof console !== 'undefined') { points = points.split(/\s+/); var parsedPoints = [], i, len; - i = 0; - len = points.length; - for (; i < len; i += 2) { + for (i = 0, len = points.length; i < len; i += 2) { parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i + 1]) @@ -4164,11 +4113,11 @@ if (typeof console !== 'undefined') { * @return {Object} CSS rules of this document */ getCSSRules: function(doc) { - var styles = doc.getElementsByTagName('style'), + var styles = doc.getElementsByTagName('style'), i, len, allRules = { }, rules; // very crude parsing of style contents - for (var i = 0, len = styles.length; i < len; i++) { + for (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; @@ -4179,13 +4128,14 @@ if (typeof console !== 'undefined') { } rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); rules = rules.map(function(rule) { return rule.trim(); }); + // eslint-disable-next-line no-loop-func 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++) { + for (i = 0, len = propertyValuePairs.length; i < len; i++) { var pair = propertyValuePairs[i].split(/\s*:\s*/), property = pair[0], value = pair[1]; @@ -4240,8 +4190,8 @@ if (typeof console !== 'undefined') { callback && callback(null); } - fabric.parseSVGDocument(xml.documentElement, function (results, _options) { - callback && callback(results, _options); + fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) { + callback && callback(results, _options, elements, allElements); }, reviver, options); } }, @@ -4271,8 +4221,8 @@ if (typeof console !== 'undefined') { doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); } - fabric.parseSVGDocument(doc.documentElement, function (results, _options) { - callback(results, _options); + fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) { + callback(results, _options, elements, allElements); }, reviver, options); } }); @@ -4323,24 +4273,19 @@ fabric.ElementsParser.prototype.createObject = function(el, index) { }; 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(); - } + klass.fromElement(el, this.createCallback(index, el), this.options); }; fabric.ElementsParser.prototype.createCallback = function(index, el) { var _this = this; return function(obj) { + var _options; _this.resolveGradient(obj, 'fill'); _this.resolveGradient(obj, 'stroke'); + if (obj instanceof fabric.Image) { + _options = obj.parsePreserveAspectRatioAttribute(el); + } + obj._removeTransformMatrix(_options); _this.reviver && _this.reviver(el, obj); _this.instances[index] = obj; _this.checkIfDone(); @@ -4366,7 +4311,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { // eslint-disable-next-line no-eq-null, eqeqeq return el != null; }); - this.callback(this.instances); + this.callback(this.instances, this.elements); } }; @@ -4491,7 +4436,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { }, /** - * Miltiplies this point by a value and returns a new one + * Multiplies this point by a value and returns a new one * TODO: rename in scalarMultiply in 2.0 * @param {Number} scalar * @return {fabric.Point} @@ -4501,7 +4446,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { }, /** - * Miltiplies this point by a value + * Multiplies this point by a value * TODO: rename in scalarMultiplyEquals in 2.0 * @param {Number} scalar * @return {fabric.Point} thisArg @@ -4812,9 +4757,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() { fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { var result = new Intersection(), length = points.length, - b1, b2, inter; + b1, b2, inter, i; - for (var i = 0; i < length; i++) { + for (i = 0; i < length; i++) { b1 = points[i]; b2 = points[(i + 1) % length]; inter = Intersection.intersectLineLine(a1, a2, b1, b2); @@ -4836,9 +4781,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() { */ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { var result = new Intersection(), - length = points1.length; + length = points1.length, i; - for (var i = 0; i < length; i++) { + for (i = 0; i < length; i++) { var a1 = points1[i], a2 = points1[(i + 1) % length], inter = Intersection.intersectLinePolygon(a1, a2, points2); @@ -5147,9 +5092,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() { alpha = this.getAlpha(), otherAlpha = 0.5, source = this.getSource(), - otherSource = otherColor.getSource(); + otherSource = otherColor.getSource(), i; - for (var i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) { result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } @@ -5165,7 +5110,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @field * @memberOf fabric.Color */ - // eslint-disable-next-line max-len + // 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*)?\)$/; /** @@ -5185,31 +5130,161 @@ fabric.ElementsParser.prototype.checkIfDone = function() { 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 + * Map of the 148 color names with HEX code * @static * @field * @memberOf fabric.Color - * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units + * @see: https://www.w3.org/TR/css3-color/#svg-color */ 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' + aliceblue: '#F0F8FF', + antiquewhite: '#FAEBD7', + aqua: '#00FFFF', + aquamarine: '#7FFFD4', + azure: '#F0FFFF', + beige: '#F5F5DC', + bisque: '#FFE4C4', + black: '#000000', + blanchedalmond: '#FFEBCD', + blue: '#0000FF', + blueviolet: '#8A2BE2', + brown: '#A52A2A', + burlywood: '#DEB887', + cadetblue: '#5F9EA0', + chartreuse: '#7FFF00', + chocolate: '#D2691E', + coral: '#FF7F50', + cornflowerblue: '#6495ED', + cornsilk: '#FFF8DC', + crimson: '#DC143C', + cyan: '#00FFFF', + darkblue: '#00008B', + darkcyan: '#008B8B', + darkgoldenrod: '#B8860B', + darkgray: '#A9A9A9', + darkgrey: '#A9A9A9', + darkgreen: '#006400', + darkkhaki: '#BDB76B', + darkmagenta: '#8B008B', + darkolivegreen: '#556B2F', + darkorange: '#FF8C00', + darkorchid: '#9932CC', + darkred: '#8B0000', + darksalmon: '#E9967A', + darkseagreen: '#8FBC8F', + darkslateblue: '#483D8B', + darkslategray: '#2F4F4F', + darkslategrey: '#2F4F4F', + darkturquoise: '#00CED1', + darkviolet: '#9400D3', + deeppink: '#FF1493', + deepskyblue: '#00BFFF', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1E90FF', + firebrick: '#B22222', + floralwhite: '#FFFAF0', + forestgreen: '#228B22', + fuchsia: '#FF00FF', + gainsboro: '#DCDCDC', + ghostwhite: '#F8F8FF', + gold: '#FFD700', + goldenrod: '#DAA520', + gray: '#808080', + grey: '#808080', + green: '#008000', + greenyellow: '#ADFF2F', + honeydew: '#F0FFF0', + hotpink: '#FF69B4', + indianred: '#CD5C5C', + indigo: '#4B0082', + ivory: '#FFFFF0', + khaki: '#F0E68C', + lavender: '#E6E6FA', + lavenderblush: '#FFF0F5', + lawngreen: '#7CFC00', + lemonchiffon: '#FFFACD', + lightblue: '#ADD8E6', + lightcoral: '#F08080', + lightcyan: '#E0FFFF', + lightgoldenrodyellow: '#FAFAD2', + lightgray: '#D3D3D3', + lightgrey: '#D3D3D3', + lightgreen: '#90EE90', + lightpink: '#FFB6C1', + lightsalmon: '#FFA07A', + lightseagreen: '#20B2AA', + lightskyblue: '#87CEFA', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#B0C4DE', + lightyellow: '#FFFFE0', + lime: '#00FF00', + limegreen: '#32CD32', + linen: '#FAF0E6', + magenta: '#FF00FF', + maroon: '#800000', + mediumaquamarine: '#66CDAA', + mediumblue: '#0000CD', + mediumorchid: '#BA55D3', + mediumpurple: '#9370DB', + mediumseagreen: '#3CB371', + mediumslateblue: '#7B68EE', + mediumspringgreen: '#00FA9A', + mediumturquoise: '#48D1CC', + mediumvioletred: '#C71585', + midnightblue: '#191970', + mintcream: '#F5FFFA', + mistyrose: '#FFE4E1', + moccasin: '#FFE4B5', + navajowhite: '#FFDEAD', + navy: '#000080', + oldlace: '#FDF5E6', + olive: '#808000', + olivedrab: '#6B8E23', + orange: '#FFA500', + orangered: '#FF4500', + orchid: '#DA70D6', + palegoldenrod: '#EEE8AA', + palegreen: '#98FB98', + paleturquoise: '#AFEEEE', + palevioletred: '#DB7093', + papayawhip: '#FFEFD5', + peachpuff: '#FFDAB9', + peru: '#CD853F', + pink: '#FFC0CB', + plum: '#DDA0DD', + powderblue: '#B0E0E6', + purple: '#800080', + rebeccapurple: '#663399', + red: '#FF0000', + rosybrown: '#BC8F8F', + royalblue: '#4169E1', + saddlebrown: '#8B4513', + salmon: '#FA8072', + sandybrown: '#F4A460', + seagreen: '#2E8B57', + seashell: '#FFF5EE', + sienna: '#A0522D', + silver: '#C0C0C0', + skyblue: '#87CEEB', + slateblue: '#6A5ACD', + slategray: '#708090', + slategrey: '#708090', + snow: '#FFFAFA', + springgreen: '#00FF7F', + steelblue: '#4682B4', + tan: '#D2B48C', + teal: '#008080', + thistle: '#D8BFD8', + tomato: '#FF6347', + turquoise: '#40E0D0', + violet: '#EE82EE', + wheat: '#F5DEB3', + white: '#FFFFFF', + whitesmoke: '#F5F5F5', + yellow: '#FFFF00', + yellowgreen: '#9ACD32' }; /** @@ -5398,7 +5473,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, - color, colorAlpha, opacity; + color, colorAlpha, opacity, i; // convert percents to absolute values offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); @@ -5410,7 +5485,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { keyValuePairs.pop(); } - for (var i = keyValuePairs.length; i--; ) { + for (i = keyValuePairs.length; i--; ) { var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), @@ -5565,22 +5640,24 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @return {String} SVG representation of an gradient (linear/radial) */ toSVG: function(object) { - var coords = clone(this.coords, true), + var coords = clone(this.coords, true), i, len, markup, commonAttributes, colorStops = clone(this.colorStops, true), - needsSwap = coords.r1 > coords.r2; + needsSwap = coords.r1 > coords.r2, + offsetX = object.width / 2, offsetY = object.height / 2; // colorStops must be sorted ascending 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') { - coords[prop] += this.offsetX - object.width / 2; - } - else if (prop === 'y1' || prop === 'y2') { - coords[prop] += this.offsetY - object.height / 2; - } + if (object.type === 'path') { + offsetX -= object.pathOffset.x; + offsetY -= object.pathOffset.y; + } + for (var prop in coords) { + if (prop === 'x1' || prop === 'x2') { + coords[prop] += this.offsetX - offsetX; + } + else if (prop === 'y1' || prop === 'y2') { + coords[prop] += this.offsetY - offsetY; } } @@ -5619,7 +5696,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { // svg goes from internal to external radius. if radius are inverted, swap color stops. colorStops = colorStops.concat(); colorStops.reverse(); - for (var i = 0; i < colorStops.length; i++) { + for (i = 0, len = colorStops.length; i < len; i++) { colorStops[i].offset = 1 - colorStops[i].offset; } } @@ -5628,19 +5705,19 @@ fabric.ElementsParser.prototype.checkIfDone = function() { // i have to shift all colorStops and add new one in 0. var maxRadius = Math.max(coords.r1, coords.r2), percentageShift = minRadius / maxRadius; - for (var i = 0; i < colorStops.length; i++) { + for (i = 0, len = colorStops.length; i < len; i++) { colorStops[i].offset += percentageShift * (1 - colorStops[i].offset); } } } - for (var i = 0; i < colorStops.length; i++) { + for (i = 0, len = colorStops.length; i < len; i++) { var colorStop = colorStops[i]; markup.push( '<stop ', - 'offset="', (colorStop.offset * 100) + '%', - '" style="stop-color:', colorStop.color, - (colorStop.opacity !== null ? ';stop-opacity: ' + colorStop.opacity : ';'), + 'offset="', (colorStop.offset * 100) + '%', + '" style="stop-color:', colorStop.color, + (colorStop.opacity !== null ? ';stop-opacity: ' + colorStop.opacity : ';'), '"/>\n' ); } @@ -5654,27 +5731,15 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * 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); + toLive: function(ctx) { + var gradient, coords = fabric.util.object.clone(this.coords), i, len; 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); @@ -5684,7 +5749,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); } - for (var i = 0, len = this.colorStops.length; i < len; i++) { + for (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; @@ -5752,7 +5817,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', gradientTransform = el.getAttribute('gradientTransform'), colorStops = [], - coords, ellipseMatrix; + coords, ellipseMatrix, i; if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') { type = 'linear'; @@ -5768,7 +5833,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { coords = getRadialCoords(el); } - for (var i = colorStopEls.length; i--; ) { + for (i = colorStopEls.length; i--; ) { colorStops.push(getColorStop(colorStopEls[i])); } @@ -5785,6 +5850,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { if (gradientTransform || ellipseMatrix !== '') { gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); } + return gradient; }, /* _FROM_SVG_END_ */ @@ -6091,7 +6157,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * 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)") + * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px") * @return {fabric.Shadow} thisArg */ initialize: function(options) { @@ -6256,7 +6322,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { */ initialize: function(el, options) { options || (options = { }); - + this.renderAndResetBound = this.renderAndReset.bind(this); + this.requestRenderAllBound = this.requestRenderAll.bind(this); this._initStatic(el, options); }, @@ -6314,9 +6381,12 @@ fabric.ElementsParser.prototype.checkIfDone = function() { stateful: false, /** - * 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) + * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove}, + * {@link fabric.StaticCanvas.moveTo}, {@link fabric.StaticCanvas.clear} and many more, should also re-render canvas. + * Disabling this option will not give a performance boost when adding/removing a lot of objects to/from canvas at once + * since the renders are quequed and executed one per frame. + * Disabling is suggested anyway and managing the renders of the app manually is not a big effort ( canvas.requestRenderAll() ) + * Left default to true to do not break documentation and old app, fiddles. * @type Boolean * @default */ @@ -6325,6 +6395,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * 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} + * @deprecated since 2.0.0 * @type Function * @default */ @@ -6383,6 +6454,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens + * @type Boolean + * @default */ enableRetinaScaling: true, @@ -6404,8 +6477,10 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * If One of the corner of the bounding box of the object is on the canvas * the objects get rendered. * @memberOf fabric.StaticCanvas.prototype + * @type Boolean + * @default */ - skipOffscreen: false, + skipOffscreen: true, /** * @private @@ -6413,7 +6488,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @param {Object} [options] Options object */ _initStatic: function(el, options) { - var cb = fabric.StaticCanvas.prototype.renderAll.bind(this); + var cb = this.requestRenderAllBound; this._objects = []; this._createLowerCanvas(el); this._initOptions(options); @@ -6580,9 +6655,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() { }, /** - * 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 + * Sets {@link fabric.StaticCanvas#overlayColor|foreground color} for this canvas + * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set foreground color to + * @param {Function} callback Callback to invoke when foreground color is set * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} @@ -6682,14 +6757,14 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * @private */ - _createCanvasElement: function(canvasEl) { - var element = fabric.util.createCanvasElement(canvasEl); - if (!element.style) { - element.style = { }; - } + _createCanvasElement: function() { + var element = fabric.util.createCanvasElement(); if (!element) { throw CANVAS_INIT_ERROR; } + if (!element.style) { + element.style = { }; + } if (typeof element.getContext === 'undefined') { throw CANVAS_INIT_ERROR; } @@ -6725,7 +6800,13 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @param {HTMLElement} [canvasEl] */ _createLowerCanvas: function (canvasEl) { - this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(canvasEl); + // canvasEl === 'HTMLCanvasElement' does not work on jsdom/node + if (canvasEl && canvasEl.getContext) { + this.lowerCanvasEl = canvasEl; + } + else { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + } fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); @@ -6811,7 +6892,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { this.calcOffset(); if (!options.cssOnly) { - this.renderAll(); + this.requestRenderAll(); } return this; @@ -6878,17 +6959,17 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @chainable true */ setViewportTransform: function (vpt) { - var activeGroup = this._activeGroup, object, ignoreVpt = false, skipAbsolute = true; + var activeObject = this._activeObject, object, ignoreVpt = false, skipAbsolute = true, i, len; this.viewportTransform = vpt; - for (var i = 0, len = this._objects.length; i < len; i++) { + for (i = 0, len = this._objects.length; i < len; i++) { object = this._objects[i]; object.group || object.setCoords(ignoreVpt, skipAbsolute); } - if (activeGroup) { - activeGroup.setCoords(ignoreVpt, skipAbsolute); + if (activeObject && activeObject.type === 'activeSelection') { + activeObject.setCoords(ignoreVpt, skipAbsolute); } this.calcViewportBoundaries(); - this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -7015,7 +7096,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { } this.clearContext(this.contextContainer); this.fire('canvas:cleared'); - this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -7031,14 +7112,38 @@ fabric.ElementsParser.prototype.checkIfDone = function() { }, /** - * Calculate the position of the 4 corner of canvas with current viewportTransform. - * helps to determinate when an object is in the current rendering viewport using - * object absolute coordinates ( aCoords ) + * Function created to be instance bound at initialization + * used in requestAnimationFrame rendering + * @return {fabric.Canvas} instance + * @chainable + */ + renderAndReset: function() { + this.isRendering = 0; + this.renderAll(); + }, + + /** + * Append a renderAll request to next animation frame. + * a boolean flag will avoid appending more. + * @return {fabric.Canvas} instance + * @chainable + */ + requestRenderAll: function () { + if (!this.isRendering) { + this.isRendering = fabric.util.requestAnimFrame(this.renderAndResetBound); + } + return this; + }, + + /** + * Calculate the position of the 4 corner of canvas with current viewportTransform. + * helps to determinate when an object is in the current rendering viewport using + * object absolute coordinates ( aCoords ) * @return {Object} points.tl * @chainable */ calcViewportBoundaries: function() { - var points = { }, width = this.getWidth(), height = this.getHeight(), + var points = { }, width = this.width, height = this.height, iVpt = invertTransform(this.viewportTransform); points.tl = transformPoint({ x: 0, y: 0 }, iVpt); points.br = transformPoint({ x: width, y: height }, iVpt); @@ -7056,6 +7161,11 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @chainable */ renderCanvas: function(ctx, objects) { + var v = this.viewportTransform; + if (this.isRendering) { + fabric.util.cancelAnimFrame(this.isRendering); + this.isRendering = 0; + } this.calcViewportBoundaries(); this.clearContext(ctx); this.fire('before:render'); @@ -7066,7 +7176,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { ctx.save(); //apply viewport transform once for all rendering process - ctx.transform.apply(ctx, this.viewportTransform); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._renderObjects(ctx, objects); ctx.restore(); if (!this.controlsAboveOverlay && this.interactive) { @@ -7088,7 +7198,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @param {Array} objects to render */ _renderObjects: function(ctx, objects) { - for (var i = 0, length = objects.length; i < length; ++i) { + var i, len; + for (i = 0, len = objects.length; i < len; ++i) { objects[i] && objects[i].render(ctx); } }, @@ -7099,7 +7210,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @param {string} property 'background' or 'overlay' */ _renderBackgroundOrOverlay: function(ctx, property) { - var object = this[property + 'Color']; + var object = this[property + 'Color'], v; if (object) { ctx.fillStyle = object.toLive ? object.toLive(ctx, this) @@ -7114,8 +7225,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() { object = this[property + 'Image']; if (object) { if (this[property + 'Vpt']) { + v = this.viewportTransform; ctx.save(); - ctx.transform.apply(ctx, this.viewportTransform); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); } object.render(ctx); this[property + 'Vpt'] && ctx.restore(); @@ -7145,8 +7257,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { */ getCenter: function () { return { - top: this.getHeight() / 2, - left: this.getWidth() / 2 + top: this.height / 2, + left: this.width / 2 }; }, @@ -7243,7 +7355,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { */ _centerObject: function(object, center) { object.setPositionByOrigin(center, 'center', 'center'); - this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -7280,6 +7392,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { _toObjectMethod: function (methodName, propertiesToInclude) { var data = { + version: fabric.version, objects: this._toObjects(methodName, propertiesToInclude) }; @@ -7422,8 +7535,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { } markup.push( '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n', - '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', - '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', + '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' ); }, @@ -7456,17 +7569,17 @@ fabric.ElementsParser.prototype.checkIfDone = function() { markup.push( '<svg ', - 'xmlns="http://www.w3.org/2000/svg" ', - 'xmlns:xlink="http://www.w3.org/1999/xlink" ', - 'version="1.1" ', - 'width="', width, '" ', - 'height="', height, '" ', - viewBox, - 'xml:space="preserve">\n', + 'xmlns="http://www.w3.org/2000/svg" ', + 'xmlns:xlink="http://www.w3.org/1999/xlink" ', + 'version="1.1" ', + 'width="', width, '" ', + 'height="', height, '" ', + viewBox, + 'xml:space="preserve">\n', '<desc>Created with Fabric.js ', fabric.version, '</desc>\n', '<defs>\n', - this.createSVGFontFacesMarkup(), - this.createSVGRefElementsMarkup(), + this.createSVGFontFacesMarkup(), + this.createSVGRefElementsMarkup(), '</defs>\n' ); }, @@ -7495,10 +7608,10 @@ fabric.ElementsParser.prototype.checkIfDone = function() { */ createSVGFontFacesMarkup: function() { var markup = '', fontList = { }, obj, fontFamily, - style, row, rowIndex, _char, charIndex, + style, row, rowIndex, _char, charIndex, i, len, fontPaths = fabric.fontPaths, objects = this.getObjects(); - for (var i = 0, len = objects.length; i < len; i++) { + for (i = 0, len = objects.length; i < len; i++) { obj = objects[i]; fontFamily = obj.fontFamily; if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { @@ -7547,8 +7660,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @private */ _setSVGObjects: function(markup, reviver) { - var instance; - for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + var instance, i, len, objects = this.getObjects(); + for (i = 0, len = objects.length; i < len; i++) { instance = objects[i]; if (instance.excludeFromExport) { continue; @@ -7558,7 +7671,6 @@ fabric.ElementsParser.prototype.checkIfDone = function() { }, /** - * push single object svg representation in the markup * @private */ _setSVGObject: function(markup, instance, reviver) { @@ -7586,25 +7698,25 @@ fabric.ElementsParser.prototype.checkIfDone = function() { var repeat = filler.repeat; markup.push( '<rect transform="translate(', this.width / 2, ',', this.height / 2, ')"', - ' x="', filler.offsetX - this.width / 2, '" y="', filler.offsetY - this.height / 2, '" ', - 'width="', - (repeat === 'repeat-y' || repeat === 'no-repeat' - ? filler.source.width - : this.width), - '" height="', - (repeat === 'repeat-x' || repeat === 'no-repeat' - ? filler.source.height - : this.height), - '" fill="url(#SVGID_' + filler.id + ')"', + ' x="', filler.offsetX - this.width / 2, '" y="', filler.offsetY - this.height / 2, '" ', + 'width="', + (repeat === 'repeat-y' || repeat === 'no-repeat' + ? filler.source.width + : this.width), + '" height="', + (repeat === 'repeat-x' || repeat === 'no-repeat' + ? filler.source.height + : this.height), + '" fill="url(#SVGID_' + filler.id + ')"', '></rect>\n' ); } else { markup.push( '<rect x="0" y="0" ', - 'width="', this.width, - '" height="', this.height, - '" fill="', this[property], '"', + 'width="', this.width, + '" height="', this.height, + '" fill="', this[property], '"', '></rect>\n' ); } @@ -7622,10 +7734,10 @@ fabric.ElementsParser.prototype.checkIfDone = function() { if (!object) { return this; } - var activeGroup = this._activeGroup, + var activeSelection = this._activeObject, i, obj, objs; - if (object === activeGroup) { - objs = activeGroup._objects; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; for (i = objs.length; i--;) { obj = objs[i]; removeFromArray(this._objects, obj); @@ -7636,7 +7748,8 @@ fabric.ElementsParser.prototype.checkIfDone = function() { removeFromArray(this._objects, object); this._objects.unshift(object); } - return this.renderAll && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; }, /** @@ -7650,10 +7763,10 @@ fabric.ElementsParser.prototype.checkIfDone = function() { if (!object) { return this; } - var activeGroup = this._activeGroup, + var activeSelection = this._activeObject, i, obj, objs; - if (object === activeGroup) { - objs = activeGroup._objects; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; for (i = 0; i < objs.length; i++) { obj = objs[i]; removeFromArray(this._objects, obj); @@ -7664,11 +7777,16 @@ fabric.ElementsParser.prototype.checkIfDone = function() { removeFromArray(this._objects, object); this._objects.push(object); } - return this.renderAll && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; }, /** * Moves an object or a selection down in stack of drawn objects + * An optional paramter, intersecting allowes to move the object in behind + * the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Canvas} thisArg @@ -7678,12 +7796,11 @@ fabric.ElementsParser.prototype.checkIfDone = function() { if (!object) { return this; } - - var activeGroup = this._activeGroup, + var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0; - if (object === activeGroup) { - objs = activeGroup._objects; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; for (i = 0; i < objs.length; i++) { obj = objs[i]; idx = this._objects.indexOf(obj); @@ -7704,7 +7821,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { this._objects.splice(newIdx, 0, object); } } - this.renderAll && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -7712,13 +7829,13 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @private */ _findNewLowerIndex: function(object, idx, intersecting) { - var newIdx; + var newIdx, i; if (intersecting) { newIdx = idx; // traverse down the stack looking for the nearest intersecting object - for (var i = idx - 1; i >= 0; --i) { + for (i = idx - 1; i >= 0; --i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || @@ -7739,6 +7856,10 @@ fabric.ElementsParser.prototype.checkIfDone = function() { /** * Moves an object or a selection up in stack of drawn objects + * An optional paramter, intersecting allowes to move the object in front + * of the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. * @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 @@ -7748,12 +7869,11 @@ fabric.ElementsParser.prototype.checkIfDone = function() { if (!object) { return this; } - - var activeGroup = this._activeGroup, + var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0; - if (object === activeGroup) { - objs = activeGroup._objects; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; for (i = objs.length; i--;) { obj = objs[i]; idx = this._objects.indexOf(obj); @@ -7774,7 +7894,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { this._objects.splice(newIdx, 0, object); } } - this.renderAll && this.renderAll(); + this.renderOnAddRemove && this.requestRenderAll(); return this; }, @@ -7782,13 +7902,13 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @private */ _findNewUpperIndex: function(object, idx, intersecting) { - var newIdx; + var newIdx, i, len; if (intersecting) { newIdx = idx; // traverse up the stack looking for the nearest intersecting object - for (var i = idx + 1; i < this._objects.length; ++i) { + for (i = idx + 1, len = this._objects.length; i < len; ++i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || @@ -7817,7 +7937,7 @@ fabric.ElementsParser.prototype.checkIfDone = function() { moveTo: function (object, index) { removeFromArray(this._objects, object); this._objects.splice(index, 0, object); - return this.renderAll && this.renderAll(); + return this.renderOnAddRemove && this.requestRenderAll(); }, /** @@ -7826,7 +7946,12 @@ fabric.ElementsParser.prototype.checkIfDone = function() { * @chainable */ dispose: function () { - this.clear(); + this._objects.length = 0; + this.backgroundImage = null; + this.overlayImage = null; + this._iTextInstances = null; + this.lowerCanvasEl = null; + this.cacheCanvasEl = null; return this; }, @@ -7957,13 +8082,20 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype strokeLineCap: 'round', /** - * Corner style of a brush (one of "bevil", "round", "miter") + * Corner style of a brush (one of "bevel", "round", "miter") * @type String * @default */ strokeLineJoin: 'round', /** + * Maximum miter length (used for strokeLineJoin = "miter") of a brush's + * @type Number + * @default + */ + strokeMiterLimit: 10, + + /** * Stroke Dash Array. * @type Array * @default @@ -7987,10 +8119,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _setBrushStyles: function() { var ctx = this.canvas.contextTop; - ctx.strokeStyle = this.color; ctx.lineWidth = this.width; ctx.lineCap = this.strokeLineCap; + ctx.miterLimit = this.strokeMiterLimit; ctx.lineJoin = this.strokeLineJoin; if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) { ctx.setLineDash(this.strokeDashArray); @@ -8136,7 +8268,6 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype 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 @@ -8215,6 +8346,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype stroke: this.color, strokeWidth: this.width, strokeLineCap: this.strokeLineCap, + strokeMiterLimit: this.strokeMiterLimit, strokeLineJoin: this.strokeLineJoin, strokeDashArray: this.strokeDashArray, }); @@ -8245,18 +8377,17 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, // whereas Chrome 10 renders nothing - this.canvas.renderAll(); + this.canvas.requestRenderAll(); return; } var path = this.createPath(pathData); - + this.canvas.clearContext(this.canvas.contextTop); this.canvas.add(path); + this.canvas.renderAll(); path.setCoords(); - - this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); - this.canvas.renderAll(); + // fire event 'path' created this.canvas.fire('path:created', { path: path }); @@ -8330,12 +8461,12 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * Invoked on mouse up */ onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len; this.canvas.renderOnAddRemove = false; var circles = []; - for (var i = 0, len = this.points.length; i < len; i++) { + for (i = 0, len = this.points.length; i < len; i++) { var point = this.points[i], circle = new fabric.Circle({ radius: point.radius, @@ -8359,7 +8490,7 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); + this.canvas.requestRenderAll(); }, /** @@ -8370,11 +8501,11 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2, + Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); + .setAlpha(fabric.util.getRandomInt(0, 100) / 100) + .toRgba(); pointerPoint.radius = circleRadius; pointerPoint.fill = circleColor; @@ -8508,7 +8639,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); + this.canvas.requestRenderAll(); }, /** @@ -8518,9 +8649,9 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric _getOptimizedRects: function(rects) { // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key; + var uniqueRects = { }, key, i, len; - for (var i = 0, len = rects.length; i < len; i++) { + for (i = 0, len = rects.length; i < len; i++) { key = rects[i].left + '' + rects[i].top; if (!uniqueRects[key]) { uniqueRects[key] = rects[i]; @@ -8541,11 +8672,11 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; - var v = this.canvas.viewportTransform; + var v = this.canvas.viewportTransform, i, len; 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++) { + for (i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; @@ -8561,9 +8692,9 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric addSprayChunk: function(pointer) { this.sprayChunkPoints = []; - var x, y, width, radius = this.width / 2; + var x, y, width, radius = this.width / 2, i; - for (var i = 0; i < this.density; i++) { + for (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); @@ -8673,14 +8804,16 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @see {@link fabric.Canvas#initialize} for constructor definition * * @fires object:added + * @fires object:removed * @fires object:modified * @fires object:rotating * @fires object:scaling * @fires object:moving - * @fires object:selected + * @fires object:selected this event is deprecated. use selection:created * * @fires before:selection:cleared * @fires selection:cleared + * @fires selection:updated * @fires selection:created * * @fires path:created @@ -8689,6 +8822,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @fires mouse:up * @fires mouse:over * @fires mouse:out + * @fires mouse:dblclick * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { @@ -8701,7 +8835,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ initialize: function(el, options) { options || (options = { }); - + this.renderAndResetBound = this.renderAndReset.bind(this); this._initStatic(el, options); this._initInteractive(); this._createCacheCanvas(); @@ -8744,7 +8878,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab centeredRotation: false, /** - * Indicates which key enable centered Transfrom + * Indicates which key enable centered Transform * values: 'altKey', 'shiftKey', 'ctrlKey'. * If `null` or 'none' or any other string that is not a modifier key * feature is disabled feature disabled. @@ -8780,12 +8914,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab selection: true, /** - * Indicates which key enable multiple click selection + * Indicates which key or keys enable multiple click selection + * Pass value as a string or array of strings * values: 'altKey', 'shiftKey', 'ctrlKey'. - * If `null` or 'none' or any other string that is not a modifier key - * feature is disabled feature disabled. + * If `null` or empty or containing any other string that is not a modifier key + * feature is disabled. * @since 1.6.2 - * @type String + * @type String|Array * @default */ selectionKey: 'shiftKey', @@ -8866,6 +9001,14 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab rotationCursor: 'crosshair', /** + * Cursor value used for disabled elements ( corners with disabled action ) + * @type String + * @since 2.0.0 + * @default + */ + notAllowedCursor: 'not-allowed', + + /** * Default element class that's given to wrapper (div) element of canvas * @type String * @default @@ -8975,25 +9118,25 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @return {Array} objects to render immediately and pushes the other in the activeGroup. */ _chooseObjectsToRender: function(
<TRUNCATED>
