text: (function(global) {
  
    'use strict';
  
    /**
     * @name fabric
     * @namespace
     */
  
    var fabric = global.fabric || (global.fabric = { }),
        extend = fabric.util.object.extend,
        capitalize = fabric.util.string.capitalize,
        clone = fabric.util.object.clone,
        toFixed = fabric.util.toFixed,
        parseUnit = fabric.util.parseUnit,
        multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
  
        attributesMap = {
          cx:                   'left',
          x:                    'left',
          r:                    'radius',
          cy:                   'top',
          y:                    'top',
          display:              'visible',
          visibility:           'visible',
          transform:            'transformMatrix',
          'fill-opacity':       'fillOpacity',
          'fill-rule':          'fillRule',
          'font-family':        'fontFamily',
          'font-size':          'fontSize',
          'font-style':         'fontStyle',
          'font-weight':        'fontWeight',
          'stroke-dasharray':   'strokeDashArray',
          'stroke-linecap':     'strokeLineCap',
          'stroke-linejoin':    'strokeLineJoin',
          'stroke-miterlimit':  'strokeMiterLimit',
          'stroke-opacity':     'strokeOpacity',
          'stroke-width':       'strokeWidth',
          'text-decoration':    'textDecoration',
          'text-anchor':        'originX'
        },
  
        colorAttributes = {
          stroke: 'strokeOpacity',
          fill:   'fillOpacity'
        };
  
    fabric.cssRules = { };
    fabric.gradientDefs = { };
  
    function normalizeAttr(attr) {
      // transform attribute names
      if (attr in attributesMap) {
        return attributesMap[attr];
      }
      return attr;
    }
  
    function normalizeValue(attr, value, parentAttributes, fontSize) {
      var isArray = Object.prototype.toString.call(value) === '[object Array]',
          parsed;
  
      if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
        value = '';
      }
      else if (attr === 'strokeDashArray') {
        value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
          return parseFloat(n);
        });
      }
      else if (attr === 'transformMatrix') {
        if (parentAttributes && parentAttributes.transformMatrix) {
          value = multiplyTransformMatrices(
            parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
        }
        else {
          value = fabric.parseTransformAttribute(value);
        }
      }
      else if (attr === 'visible') {
        value = (value === 'none' || value === 'hidden') ? false : true;
        // display=none on parent element always takes precedence over child element
        if (parentAttributes && parentAttributes.visible === false) {
          value = false;
        }
      }
      else if (attr === 'originX' /* text-anchor */) {
        value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
      }
      else {
        parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
      }
  
      return (!isArray && isNaN(parsed) ? value : parsed);
    }
  
    /**
     * @private
     * @param {Object} attributes Array of attributes to parse
     */
    function _setStrokeFillOpacity(attributes) {
      for (var attr in colorAttributes) {
  
        if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') {
          continue;
        }
  
        if (attributes[attr].indexOf('url(') === 0) {
          continue;
        }
  
        var color = new fabric.Color(attributes[attr]);
        attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
      }
      return attributes;
    }
  
    /**
     * Parses "transform" attribute, returning an array of values
     * @static
     * @function
     * @memberOf fabric
     * @param {String} attributeValue String containing attribute value
     * @return {Array} Array of 6 elements representing transformation matrix
     */
    fabric.parseTransformAttribute = (function() {
      function rotateMatrix(matrix, args) {
        var angle = args[0];
  
        matrix[0] = Math.cos(angle);
        matrix[1] = Math.sin(angle);
        matrix[2] = -Math.sin(angle);
        matrix[3] = Math.cos(angle);
      }
  
      function scaleMatrix(matrix, args) {
        var multiplierX = args[0],
            multiplierY = (args.length === 2) ? args[1] : args[0];
  
        matrix[0] = multiplierX;
        matrix[3] = multiplierY;
      }
  
      function skewXMatrix(matrix, args) {
        matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0]));
      }
  
      function skewYMatrix(matrix, args) {
        matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0]));
      }
  
      function translateMatrix(matrix, args) {
        matrix[4] = args[0];
        if (args.length === 2) {
          matrix[5] = args[1];
        }
      }
  
      // identity matrix
      var iMatrix = [
            1, // a
            0, // b
            0, // c
            1, // d
            0, // e
            0  // f
          ],
  
          // == begin transform regexp
          number = fabric.reNum,
  
          commaWsp = '(?:\\s+,?\\s*|,\\s*)',
  
          skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
  
          skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
  
          rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
                      commaWsp + '(' + number + ')' +
                      commaWsp + '(' + number + '))?\\s*\\))',
  
          scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
                      commaWsp + '(' + number + '))?\\s*\\))',
  
          translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
                      commaWsp + '(' + number + '))?\\s*\\))',
  
          matrix = '(?:(matrix)\\s*\\(\\s*' +
                    '(' + number + ')' + commaWsp +
                    '(' + number + ')' + commaWsp +
                    '(' + number + ')' + commaWsp +
                    '(' + number + ')' + commaWsp +
                    '(' + number + ')' + commaWsp +
                    '(' + number + ')' +
                    '\\s*\\))',
  
          transform = '(?:' +
                      matrix + '|' +
                      translate + '|' +
                      scale + '|' +
                      rotate + '|' +
                      skewX + '|' +
                      skewY +
                      ')',
  
          transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')',
  
          transformList = '^\\s*(?:' + transforms + '?)\\s*$',
  
          // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
          reTransformList = new RegExp(transformList),
          // == end transform regexp
  
          reTransform = new RegExp(transform, 'g');
  
      return function(attributeValue) {
  
        // start with identity matrix
        var matrix = iMatrix.concat(),
            matrices = [ ];
  
        // return if no argument was given or
        // an argument does not match transform attribute regexp
        if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
          return matrix;
        }
  
        attributeValue.replace(reTransform, function(match) {
  
          var m = new RegExp(transform).exec(match).filter(function (match) {
                return (match !== '' && match != null);
              }),
              operation = m[1],
              args = m.slice(2).map(parseFloat);
  
          switch (operation) {
            case 'translate':
              translateMatrix(matrix, args);
              break;
            case 'rotate':
              args[0] = fabric.util.degreesToRadians(args[0]);
              rotateMatrix(matrix, args);
              break;
            case 'scale':
              scaleMatrix(matrix, args);
              break;
            case 'skewX':
              skewXMatrix(matrix, args);
              break;
            case 'skewY':
              skewYMatrix(matrix, args);
              break;
            case 'matrix':
              matrix = args;
              break;
          }
  
          // snapshot current matrix into matrices array
          matrices.push(matrix.concat());
          // reset
          matrix = iMatrix.concat();
        });
  
        var combinedMatrix = matrices[0];
        while (matrices.length > 1) {
          matrices.shift();
          combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
        }
        return combinedMatrix;
      };
    })();
  
    /**
     * @private
     */
    function parseStyleString(style, oStyle) {
      var attr, value;
      style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
        var pair = chunk.split(':');
  
        attr = normalizeAttr(pair[0].trim().toLowerCase());
        value = normalizeValue(attr, pair[1].trim());
  
        oStyle[attr] = value;
      });
    }
  
    /**
     * @private
     */
    function parseStyleObject(style, oStyle) {
      var attr, value;
      for (var prop in style) {
        if (typeof style[prop] === 'undefined') {
          continue;
        }
  
        attr = normalizeAttr(prop.toLowerCase());
        value = normalizeValue(attr, style[prop]);
  
        oStyle[attr] = value;
      }
    }
  
    /**
     * @private
     */
    function getGlobalStylesForElement(element, svgUid) {
      var styles = { };
      for (var rule in fabric.cssRules[svgUid]) {
        if (elementMatchesRule(element, rule.split(' '))) {
          for (var property in fabric.cssRules[svgUid][rule]) {
            styles[property] = fabric.cssRules[svgUid][rule][property];
          }
        }
      }
      return styles;
    }
  
    /**
     * @private
     */
    function elementMatchesRule(element, selectors) {
      var firstMatching, parentMatching = true;
      //start from rightmost selector.
      firstMatching = selectorMatches(element, selectors.pop());
      if (firstMatching && selectors.length) {
        parentMatching = doesSomeParentMatch(element, selectors);
      }
      return firstMatching && parentMatching && (selectors.length === 0);
    }
  
    function doesSomeParentMatch(element, selectors) {
      var selector, parentMatching = true;
      while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
        if (parentMatching) {
          selector = selectors.pop();
        }
        element = element.parentNode;
        parentMatching = selectorMatches(element, selector);
      }
      return selectors.length === 0;
    }
  
    /**
     * @private
     */
    function selectorMatches(element, selector) {
      var nodeName = element.nodeName,
          classNames = element.getAttribute('class'),
          id = element.getAttribute('id'), matcher;
      // i check if a selector matches slicing away part from it.
      // if i get empty string i should match
      matcher = new RegExp('^' + nodeName, 'i');
      selector = selector.replace(matcher, '');
      if (id && selector.length) {
        matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
        selector = selector.replace(matcher, '');
      }
      if (classNames && selector.length) {
        classNames = classNames.split(' ');
        for (var i = classNames.length; i--;) {
          matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
          selector = selector.replace(matcher, '');
        }
      }
      return selector.length === 0;
    }
  
    /**
     * @private
     * to support IE8 missing getElementById on SVGdocument
     */
    function elementById(doc, id) {
      var el;
      doc.getElementById && (el = doc.getElementById(id));
      if (el) {
        return el;
      }
      var node, i, idAttr, nodelist = doc.getElementsByTagName('*');
      for (i = 0; i < nodelist.length; i++) {
        node = nodelist[i];
        if (idAttr === node.getAttribute('id')) {
          return node;
        }
      }
    }
  
    /**
     * @private
     */
    function parseUseDirectives(doc) {
      var nodelist = doc.getElementsByTagName('use'), i = 0;
      while (nodelist.length && i < nodelist.length) {
        var el = nodelist[i],
            xlink = el.getAttribute('xlink:href').substr(1),
            x = el.getAttribute('x') || 0,
            y = el.getAttribute('y') || 0,
            el2 = elementById(doc, xlink).cloneNode(true),
            currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
            parentNode, oldLength = nodelist.length;
  
        for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
          var attr = attrs.item(j);
          if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
            continue;
          }
  
          if (attr.nodeName === 'transform') {
            currentTrans = attr.nodeValue + ' ' + currentTrans;
          }
          else {
            el2.setAttribute(attr.nodeName, attr.nodeValue);
          }
        }
  
        el2.setAttribute('transform', currentTrans);
        el2.setAttribute('instantiated_by_use', '1');
        el2.removeAttribute('id');
        parentNode = el.parentNode;
        parentNode.replaceChild(el2, el);
        // some browsers do not shorten nodelist after replaceChild (IE8)
        if (nodelist.length === oldLength) {
          i++;
        }
      }
    }
  
    // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
    // matches, e.g.: +14.56e-12, etc.
    var reViewBoxAttrValue = new RegExp(
      '^' +
      '\\s*(' + fabric.reNum + '+)\\s*,?' +
      '\\s*(' + fabric.reNum + '+)\\s*,?' +
      '\\s*(' + fabric.reNum + '+)\\s*,?' +
      '\\s*(' + fabric.reNum + '+)\\s*' +
      '$'
    );
  
    /**
     * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
     */
    function addVBTransform(element, widthAttr, heightAttr) {
  
      var viewBoxAttr = element.getAttribute('viewBox'),
          scaleX = 1,
          scaleY = 1,
          minX = 0,
          minY = 0,
          viewBoxWidth, viewBoxHeight, matrix, el;
  
      if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
        minX = -parseFloat(viewBoxAttr[1]),
        minY = -parseFloat(viewBoxAttr[2]),
        viewBoxWidth = parseFloat(viewBoxAttr[3]),
        viewBoxHeight = parseFloat(viewBoxAttr[4]);
      }
      else {
        return;
      }
      if (widthAttr && widthAttr !== viewBoxWidth) {
        scaleX = widthAttr / viewBoxWidth;
      }
      if (heightAttr && heightAttr !== viewBoxHeight) {
        scaleY = heightAttr / viewBoxHeight;
      }
  
      // default is to preserve aspect ratio
      // preserveAspectRatio attribute to be implemented
      scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
  
      if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) {
        return;
      }
      matrix = ' matrix(' + scaleX +
                    ' 0' +
                    ' 0 ' +
                    scaleY + ' ' +
                    (minX * scaleX) + ' ' +
                    (minY * scaleY) + ') ';
  
      if (element.tagName === 'svg') {
        el = element.ownerDocument.createElement('g');
        while (element.firstChild != null) {
          el.appendChild(element.firstChild);
        }
        element.appendChild(el);
      }
      else {
        el = element;
        matrix = el.getAttribute('transform') + matrix;
      }
  
      el.setAttribute('transform', matrix);
    }
  
    /**
     * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
     * @static
     * @function
     * @memberOf fabric
     * @param {SVGDocument} doc SVG document to parse
     * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
     */
    fabric.parseSVGDocument = (function() {
  
      var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
          reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/;
  
      function hasAncestorWithNodeName(element, nodeName) {
        while (element && (element = element.parentNode)) {
          if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) {
            return true;
          }
        }
        return false;
      }
  
      return function(doc, callback, reviver) {
        if (!doc) {
          return;
        }
  
        parseUseDirectives(doc);
  
        var startTime = new Date(),
            svgUid =  fabric.Object.__uid++,
            widthAttr, heightAttr, toBeParsed = false;
  
        if (doc.getAttribute('width') && doc.getAttribute('width') !== '100%') {
          widthAttr = parseUnit(doc.getAttribute('width'));
        }
        if (doc.getAttribute('height') && doc.getAttribute('height') !== '100%') {
          heightAttr = parseUnit(doc.getAttribute('height'));
        }
  
        if (!widthAttr || !heightAttr) {
          var viewBoxAttr = doc.getAttribute('viewBox');
          if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
            widthAttr = parseFloat(viewBoxAttr[3]),
            heightAttr = parseFloat(viewBoxAttr[4]);
          }
          else {
            toBeParsed = true;
          }
        }
  
        addVBTransform(doc, widthAttr, heightAttr);
  
        var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
  
        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) {
          reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0);
          return reAllowedSVGTagNames.test(el.tagName) &&
                !hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol|metadata)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
        });
  
        if (!elements || (elements && !elements.length)) {
          callback && callback([], {});
          return;
        }
  
        var options = {
          width: widthAttr,
          height: heightAttr,
          svgUid: svgUid,
          toBeParsed: toBeParsed
        };
  
        fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
        fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
        // Precedence of rules:   style > class > attribute
        fabric.parseElements(elements, function(instances) {
          fabric.documentParsingTime = new Date() - startTime;
          if (callback) {
            callback(instances, options);
          }
        }, clone(options), reviver);
      };
    })();
  
    /**
     * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
     * @namespace
     */
    var svgCache = {
  
      /**
       * @param {String} name
       * @param {Function} callback
       */
      has: function (name, callback) {
        callback(false);
      },
  
      get: function () {
        /* NOOP */
      },
  
      set: function () {
        /* NOOP */
      }
    };
  
    /**
     * @private
     */
    function _enlivenCachedObject(cachedObject) {
  
      var objects = cachedObject.objects,
          options = cachedObject.options;
  
      objects = objects.map(function (o) {
        return fabric[capitalize(o.type)].fromObject(o);
      });
  
      return ({ objects: objects, options: options });
    }
  
    /**
     * @private
     */
    function _createSVGPattern(markup, canvas, property) {
      if (canvas[property] && canvas[property].toSVG) {
        markup.push(
          '<pattern x="0" y="0" id="', property, 'Pattern" ',
            'width="', canvas[property].source.width,
            '" height="', canvas[property].source.height,
            '" patternUnits="userSpaceOnUse">',
          '<image x="0" y="0" ',
          'width="', canvas[property].source.width,
          '" height="', canvas[property].source.height,
          '" xlink:href="', canvas[property].source.src,
          '"></image></pattern>'
        );
      }
    }
  
    var reFontDeclaration = new RegExp(
      '(normal|italic)?\\s*(normal|small-caps)?\\s*' +
      '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
        fabric.reNum +
      '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
  
    extend(fabric, {
      /**
       * Parses a short font declaration, building adding its properties to a style object
       * @static
       * @function
       * @memberOf fabric
       * @param {String} value font declaration
       * @param {Object} oStyle definition
       */
      parseFontDeclaration: function(value, oStyle) {
        var match = value.match(reFontDeclaration);
  
        if (!match) {
          return;
        }
        var fontStyle = match[1],
            // font variant is not used
            // fontVariant = match[2],
            fontWeight = match[3],
            fontSize = match[4],
            lineHeight = match[5],
            fontFamily = match[6];
  
        if (fontStyle) {
          oStyle.fontStyle = fontStyle;
        }
        if (fontWeight) {
          oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
        }
        if (fontSize) {
          oStyle.fontSize = parseUnit(fontSize);
        }
        if (fontFamily) {
          oStyle.fontFamily = fontFamily;
        }
        if (lineHeight) {
          oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
        }
      },
  
      /**
       * Parses an SVG document, returning all of the gradient declarations found in it
       * @static
       * @function
       * @memberOf fabric
       * @param {SVGDocument} doc SVG document to parse
       * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
       */
      getGradientDefs: function(doc) {
        var linearGradientEls = doc.getElementsByTagName('linearGradient'),
            radialGradientEls = doc.getElementsByTagName('radialGradient'),
            el, i, j = 0, id, xlink, elList = [ ],
            gradientDefs = { }, idsToXlinkMap = { };
  
        elList.length = linearGradientEls.length + radialGradientEls.length;
        i = linearGradientEls.length;
        while (i--) {
          elList[j++] = linearGradientEls[i];
        }
        i = radialGradientEls.length;
        while (i--) {
          elList[j++] = radialGradientEls[i];
        }
  
        while (j--) {
          el = elList[j];
          xlink = el.getAttribute('xlink:href');
          id = el.getAttribute('id');
          if (xlink) {
            idsToXlinkMap[id] = xlink.substr(1);
          }
          gradientDefs[id] = el;
        }
  
        for (id in idsToXlinkMap) {
          var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
          el = gradientDefs[id];
          while (el2.firstChild) {
            el.appendChild(el2.firstChild);
          }
        }
        return gradientDefs;
      },
  
      /**
       * Returns an object of attributes' name/value, given element and an array of attribute names;
       * Parses parent "g" nodes recursively upwards.
       * @static
       * @memberOf fabric
       * @param {DOMElement} element Element to parse
       * @param {Array} attributes Array of attributes to parse
       * @return {Object} object containing parsed attributes' names/values
       */
      parseAttributes: function(element, attributes, svgUid) {
  
        if (!element) {
          return;
        }
  
        var value,
            parentAttributes = { },
            fontSize;
  
        if (typeof svgUid === 'undefined') {
          svgUid = element.getAttribute('svgUid');
        }
        // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
        if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {
          parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
        }
        fontSize = (parentAttributes && parentAttributes.fontSize ) ||
                   element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
  
        var ownAttributes = attributes.reduce(function(memo, attr) {
          value = element.getAttribute(attr);
          if (value) {
            attr = normalizeAttr(attr);
            value = normalizeValue(attr, value, parentAttributes, fontSize);
  
            memo[attr] = value;
          }
          return memo;
        }, { });
  
        // add values parsed from style, which take precedence over attributes
        // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
        ownAttributes = extend(ownAttributes,
          extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
        if (ownAttributes.font) {
          fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
        }
        return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
      },
  
      /**
       * Transforms an array of svg elements to corresponding fabric.* instances
       * @static
       * @memberOf fabric
       * @param {Array} elements Array of elements to parse
       * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
       * @param {Object} [options] Options object
       * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
       */
      parseElements: function(elements, callback, options, reviver) {
        new fabric.ElementsParser(elements, callback, options, reviver).parse();
      },
  
      /**
       * Parses "style" attribute, retuning an object with values
       * @static
       * @memberOf fabric
       * @param {SVGElement} element Element to parse
       * @return {Object} Objects with values parsed from style attribute of an element
       */
      parseStyleAttribute: function(element) {
        var oStyle = { },
            style = element.getAttribute('style');
  
        if (!style) {
          return oStyle;
        }
  
        if (typeof style === 'string') {
          parseStyleString(style, oStyle);
        }
        else {
          parseStyleObject(style, oStyle);
        }
  
        return oStyle;
      },
  
      /**
       * Parses "points" attribute, returning an array of values
       * @static
       * @memberOf fabric
       * @param {String} points points attribute string
       * @return {Array} array of points
       */
      parsePointsAttribute: function(points) {
  
        // points attribute is required and must not be empty
        if (!points) {
          return null;
        }
  
        // replace commas with whitespace and remove bookending whitespace
        points = points.replace(/,/g, ' ').trim();
  
        points = points.split(/\s+/);
        var parsedPoints = [ ], i, len;
  
        i = 0;
        len = points.length;
        for (; i < len; i+=2) {
          parsedPoints.push({
            x: parseFloat(points[i]),
            y: parseFloat(points[i + 1])
          });
        }
  
        // odd number of points is an error
        // if (parsedPoints.length % 2 !== 0) {
        //   return null;
        // }
  
        return parsedPoints;
      },
  
      /**
       * Returns CSS rules for a given SVG document
       * @static
       * @function
       * @memberOf fabric
       * @param {SVGDocument} doc SVG document to parse
       * @return {Object} CSS rules of this document
       */
      getCSSRules: function(doc) {
        var styles = doc.getElementsByTagName('style'),
            allRules = { }, rules;
  
        // very crude parsing of style contents
        for (var i = 0, len = styles.length; i < len; i++) {
          var styleContents = styles[i].textContent;
  
          // remove comments
          styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
          if (styleContents.trim() === '') {
            continue;
          }
          rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
          rules = rules.map(function(rule) { return rule.trim(); });
  
          rules.forEach(function(rule) {
  
            var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
            ruleObj = { }, declaration = match[2].trim(),
            propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
  
            for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
              var pair = propertyValuePairs[i].split(/\s*:\s*/),
                  property = normalizeAttr(pair[0]),
                  value = normalizeValue(property, pair[1], pair[0]);
              ruleObj[property] = value;
            }
            rule = match[1];
            rule.split(',').forEach(function(_rule) {
              _rule = _rule.replace(/^svg/i, '').trim();
              if (_rule === '') {
                return;
              }
              allRules[_rule] = fabric.util.object.clone(ruleObj);
            });
          });
        }
        return allRules;
      },
  
      /**
       * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
       * @memberof fabric
       * @param {String} url
       * @param {Function} callback
       * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
       */
      loadSVGFromURL: function(url, callback, reviver) {
  
        url = url.replace(/^\n\s*/, '').trim();
        svgCache.has(url, function (hasUrl) {
          if (hasUrl) {
            svgCache.get(url, function (value) {
              var enlivedRecord = _enlivenCachedObject(value);
              callback(enlivedRecord.objects, enlivedRecord.options);
            });
          }
          else {
            new fabric.util.request(url, {
              method: 'get',
              onComplete: onComplete
            });
          }
        });
  
        function onComplete(r) {
  
          var xml = r.responseXML;
          if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
            xml = new ActiveXObject('Microsoft.XMLDOM');
            xml.async = 'false';
            //IE chokes on DOCTYPE
            xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
          }
          if (!xml || !xml.documentElement) {
            return;
          }
  
          fabric.parseSVGDocument(xml.documentElement, function (results, options) {
            svgCache.set(url, {
              objects: fabric.util.array.invoke(results, 'toObject'),
              options: options
            });
            callback(results, options);
          }, reviver);
        }
      },
  
      /**
       * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
       * @memberof fabric
       * @param {String} string
       * @param {Function} callback
       * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
       */
      loadSVGFromString: function(string, callback, reviver) {
        string = string.trim();
        var doc;
        if (typeof DOMParser !== 'undefined') {
          var parser = new DOMParser();
          if (parser && parser.parseFromString) {
            doc = parser.parseFromString(string, 'text/xml');
          }
        }
        else if (fabric.window.ActiveXObject) {
          doc = new ActiveXObject('Microsoft.XMLDOM');
          doc.async = 'false';
          // IE chokes on DOCTYPE
          doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
        }
  
        fabric.parseSVGDocument(doc.documentElement, function (results, options) {
          callback(results, options);
        }, reviver);
      },
  
      /**
       * Creates markup containing SVG font faces
       * @param {Array} objects Array of fabric objects
       * @return {String}
       */
      createSVGFontFacesMarkup: function(objects) {
        var markup = '';
  
        for (var i = 0, len = objects.length; i < len; i++) {
          if (objects[i].type !== 'text' || !objects[i].path) {
            continue;
          }
  
          markup += [
            //jscs:disable validateIndentation
            '@font-face {',
              'font-family: ', objects[i].fontFamily, '; ',
              'src: url(\'', objects[i].path, '\')',
            '}'
            //jscs:enable validateIndentation
          ].join('');
        }
  
        if (markup) {
          markup = [
            //jscs:disable validateIndentation
            '<style type="text/css">',
              '<![CDATA[',
                markup,
              ']]>',
            '</style>'
            //jscs:enable validateIndentation
          ].join('');
        }
  
        return markup;
      },
  
      /**
       * Creates markup containing SVG referenced elements like patterns, gradients etc.
       * @param {fabric.Canvas} canvas instance of fabric.Canvas
       * @return {String}
       */
      createSVGRefElementsMarkup: function(canvas) {
        var markup = [ ];
  
        _createSVGPattern(markup, canvas, 'backgroundColor');
        _createSVGPattern(markup, canvas, 'overlayColor');
  
        return markup.join('');
      }
    });
  
  })(typeof exports !== 'undefined' ? exports : this);
  