diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2aca5de..5a08dae0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+**Version 1.7.3**
+
+- Improvement: mousewheel event is handled with target and fired also from objects. [#3612](https://github.com/kangax/fabric.js/pull/3612)
+- Improvement: Pattern loads for canvas background and overlay, corrected svg pattern export [#3601](https://github.com/kangax/fabric.js/pull/3601)
+- Fix: Wait for pattern loading before calling callback [#3598](https://github.com/kangax/fabric.js/pull/3598)
+- Fix: add 2 extra pixels to cache canvases to avoid aliasing cut [#3596](https://github.com/kangax/fabric.js/pull/3596)
+- Fix: Rerender when deselect an itext editing object [#3594](https://github.com/kangax/fabric.js/pull/3594)
+- Fix: save new state of dimensionProperties at every cache clear [#3595](https://github.com/kangax/fabric.js/pull/3595)
+- Improvement: Better error managment in loadFromJSON [#3586](https://github.com/kangax/fabric.js/pull/3586)
+- Improvement: do not reload backgroundImage as an image if is different type [#3550](https://github.com/kangax/fabric.js/pull/3550)
+- Improvement: if a children element is set dirty, set the parent dirty as well. [#3564](https://github.com/kangax/fabric.js/pull/3564)
+
**Version 1.7.2**
- Fix: Textbox do not use stylemap for line wrapping [#3546](https://github.com/kangax/fabric.js/pull/3546)
diff --git a/HEADER.js b/HEADER.js
index 1308130e..68a4b281 100644
--- a/HEADER.js
+++ b/HEADER.js
@@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: "1.7.2" };
+var fabric = fabric || { version: "1.7.3" };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index dc952c84..ab31aa60 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -25,7 +25,7 @@ Remove the template from below and provide thoughtful commentary *and code sampl
## Version
-1.7.2
+1.7.3
## Test Case
http://jsfiddle.net/fabricjs/Da7SP/
diff --git a/dist/fabric.js b/dist/fabric.js
index 69a39fe9..785ef5e2 100644
--- a/dist/fabric.js
+++ b/dist/fabric.js
@@ -1,7 +1,7 @@
/* 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.7.2" };
+var fabric = fabric || { version: "1.7.3" };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
@@ -363,6 +363,122 @@ fabric.Collection = {
};
+/**
+ * @namespace fabric.CommonMethods
+ */
+fabric.CommonMethods = {
+
+ /**
+ * Sets object's properties from options
+ * @param {Object} [options] Options object
+ */
+ _setOptions: function(options) {
+ for (var prop in options) {
+ this.set(prop, options[prop]);
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Gradient to
+ */
+ _initGradient: function(filler, property) {
+ if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
+ this.set(property, new fabric.Gradient(filler));
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Pattern to
+ * @param {Function} [callback] callback to invoke after pattern load
+ */
+ _initPattern: function(filler, property, callback) {
+ if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
+ this.set(property, new fabric.Pattern(filler, callback));
+ }
+ else {
+ callback && callback();
+ }
+ },
+
+ /**
+ * @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);
+ }
+ },
+
+ /**
+ * @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;
+ },
+
+ _set: function(key, value) {
+ this[key] = value;
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * Basic getter
+ * @param {String} property Property name
+ * @return {*} value of a property
+ */
+ get: function(property) {
+ return this[property];
+ }
+};
+
+
(function(global) {
var sqrt = Math.sqrt,
@@ -677,7 +793,8 @@ fabric.Collection = {
var enlivenedObjects = [],
numLoadedObjects = 0,
- numTotalObjects = objects.length;
+ numTotalObjects = objects.length,
+ forceAsync = true;
if (!numTotalObjects) {
callback && callback(enlivenedObjects);
@@ -691,18 +808,51 @@ fabric.Collection = {
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]);
- }
+ klass.fromObject(o, function (obj, error) {
+ error || (enlivenedObjects[index] = obj);
+ reviver && reviver(o, obj, error);
+ onLoaded();
+ }, forceAsync);
+ });
+ },
+
+ /**
+ * Create and wait for loading of patterns
+ * @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.
+ */
+ enlivenPatterns: function(patterns, callback) {
+ patterns = patterns || [];
+
+ function onLoaded() {
+ if (++numLoadedPatterns === numPatterns) {
+ callback && callback(enlivenedPatterns);
+ }
+ }
+
+ var enlivenedPatterns = [],
+ numLoadedPatterns = 0,
+ numPatterns = patterns.length;
+
+ if (!numPatterns) {
+ callback && callback(enlivenedPatterns);
+ return;
+ }
+
+ patterns.forEach(function (p, index) {
+ if (p && p.source) {
+ new fabric.Pattern(p, function(pattern) {
+ enlivenedPatterns[index] = pattern;
onLoaded();
});
}
else {
- enlivenedObjects[index] = klass.fromObject(o);
- reviver && reviver(o, enlivenedObjects[index]);
+ enlivenedPatterns[index] = p;
onLoaded();
}
});
@@ -3058,7 +3208,6 @@ if (typeof console !== 'undefined') {
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,
@@ -3066,7 +3215,7 @@ if (typeof console !== 'undefined') {
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,
+ reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata|clipPath|mask)$/i,
reAllowedParents = /^(symbol|g|a|svg)$/i,
attributesMap = {
@@ -3204,16 +3353,19 @@ if (typeof console !== 'undefined') {
*/
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;
+ var cos = Math.cos(args[0]), sin = Math.sin(args[0]),
+ x = 0, y = 0;
+ if (args.length === 3) {
+ x = args[1];
+ y = args[2];
+ }
- 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);
+ matrix[0] = cos;
+ matrix[1] = sin;
+ matrix[2] = -sin;
+ matrix[3] = cos;
+ matrix[4] = x - (cos * x - sin * y);
+ matrix[5] = y - (sin * x + cos * y);
}
function scaleMatrix(matrix, args) {
@@ -3224,12 +3376,8 @@ if (typeof console !== 'undefined') {
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 skewMatrix(matrix, args, pos) {
+ matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0]));
}
function translateMatrix(matrix, args) {
@@ -3329,10 +3477,10 @@ if (typeof console !== 'undefined') {
scaleMatrix(matrix, args);
break;
case 'skewX':
- skewXMatrix(matrix, args);
+ skewMatrix(matrix, args, 2);
break;
case 'skewY':
- skewYMatrix(matrix, args);
+ skewMatrix(matrix, args, 1);
break;
case 'matrix':
matrix = args;
@@ -3627,6 +3775,16 @@ if (typeof console !== 'undefined') {
return parsedDim;
}
+ 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;
+ }
+
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @static
@@ -3637,122 +3795,50 @@ if (typeof console !== 'undefined') {
* 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;
+ fabric.parseSVGDocument = function(doc, callback, reviver) {
+ if (!doc) {
+ return;
}
- return function(doc, callback, reviver) {
- if (!doc) {
- return;
+ parseUseDirectives(doc);
+
+ var 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];
}
-
- 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 */
+ descendants = arr;
}
- };
- /**
- * @private
- */
- function _enlivenCachedObject(cachedObject) {
-
- var objects = cachedObject.objects,
- options = cachedObject.options;
-
- objects = objects.map(function (o) {
- return fabric[capitalize(o.type)].fromObject(o);
+ 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 ({ 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'
- );
+ 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) {
+ if (callback) {
+ callback(instances, options);
+ }
+ }, clone(options), reviver);
+ };
var reFontDeclaration = new RegExp(
'(normal|italic)?\\s*(normal|small-caps)?\\s*' +
@@ -4030,19 +4116,9 @@ if (typeof console !== 'undefined') {
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
- });
- }
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
});
function onComplete(r) {
@@ -4059,10 +4135,6 @@ if (typeof console !== 'undefined') {
}
fabric.parseSVGDocument(xml.documentElement, function (results, options) {
- svgCache.set(url, {
- objects: fabric.util.array.invoke(results, 'toObject'),
- options: options
- });
callback && callback(results, options);
}, reviver);
}
@@ -4094,77 +4166,6 @@ if (typeof console !== 'undefined') {
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('');
}
});
@@ -5401,9 +5402,9 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
* @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]);
+ addColorStop: function(colorStops) {
+ for (var position in colorStops) {
+ var color = new fabric.Color(colorStops[position]);
this.colorStops.push({
offset: position,
color: color.toRgb(),
@@ -5703,177 +5704,181 @@ fabric.ElementsParser.prototype.checkIfDone = function() {
})();
-/**
- * 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 */ {
+(function() {
+
+ 'use strict';
+
+ var toFixed = fabric.util.toFixed;
/**
- * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
- * @type String
- * @default
+ * 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
*/
- 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,
+ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
- /**
- * Constructor
- * @param {Object} [options] Options object
- * @return {fabric.Pattern} thisArg
- */
- initialize: function(options) {
- options || (options = { });
+ /**
+ * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
+ * @type String
+ * @default
+ */
+ repeat: 'repeat',
- this.id = fabric.Object.__uid++;
+ /**
+ * Pattern horizontal offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
- 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;
- });
- }
+ /**
+ * Pattern vertical offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Constructor
+ * @param {Object} [options] Options object
+ * @param {Function} [callback] function to invoke after callback init.
+ * @return {fabric.Pattern} thisArg
+ */
+ initialize: function(options, callback) {
+ options || (options = { });
+
+ this.id = fabric.Object.__uid++;
+ this.setOptions(options);
+ if (!options.source || (options.source && typeof options.source !== 'string')) {
+ callback && callback(this);
+ return;
+ }
+ // function string
+ if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
+ this.source = new Function(fabric.util.getFunctionBody(options.source));
+ callback && callback(this);
}
else {
- // img element
- this.source = options.source;
+ // img src string
+ var _this = this;
+ this.source = fabric.util.createImage();
+ fabric.util.loadImage(options.source, function(img) {
+ _this.source = img;
+ callback && callback(_this);
+ });
}
- }
- 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
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
- * @return {Object} Object representation of a pattern instance
- */
- toObject: function(propertiesToInclude) {
+ /**
+ * Returns object representation of a pattern
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {Object} Object representation of a pattern instance
+ */
+ toObject: function(propertiesToInclude) {
+ var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
+ source, object;
- var source, object;
+ // callback
+ if (typeof this.source === 'function') {
+ source = String(this.source);
+ }
+ //
element
+ else if (typeof this.source.src === 'string') {
+ source = this.source.src;
+ }
+ //