diff --git a/src/elements_parser.js b/src/elements_parser.js
index 30aa7eedb26..0fa77b69352 100644
--- a/src/elements_parser.js
+++ b/src/elements_parser.js
@@ -4,6 +4,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver) {
this.options = options;
this.reviver = reviver;
this.svgUid = (options && options.svgUid) || 0;
+ this.fillers = ['fill', 'stroke'];
};
fabric.ElementsParser.prototype.parse = function() {
@@ -40,41 +41,55 @@ fabric.ElementsParser.prototype.createObject = function(el, index) {
};
fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
+ var callback = this.createCallback(index, el);
if (klass.async) {
- klass.fromElement(el, this.createCallback(index, el), this.options);
+ klass.fromElement(el, callback, 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();
+ callback(obj);
}
};
fabric.ElementsParser.prototype.createCallback = function(index, el) {
var _this = this;
return function(obj) {
- _this.resolveGradient(obj, 'fill');
- _this.resolveGradient(obj, 'stroke');
- _this.reviver && _this.reviver(el, obj);
- _this.instances[index] = obj;
- _this.checkIfDone();
+ _this.resolveFillers(obj, function(_obj) {
+ _this.reviver && _this.reviver(el, _obj);
+ _this.instances[index] = _obj;
+ _this.checkIfDone();
+ });
};
};
-fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
-
- var instanceFillValue = obj.get(property);
- if (!(/^url\(/).test(instanceFillValue)) {
- return;
- }
- var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
- if (fabric.gradientDefs[this.svgUid][gradientId]) {
- obj.set(property,
- fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
- }
+fabric.ElementsParser.prototype.resolveFillers = function(obj, callback) {
+ var counter,
+ _callback = function(_obj) {
+ counter++;
+ if (counter === 2) {
+ callback(_obj);
+ }
+ };
+ this.fillers.forEach(function(filler) {
+ var instanceFillValue = obj.get(filler);
+ if (!(/^url\(/).test(instanceFillValue)) {
+ if (counter) {
+ _callback(obj);
+ return;
+ }
+ }
+ var fillerId = instanceFillValue.slice(5, instanceFillValue.length - 1);
+ if (fabric.gradientDefs[this.svgUid][fillerId]) {
+ obj.set(filler, fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][fillerId], obj));
+ _callback(obj);
+ }
+ else if (fabric.patternDefs[this.svgUid][fillerId]) {
+ fabric.Pattern.fromElement(fabric.patternDefs[this.svgUid][fillerId], obj, function(_pattern) {
+ obj.set(filler, _pattern);
+ _callback(obj);
+ });
+ }
+ });
};
fabric.ElementsParser.prototype.checkIfDone = function() {
diff --git a/src/parser.js b/src/parser.js
index baec5f83209..70cbe25c2c9 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -17,7 +17,7 @@
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)$/i,
reAllowedParents = /^(symbol|g|a|svg)$/i,
attributesMap = {
@@ -52,6 +52,7 @@
fabric.cssRules = { };
fabric.gradientDefs = { };
+ fabric.patternDefs = { };
function normalizeAttr(attr) {
// transform attribute names
@@ -636,13 +637,20 @@
return;
}
- fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
+ var gradientTags = ['linearGradient', 'radialGradient', 'svg:linearGradient', 'svg:radialGradient'];
+ var patternTags = ['pattern', 'svg:pattern'];
+
+ fabric.gradientDefs[svgUid] = fabric.getFillerDefs(doc, gradientTags);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
+ fabric.patternDefs[svgUid] = fabric.getFillerDefs(doc, patternTags);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances) {
fabric.documentParsingTime = new Date() - startTime;
if (callback) {
callback(instances, options);
+ delete fabric.gradientDefs[svgUid];
+ delete fabric.cssRules[svgUid];
+ delete fabric.patterns[svgUid];
}
}, clone(options), reviver);
};
@@ -752,22 +760,17 @@
},
/**
- * Parses an SVG document, returning all of the gradient declarations found in it
+ * Parses an SVG document, returning all of the gradient or patterns declarations found in it
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
- * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
+ * @return {Object} Gradient or Patterns definitions; key corresponds to element id, value -- to gradient definition element
*/
- getGradientDefs: function(doc) {
- var tagArray = [
- 'linearGradient',
- 'radialGradient',
- 'svg:linearGradient',
- 'svg:radialGradient'],
- elList = _getMultipleNodes(doc, tagArray),
+ getFillerDefs: function(doc, tagArray) {
+ var elList = _getMultipleNodes(doc, tagArray),
el, j = 0, id, xlink,
- gradientDefs = { }, idsToXlinkMap = { };
+ defs = { }, idsToXlinkMap = { };
j = elList.length;
@@ -778,17 +781,17 @@
if (xlink) {
idsToXlinkMap[id] = xlink.substr(1);
}
- gradientDefs[id] = el;
+ defs[id] = el;
}
for (id in idsToXlinkMap) {
- var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
- el = gradientDefs[id];
+ var el2 = defs[idsToXlinkMap[id]].cloneNode(true);
+ el = defs[id];
while (el2.firstChild) {
el.appendChild(el2.firstChild);
}
}
- return gradientDefs;
+ return defs;
},
/**
diff --git a/src/pattern.class.js b/src/pattern.class.js
index 7e772823007..7d8b9f2f4ad 100644
--- a/src/pattern.class.js
+++ b/src/pattern.class.js
@@ -1,171 +1,278 @@
-/**
- * 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() {
- /**
- * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
- * @type String
- * @default
- */
- repeat: 'repeat',
+ 'use strict';
- /**
- * Pattern horizontal offset from object's left/top corner
- * @type Number
- * @default
- */
- offsetX: 0,
+ var toFixed = fabric.util.toFixed;
/**
- * Pattern vertical offset from object's left/top corner
- * @type Number
- * @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
*/
- offsetY: 0,
- /**
- * Constructor
- * @param {Object} [options] Options object
- * @return {fabric.Pattern} thisArg
- */
- initialize: function(options) {
- options || (options = { });
- this.id = fabric.Object.__uid++;
+ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
- 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;
- });
- }
+ /**
+ * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
+ * @type String
+ * @default
+ */
+ repeat: 'repeat',
+
+ /**
+ * Pattern horizontal offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
+
+ /**
+ * Pattern vertical offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Pattern rotation angle, relative to center of object where the pattern is applied
+ * @type Number
+ * @default
+ */
+ angle: 0,
+
+ /**
+ * Pattern scale factor on X axis
+ * @type Number
+ * @default
+ */
+ scaleX: 1,
+
+ /**
+ * Pattern scale factor on Y axis
+ * @type Number
+ * @default
+ */
+ scaleY: 1,
+
+ /**
+ * Determines if a pattern scale with object container
+ * @type Boolean
+ * @default
+ */
+ scaleWithObject: true,
+
+ /**
+ * pattern transformation matrix, imported from SVGs file
+ * @type [Array] array of 6 numbers
+ * @default
+ */
+ patternTransform: null,
+
+ /**
+ * 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 && 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;
+ }
+ //