1 (function(){
  2   
  3   /** @name fabric */
  4   
  5   var fabric = this.fabric || (this.fabric = { }),
  6       extend = fabric.util.object.extend,
  7       capitalize = fabric.util.string.capitalize,
  8       clone = fabric.util.object.clone;
  9   
 10   var attributesMap = {
 11     'cx':             'left',
 12     'x':              'left',
 13     'cy':             'top',
 14     'y':              'top',
 15     'r':              'radius',
 16     'fill-opacity':   'opacity',
 17     'fill-rule':      'fillRule',
 18     'stroke-width':   'strokeWidth',
 19     'transform':      'transformMatrix'
 20   };
 21   
 22   /**
 23    * Returns an object of attributes' name/value, given element and an array of attribute names;
 24    * Parses parent "g" nodes recursively upwards.
 25    *
 26    * @static
 27    * @memberOf fabric
 28    * @method parseAttributes
 29    * @param {DOMElement} element Element to parse
 30    * @param {Array} attributes Array of attributes to parse
 31    * @return {Object} object containing parsed attributes' names/values
 32    */
 33   function parseAttributes(element, attributes) {
 34     
 35     if (!element) {
 36       return;
 37     }
 38     
 39     var value, 
 40         parsed, 
 41         parentAttributes = { };
 42 
 43     // if there's a parent container (`g` node), parse its attributes recursively upwards
 44     if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
 45       parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
 46     }
 47     
 48     var ownAttributes = attributes.reduce(function(memo, attr) {
 49       value = element.getAttribute(attr);
 50       parsed = parseFloat(value);
 51       if (value) {        
 52         // "normalize" attribute values
 53         if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
 54           value = '';
 55         }
 56         if (attr === 'fill-rule') {
 57           value = (value === 'evenodd') ? 'destination-over' : value;
 58         }
 59         if (attr === 'transform') {
 60           value = fabric.parseTransformAttribute(value);
 61         }
 62         // transform attribute names
 63         if (attr in attributesMap) {
 64           attr = attributesMap[attr];
 65         }
 66         memo[attr] = isNaN(parsed) ? value : parsed;
 67       }
 68       return memo;
 69     }, { });
 70     
 71     // add values parsed from style
 72     // TODO (kangax): check the presedence of values from the style attribute
 73     ownAttributes = extend(fabric.parseStyleAttribute(element), ownAttributes);
 74     return extend(parentAttributes, ownAttributes);
 75   };
 76   
 77   /**
 78    * @static
 79    * @function
 80    * @memberOf fabric
 81    * @method parseTransformAttribute
 82    * @param attributeValue {String} string containing attribute value
 83    * @return {Array} array of 6 elements representing transformation matrix
 84    */
 85   fabric.parseTransformAttribute = (function() {
 86     function rotateMatrix(matrix, args) {
 87       var angle = args[0];
 88       
 89       matrix[0] = Math.cos(angle);
 90       matrix[1] = Math.sin(angle);
 91       matrix[2] = -Math.sin(angle);
 92       matrix[3] = Math.cos(angle);
 93     }
 94     
 95     function scaleMatrix(matrix, args) {
 96       var multiplierX = args[0],
 97           multiplierY = (args.length === 2) ? args[1] : args[0];
 98 
 99       matrix[0] = multiplierX;
100       matrix[3] = multiplierY;
101     }
102     
103     function skewXMatrix(matrix, args) {
104       matrix[2] = args[0];
105     }
106     
107     function skewYMatrix(matrix, args) {
108       matrix[1] = args[0];
109     }
110     
111     function translateMatrix(matrix, args) {
112       matrix[4] = args[0];
113       if (args.length === 2) {
114         matrix[5] = args[1];
115       }
116     }
117     
118     // identity matrix
119     var iMatrix = [
120           1, // a
121           0, // b
122           0, // c
123           1, // d
124           0, // e
125           0  // f
126         ],
127     
128         // == begin transform regexp
129         number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)',
130         comma_wsp = '(?:\\s+,?\\s*|,\\s*)',
131         
132         skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
133         skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
134         rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))',
135         scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
136         translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
137         
138         matrix = '(?:(matrix)\\s*\\(\\s*' + 
139                   '(' + number + ')' + comma_wsp + 
140                   '(' + number + ')' + comma_wsp + 
141                   '(' + number + ')' + comma_wsp +
142                   '(' + number + ')' + comma_wsp +
143                   '(' + number + ')' + comma_wsp +
144                   '(' + number + ')' + 
145                   '\\s*\\))',
146         
147         transform = '(?:' +
148                     matrix + '|' +
149                     translate + '|' +
150                     scale + '|' +
151                     rotate + '|' +
152                     skewX + '|' +
153                     skewY + 
154                     ')',
155         
156         transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')',
157         
158         transform_list = '^\\s*(?:' + transforms + '?)\\s*$',
159     
160         // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
161         reTransformList = new RegExp(transform_list),
162         // == end transform regexp
163         
164         reTransform = new RegExp(transform);
165     
166     return function(attributeValue) {
167       
168       // start with identity matrix
169       var matrix = iMatrix.concat();
170       
171       // return if no argument was given or 
172       // an argument does not match transform attribute regexp
173       if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
174         return matrix;
175       }
176       
177       attributeValue.replace(reTransform, function(match) {
178           
179         var m = new RegExp(transform).exec(match).filter(function (match) {
180               return (match !== '' && match != null);
181             }),
182             operation = m[1],
183             args = m.slice(2).map(parseFloat);
184         
185         switch(operation) {
186           case 'translate':
187             translateMatrix(matrix, args);
188             break;
189           case 'rotate':
190             rotateMatrix(matrix, args);
191             break;
192           case 'scale':
193             scaleMatrix(matrix, args);
194             break;
195           case 'skewX':
196             skewXMatrix(matrix, args);
197             break;
198           case 'skewY':
199             skewYMatrix(matrix, args);
200             break;
201           case 'matrix':
202             matrix = args;
203             break;
204         }
205       })
206       return matrix;
207     }
208   })();
209   
210   /**
211    * @static
212    * @memberOf fabric
213    * @method parsePointsAttribute
214    * @param points {String} points attribute string
215    * @return {Array} array of points
216    */
217   function parsePointsAttribute(points) {
218         
219     // points attribute is required and must not be empty
220     if (!points) return null;
221     
222     points = points.trim();
223     var asPairs = points.indexOf(',') > -1;
224     
225     points = points.split(/\s+/);
226     var parsedPoints = [ ];
227     
228     // points could look like "10,20 30,40" or "10 20 30 40"
229     if (asPairs) {  
230      for (var i = 0, len = points.length; i < len; i++) {
231        var pair = points[i].split(',');
232        parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
233      } 
234     }
235     else {
236       for (var i = 0, len = points.length; i < len; i+=2) {
237         parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) });
238       }
239     }
240     
241     // odd number of points is an error
242     if (parsedPoints.length % 2 !== 0) {
243       // return null;
244     }
245     
246     return parsedPoints;
247   };
248 
249   /**
250    * @static
251    * @memberOf fabric
252    * @method parseStyleAttribute
253    * @param {SVGElement} element Element to parse
254    * @return {Object} Objects with values parsed from style attribute of an element
255    */
256   function parseStyleAttribute(element) {
257     var oStyle = { },
258         style = element.getAttribute('style');
259     if (style) {
260       if (typeof style == 'string') {
261         style = style.split(';');
262         style.pop();
263         oStyle = style.reduce(function(memo, current) {
264           var attr = current.split(':'),
265               key = attr[0].trim(),
266               value = attr[1].trim();
267           memo[key] = value;
268           return memo;
269         }, { });
270       }
271       else {
272         for (var prop in style) {
273           if (typeof style[prop] !== 'undefined') {
274             oStyle[prop] = style[prop];
275           }
276         }
277       }
278     }
279     return oStyle;
280   };
281 
282   /**
283    * @static
284    * @memberOf fabric
285    * @method parseElements
286    * @param {Array} elements Array of elements to parse
287    * @param {Object} options Options object
288    * @return {Array} Array of corresponding instances (transformed from SVG elements)
289    */
290    function parseElements(elements, options) {
291     // transform svg elements to fabric.Path elements
292     var _elements = elements.map(function(el) {
293       var klass = fabric[capitalize(el.tagName)];
294       if (klass && klass.fromElement) {
295         try {
296           return klass.fromElement(el, options);
297         }
298         catch(e) {
299           fabric.log(e.message || e);
300         }
301       }
302     });
303     _elements = _elements.filter(function(el) {
304       return el != null;
305     });
306     return _elements;
307   };
308   
309   /**
310    * @static
311    * @function
312    * @memberOf fabric
313    * @method parseSVGDocument
314    * @param {SVGDocument} doc SVG document to parse
315    * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
316    */
317   fabric.parseSVGDocument = (function() {
318 
319     var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line)$/;
320 
321     // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
322     // \d doesn't quite cut it (as we need to match an actual float number)
323 
324     // matches, e.g.: +14.56e-12, etc.
325     var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)';
326 
327     var reViewBoxAttrValue = new RegExp(
328       '^' +
329       '\\s*(' + reNum + '+)\\s*,?' +
330       '\\s*(' + reNum + '+)\\s*,?' +
331       '\\s*(' + reNum + '+)\\s*,?' +
332       '\\s*(' + reNum + '+)\\s*' +
333       '$'
334     );
335     
336     function hasParentWithNodeName(element, parentNodeName) {
337       while (element && (element = element.parentNode)) {
338         if (element.nodeName === parentNodeName) {
339           return true;
340         }
341       }
342       return false;
343     }
344 
345     return function(doc, callback) {
346       if (!doc) return;
347       var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
348       
349       var elements = descendants.filter(function(el) {
350         return reAllowedSVGTagNames.test(el.tagName) && 
351           !hasParentWithNodeName(el, 'pattern');
352       });
353 
354       if (!elements || (elements && !elements.length)) return;
355 
356       var viewBoxAttr = doc.getAttribute('viewBox'),
357           widthAttr = doc.getAttribute('width'),
358           heightAttr = doc.getAttribute('height'),
359           width = null,
360           height = null,
361           minX,
362           minY;
363       
364       if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
365         minX = parseInt(viewBoxAttr[1], 10);
366         minY = parseInt(viewBoxAttr[2], 10);
367         width = parseInt(viewBoxAttr[3], 10);
368         height = parseInt(viewBoxAttr[4], 10);
369       }
370 
371       // values of width/height attributes overwrite those extracted from viewbox attribute
372       width = widthAttr ? parseFloat(widthAttr) : width;
373       height = heightAttr ? parseFloat(heightAttr) : height;
374 
375       var options = { 
376         width: width, 
377         height: height
378       };
379 
380       var elements = fabric.parseElements(elements, clone(options));
381       if (!elements || (elements && !elements.length)) return;
382 
383       if (callback) {
384         callback(elements, options);
385       }
386     };
387   })();
388   
389   extend(fabric, {
390     parseAttributes:        parseAttributes,
391     parseElements:          parseElements,
392     parseStyleAttribute:    parseStyleAttribute,
393     parsePointsAttribute:   parsePointsAttribute
394   });
395   
396 })();