text: (function(global) {
  
    'use strict';
  
    /**
     * @name fabric
     * @namespace
     */
  
    var fabric = global.fabric || (global.fabric = { }),
        extend = fabric.util.object.extend,
        clone = fabric.util.object.clone,
        toFixed = fabric.util.toFixed,
        parseUnit = fabric.util.parseUnit,
        multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
  
        svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line',
          'image', 'text'],
        svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'],
        svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'],
        svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'],
  
        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',
          'letter-spacing':     'charSpacing',
          'paint-order':        'paintFirst',
          'stroke-dasharray':   'strokeDashArray',
          'stroke-dashoffset':  'strokeDashOffset',
          'stroke-linecap':     'strokeLineCap',
          'stroke-linejoin':    'strokeLineJoin',
          'stroke-miterlimit':  'strokeMiterLimit',
          'stroke-opacity':     'strokeOpacity',
          'stroke-width':       'strokeWidth',
          'text-decoration':    'textDecoration',
          'text-anchor':        'textAnchor',
          opacity:              'opacity',
          'clip-path':          'clipPath',
          'clip-rule':          'clipRule',
          'vector-effect':      'strokeUniform'
        },
  
        colorAttributes = {
          stroke: 'strokeOpacity',
          fill:   'fillOpacity'
        },
  
        fSize = 'font-size', cPath = 'clip-path';
  
    fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames);
    fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements);
    fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors);
    fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents);
  
    fabric.cssRules = { };
    fabric.gradientDefs = { };
    fabric.clipPaths = { };
  
    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 === 'vector-effect') {
        value = value === 'non-scaling-stroke';
      }
      else if (attr === 'strokeDashArray') {
        if (value === 'none') {
          value = null;
        }
        else {
          value = value.replace(/,/g, ' ').split(/\s+/).map(parseFloat);
        }
      }
      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';
        // display=none on parent element always takes precedence over child element
        if (parentAttributes && parentAttributes.visible === false) {
          value = false;
        }
      }
      else if (attr === 'opacity') {
        value = parseFloat(value);
        if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
          value *= parentAttributes.opacity;
        }
      }
      else if (attr === 'textAnchor' /* text-anchor */) {
        value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
      }
      else if (attr === 'charSpacing') {
        // parseUnit returns px and we convert it to em
        parsed = parseUnit(value, fontSize) / fontSize * 1000;
      }
      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 if (attr === 'href' || attr === 'xlink:href') {
        return value;
      }
      else {
        parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
      }
  
      return (!isArray && isNaN(parsed) ? value : parsed);
    }
  
    /**
      * @private
      */
    function getSvgRegex(arr) {
      return new RegExp('^(' + arr.join('|') + ')\\b', 'i');
    }
  
    /**
     * @private
     * @param {Object} attributes Array of attributes to parse
     */
    function _setStrokeFillOpacity(attributes) {
      for (var attr in colorAttributes) {
  
        if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
          continue;
        }
  
        if (typeof attributes[attr] === 'undefined') {
          if (!fabric.Object.prototype[attr]) {
            continue;
          }
          attributes[attr] = fabric.Object.prototype[attr];
        }
  
        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;
    }
  
    /**
     * @private
     */
    function _getMultipleNodes(doc, nodeNames) {
      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));
      }
      return nodeArray;
    }
  
    /**
     * 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 cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]),
            x = 0, y = 0;
        if (args.length === 3) {
          x = args[1];
          y = args[2];
        }
  
        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) {
        var multiplierX = args[0],
            multiplierY = (args.length === 2) ? args[1] : args[0];
  
        matrix[0] = multiplierX;
        matrix[3] = multiplierY;
      }
  
      function skewMatrix(matrix, args, pos) {
        matrix[pos] = 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 = fabric.iMatrix,
  
          // == 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) {
                // match !== '' && match != null
                return (!!match);
              }),
              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':
              skewMatrix(matrix, args, 2);
              break;
            case 'skewY':
              skewMatrix(matrix, args, 1);
              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 = pair[0].trim().toLowerCase();
        value =  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 = prop.toLowerCase();
        value = 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;
      // 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 (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 and on node xmlDOM
     */
    function elementById(doc, id) {
      var el;
      doc.getElementById && (el = doc.getElementById(id));
      if (el) {
        return el;
      }
      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;
        }
      }
    }
  
    /**
     * @private
     */
    function parseUseDirectives(doc) {
      var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
      while (nodelist.length && i < nodelist.length) {
        var el = nodelist[i],
            xlink = (el.getAttribute('xlink:href') || el.getAttribute('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, attr, j, attrs, len;
  
        applyViewboxTransform(el2);
        if (/^svg$/i.test(el2.nodeName)) {
          var el3 = el2.ownerDocument.createElement('g');
          for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) {
            attr = attrs.item(j);
            el3.setAttribute(attr.nodeName, attr.nodeValue);
          }
          // el2.firstChild != null
          while (el2.firstChild) {
            el3.appendChild(el2.firstChild);
          }
          el2 = el3;
        }
  
        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' || attr.nodeName === '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 applyViewboxTransform(element) {
  
      var viewBoxAttr = element.getAttribute('viewBox'),
          scaleX = 1,
          scaleY = 1,
          minX = 0,
          minY = 0,
          viewBoxWidth, viewBoxHeight, matrix, el,
          widthAttr = element.getAttribute('width'),
          heightAttr = element.getAttribute('height'),
          x = element.getAttribute('x') || 0,
          y = element.getAttribute('y') || 0,
          preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
          missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName)
                             || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
          missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
          toBeParsed = missingViewBox && missingDimAttr,
          parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0;
  
      parsedDim.width = 0;
      parsedDim.height = 0;
      parsedDim.toBeParsed = toBeParsed;
  
      if (toBeParsed) {
        return parsedDim;
      }
  
      if (missingViewBox) {
        parsedDim.width = parseUnit(widthAttr);
        parsedDim.height = parseUnit(heightAttr);
        return parsedDim;
      }
      minX = -parseFloat(viewBoxAttr[1]);
      minY = -parseFloat(viewBoxAttr[2]);
      viewBoxWidth = parseFloat(viewBoxAttr[3]);
      viewBoxHeight = parseFloat(viewBoxAttr[4]);
      parsedDim.minX = minX;
      parsedDim.minY = minY;
      parsedDim.viewBoxWidth = viewBoxWidth;
      parsedDim.viewBoxHeight = viewBoxHeight;
      if (!missingDimAttr) {
        parsedDim.width = parseUnit(widthAttr);
        parsedDim.height = parseUnit(heightAttr);
        scaleX = parsedDim.width / viewBoxWidth;
        scaleY = parsedDim.height / viewBoxHeight;
      }
      else {
        parsedDim.width = viewBoxWidth;
        parsedDim.height = viewBoxHeight;
      }
  
      // default is to preserve aspect ratio
      preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
      if (preserveAspectRatio.alignX !== 'none') {
        //translate all container for the effect of Mid, Min, Max
        if (preserveAspectRatio.meetOrSlice === 'meet') {
          scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
          // calculate additional translation to move the viewbox
        }
        if (preserveAspectRatio.meetOrSlice === 'slice') {
          scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY);
          // calculate additional translation to move the viewbox
        }
        widthDiff = parsedDim.width - viewBoxWidth * scaleX;
        heightDiff = parsedDim.height - viewBoxHeight * scaleX;
        if (preserveAspectRatio.alignX === 'Mid') {
          widthDiff /= 2;
        }
        if (preserveAspectRatio.alignY === 'Mid') {
          heightDiff /= 2;
        }
        if (preserveAspectRatio.alignX === 'Min') {
          widthDiff = 0;
        }
        if (preserveAspectRatio.alignY === 'Min') {
          heightDiff = 0;
        }
      }
  
      if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
        return parsedDim;
      }
  
      if (x || y) {
        translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
      }
  
      matrix = translateMatrix + ' matrix(' + scaleX +
                    ' 0' +
                    ' 0 ' +
                    scaleY + ' ' +
                    (minX * scaleX + widthDiff) + ' ' +
                    (minY * scaleY + heightDiff) + ') ';
      parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix);
      if (element.nodeName === 'svg') {
        el = element.ownerDocument.createElement('g');
        // element.firstChild != null
        while (element.firstChild) {
          el.appendChild(element.firstChild);
        }
        element.appendChild(el);
      }
      else {
        el = element;
        matrix = el.getAttribute('transform') + matrix;
      }
      el.setAttribute('transform', matrix);
      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
     * @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.
     * @param {Object} [parsingOptions] options for parsing document
     * @param {String} [parsingOptions.crossOrigin] crossOrigin settings
     */
    fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
      if (!doc) {
        return;
      }
  
      parseUseDirectives(doc);
  
      var svgUid =  fabric.Object.__uid++, i, len,
          options = applyViewboxTransform(doc),
          descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
      options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
      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 (i = 0, len = descendants.length; i < len; i++) {
          arr[i] = descendants[i];
        }
        descendants = arr;
      }
  
      var elements = descendants.filter(function(el) {
        applyViewboxTransform(el);
        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)) {
        callback && callback([], {});
        return;
      }
      var clipPaths = { };
      descendants.filter(function(el) {
        return el.nodeName.replace('svg:', '') === 'clipPath';
      }).forEach(function(el) {
        var id = el.getAttribute('id');
        clipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) {
          return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', ''));
        });
      });
      fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
      fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
      fabric.clipPaths[svgUid] = clipPaths;
      // Precedence of rules:   style > class > attribute
      fabric.parseElements(elements, function(instances, elements) {
        if (callback) {
          callback(instances, options, elements, descendants);
          delete fabric.gradientDefs[svgUid];
          delete fabric.cssRules[svgUid];
          delete fabric.clipPaths[svgUid];
        }
      }, clone(options), reviver, parsingOptions);
    };
  
    function recursivelyParseGradientsXlink(doc, gradient) {
      var gradientsAttrs = ['gradientTransform', 'x1', 'x2', 'y1', 'y2', 'gradientUnits', 'cx', 'cy', 'r', 'fx', 'fy'],
          xlinkAttr = 'xlink:href',
          xLink = gradient.getAttribute(xlinkAttr).substr(1),
          referencedGradient = elementById(doc, xLink);
      if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) {
        recursivelyParseGradientsXlink(doc, referencedGradient);
      }
      gradientsAttrs.forEach(function(attr) {
        if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) {
          gradient.setAttribute(attr, referencedGradient.getAttribute(attr));
        }
      });
      if (!gradient.children.length) {
        var referenceClone = referencedGradient.cloneNode(true);
        while (referenceClone.firstChild) {
          gradient.appendChild(referenceClone.firstChild);
        }
      }
      gradient.removeAttribute(xlinkAttr);
    }
  
    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 tagArray = [
              'linearGradient',
              'radialGradient',
              'svg:linearGradient',
              'svg:radialGradient'],
            elList = _getMultipleNodes(doc, tagArray),
            el, j = 0, gradientDefs = { };
        j = elList.length;
        while (j--) {
          el = elList[j];
          if (el.getAttribute('xlink:href')) {
            recursivelyParseGradientsXlink(doc, el);
          }
          gradientDefs[el.getAttribute('id')] = el;
        }
        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, parentFontSize;
  
        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 && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) {
          parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
        }
  
        var ownAttributes = attributes.reduce(function(memo, attr) {
          value = element.getAttribute(attr);
          if (value) { // eslint-disable-line
            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)
        var cssAttrs = extend(
          getGlobalStylesForElement(element, svgUid),
          fabric.parseStyleAttribute(element)
        );
        ownAttributes = extend(
          ownAttributes,
          cssAttrs
        );
        if (cssAttrs[cPath]) {
          element.setAttribute(cPath, cssAttrs[cPath]);
        }
        fontSize = parentFontSize = parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE;
        if (ownAttributes[fSize]) {
          // looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers.
          ownAttributes[fSize] = fontSize = parseUnit(ownAttributes[fSize], parentFontSize);
        }
  
        var normalizedAttr, normalizedValue, normalizedStyle = {};
        for (var attr in ownAttributes) {
          normalizedAttr = normalizeAttr(attr);
          normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
          normalizedStyle[normalizedAttr] = normalizedValue;
        }
        if (normalizedStyle && normalizedStyle.font) {
          fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
        }
        var mergedAttrs = extend(parentAttributes, normalizedStyle);
        return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
      },
  
      /**
       * 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, parsingOptions) {
        new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).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;
  
        for (i = 0, len = points.length; 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'), i, len,
            allRules = { }, rules;
  
        // very crude parsing of style contents
        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;
  
          // 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(); });
          // 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 (i = 0, len = propertyValuePairs.length; i < len; i++) {
              var pair = propertyValuePairs[i].split(/\s*:\s*/),
                  property = pair[0],
                  value = pair[1];
              ruleObj[property] = value;
            }
            rule = match[1];
            rule.split(',').forEach(function(_rule) {
              _rule = _rule.replace(/^svg/i, '').trim();
              if (_rule === '') {
                return;
              }
              if (allRules[_rule]) {
                fabric.util.object.extend(allRules[_rule], ruleObj);
              }
              else {
                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.
       * @param {Object} [options] Object containing options for parsing
       * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
       */
      loadSVGFromURL: function(url, callback, reviver, options) {
  
        url = url.replace(/^\n\s*/, '').trim();
        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) {
            callback && callback(null);
            return false;
          }
  
          fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) {
            callback && callback(results, _options, elements, allElements);
          }, reviver, options);
        }
      },
  
      /**
       * 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.
       * @param {Object} [options] Object containing options for parsing
       * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
       */
      loadSVGFromString: function(string, callback, reviver, options) {
        string = string.trim();
        var doc;
        if (typeof fabric.window.DOMParser !== 'undefined') {
          var parser = new fabric.window.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, elements, allElements) {
          callback(results, _options, elements, allElements);
        }, reviver, options);
      }
    });
  
  })(typeof exports !== 'undefined' ? exports : this);
  