text: (function(global) {
  
    'use strict';
  
    var fabric = global.fabric || (global.fabric = {}),
        extend = fabric.util.object.extend,
        min = fabric.util.array.min,
        max = fabric.util.array.max,
        invoke = fabric.util.array.invoke;
  
    if (fabric.Group) {
      return;
    }
  
    // lock-related properties, for use in fabric.Group#get
    // to enable locking behavior on group
    // when one of its objects has lock-related properties set
    var _lockProperties = {
      lockMovementX: true,
      lockMovementY: true,
      lockRotation: true,
      lockScalingX: true,
      lockScalingY: true,
      lockUniScaling: true
    };
  
    /**
     * Group class
     * @class fabric.Group
     * @extends fabric.Object
     * @mixes fabric.Collection
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups}
     * @see {@link fabric.Group#initialize} for constructor definition
     */
    fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: 'group',
      /**
       * Constructor
       * @param {Object} objects Group objects
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function(objects, options) {
        options = options || {};
  
        this._objects = objects || [];
        for (var i = this._objects.length; i--; ) {
          this._objects[i].group = this;
        }
  
        this.originalState = { };
  
        if (options.originX) {
          this.originX = options.originX;
        }
  
        if (options.originY) {
          this.originY = options.originY;
        }
  
        this._refreshControlsVisibility();
  
        this._calcBounds();
        this._updateObjectsCoords();
  
        this.callSuper('initialize', options);
  
        this.setCoords();
        this.saveCoords();
      },
      /**
       * To be implemented by other shape classes as needed. This is to allow a
       * shape to impose its own rules for control visibility on the group if it is
       * part of one.
       */
      _refreshControlsVisibility: function() {
      },
      /**
       * @private
       */
      _updateObjectsCoords: function() {
        this.forEachObject(this._updateObjectCoords, this);
      },
      /**
       * @private
       */
      _updateObjectCoords: function(object) {
        var objectLeft = object.getLeft(),
            objectTop = object.getTop(),
            center = this.getCenterPoint();
  
        object.set({
          originalLeft: objectLeft,
          originalTop: objectTop,
          left: objectLeft - center.x,
          top: objectTop - center.y
        });
  
        object.setCoords();
  
        // do not display corners of objects enclosed in a group
        object.__origHasControls = object.hasControls;
        object.hasControls = false;
      },
      /**
       * Returns string represenation of a group
       * @return {String}
       */
      toString: function() {
        return '#<fabric.Group: (' + this.complexity() + ')>';
      },
      /**
       * Adds an object to a group; Then recalculates group's dimension, position.
       * @param {Object} object
       * @return {fabric.Group} thisArg
       * @chainable
       */
      addWithUpdate: function(object) {
        this._restoreObjectsState();
        if (object) {
          this._objects.push(object);
          object.group = this;
          object._set('canvas', this.canvas);
        }
        // since _restoreObjectsState set objects inactive
        this.forEachObject(this._setObjectActive, this);
  
        //reset the scale/angle before updating objectCoords
        this._resetTransform();
        this._calcBounds();
        this._updateObjectsCoords();
        return this;
      },
      /**
       * @private
       */
      _setObjectActive: function(object) {
        object.set('active', true);
        object.group = this;
      },
      /**
       * Removes an object from a group; Then recalculates group's dimension, position.
       * @param {Object} object
       * @return {fabric.Group} thisArg
       * @chainable
       */
      removeWithUpdate: function(object) {
        this._moveFlippedObject(object);
        this._restoreObjectsState();
  
        this.remove(object);
  
        // since _restoreObjectsState set objects inactive
        this.forEachObject(this._setObjectActive, this);
        //reset the scale/angle before updating objectCoords
        this._resetTransform();
        this._calcBounds();
        this._updateObjectsCoords();
  
        return this;
      },
  
      /**
       * @private
       */
      _resetTransform: function () {
        this.scaleX = 1;
        this.scaleY = 1;
        this.setAngle(0);
      },
  
      /**
       * @private
       */
      _onObjectAdded: function(object) {
        object.group = this;
        object._set('canvas', this.canvas);
        this._refreshControlsVisibility();
      },
      /**
       * @private
       */
      _onObjectRemoved: function(object) {
        delete object.group;
        object.set('active', false);
        this._refreshControlsVisibility();
      },
      /**
       * Properties that are delegated to group objects when reading/writing
       * @param {Object} delegatedProperties
       */
      delegatedProperties: {
        fill:             true,
        opacity:          true,
        fontFamily:       true,
        fontWeight:       true,
        fontSize:         true,
        fontStyle:        true,
        lineHeight:       true,
        textDecoration:   true,
        textAlign:        true,
        backgroundColor:  true
      },
      /**
       * @private
       */
      _set: function(key, value) {
        var i = this._objects.length;
        if (key in this.delegatedProperties || key === 'canvas') {
          while (i--) {
            this._objects[i].set(key, value);
          }
        }
        else {
          while (i--) {
            this._objects[i].setOnGroup(key, value);
          }
        }
        this.callSuper('_set', key, value);
      },
      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function(propertiesToInclude) {
        return extend(this.callSuper('toObject', propertiesToInclude), {
          objects: invoke(this._objects, 'toObject', propertiesToInclude)
        });
      },
      /**
       * Renders instance on a given context
       * @param {CanvasRenderingContext2D} ctx context to render instance on
       */
      render: function(ctx) {
        // do not render if object is not visible
        if (!this.visible) {
          return;
        }
  
        ctx.save();
        this.clipTo && fabric.util.clipContext(this, ctx);
        this.transform(ctx);
        // the array is now sorted in order of highest first, so start from end
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._renderObject(this._objects[i], ctx);
        }
  
        this.clipTo && ctx.restore();
  
        ctx.restore();
      },
      /**
       * Renders controls and borders for the object
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      _renderControls: function(ctx, noTransform) {
        this.callSuper('_renderControls', ctx, noTransform);
        //AS: we are removing the call to draw controls on the children of the group
        //as we always draw controls on any active object now.
      },
      /**
       * @private
       */
      _renderObject: function(object, ctx) {
        // do not render if object is not visible
        if (!object.visible) {
          return;
        }
  
        var originalHasRotatingPoint = object.hasRotatingPoint;
        object.hasRotatingPoint = false;
        object.render(ctx);
        object.hasRotatingPoint = originalHasRotatingPoint;
      },
      /**
       * Retores original state of each of group objects (original state is that which was before group was created).
       * @private
       * @return {fabric.Group} thisArg
       * @chainable
       */
      _restoreObjectsState: function() {
        this._objects.forEach(this._restoreObjectState, this);
        return this;
      },
      /**
       * Realises the transform from this group onto the supplied object
       * i.e. it tells you what would happen if the supplied object was in
       * the group, and then the group was destroyed. It mutates the supplied
       * object.
       * @param {fabric.Object} object
       * @return {fabric.Object} transformedObject
      */
      realizeTransform: function(object) {
        this._moveFlippedObject(object);
        this._setObjectPosition(object);
        return object;
      },
      /**
       * Moves a flipped object to the position where it's displayed
       * @private
       * @param {fabric.Object} object
       * @return {fabric.Group} thisArg
       */
      _moveFlippedObject: function(object) {
        var oldOriginX = object.get('originX'),
                oldOriginY = object.get('originY'),
                center = object.getCenterPoint();
  
        object.set({
          originX: 'center',
          originY: 'center',
          left: center.x,
          top: center.y
        });
  
        this._toggleFlipping(object);
  
        var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY);
  
        object.set({
          originX: oldOriginX,
          originY: oldOriginY,
          left: newOrigin.x,
          top: newOrigin.y
        });
  
        return this;
      },
      /**
       * @private
       */
      _toggleFlipping: function(object) {
        if (this.flipX) {
          object.toggle('flipX');
          object.set('left', -object.get('left'));
          object.setAngle(-object.getAngle());
        }
        if (this.flipY) {
          object.toggle('flipY');
          object.set('top', -object.get('top'));
          object.setAngle(-object.getAngle());
        }
      },
      /**
       * Restores original state of a specified object in group
       * @private
       * @param {fabric.Object} object
       * @return {fabric.Group} thisArg
       */
      _restoreObjectState: function(object) {
        this._setObjectPosition(object);
  
        object.hasControls = object.__origHasControls;
        delete object.__origHasControls;
        object.set('active', false);
        object.setCoords();
        delete object.group;
  
        return this;
      },
      /**
       * @private
       */
      _setObjectPosition: function(object) {
        var center = this.getCenterPoint(),
                rotated = this._getRotatedLeftTop(object);
  
        object.set({
          angle: object.getAngle() + this.getAngle(),
          left: center.x + rotated.left,
          top: center.y + rotated.top,
          scaleX: object.get('scaleX') * this.get('scaleX'),
          scaleY: object.get('scaleY') * this.get('scaleY')
        });
      },
      /**
       * @private
       */
      _getRotatedLeftTop: function(object) {
        var groupAngle = this.getAngle() * (Math.PI / 180);
        return {
          left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') +
                  Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')),
          top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') +
                  Math.sin(groupAngle) * object.getLeft() * this.get('scaleX'))
        };
      },
      /**
       * Destroys a group (restoring state of its objects)
       * @return {fabric.Group} thisArg
       * @chainable
       */
      destroy: function() {
        this._objects.forEach(this._moveFlippedObject, this);
        return this._restoreObjectsState();
      },
      /**
       * Saves coordinates of this instance (to be used together with `hasMoved`)
       * @saveCoords
       * @return {fabric.Group} thisArg
       * @chainable
       */
      saveCoords: function() {
        this._originalLeft = this.get('left');
        this._originalTop = this.get('top');
        return this;
      },
      /**
       * Checks whether this group was moved (since `saveCoords` was called last)
       * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
       */
      hasMoved: function() {
        return this._originalLeft !== this.get('left') ||
                this._originalTop !== this.get('top');
      },
      /**
       * Sets coordinates of all group objects
       * @return {fabric.Group} thisArg
       * @chainable
       */
      setObjectsCoords: function() {
        this.forEachObject(function(object) {
          object.setCoords();
        });
        return this;
      },
      /**
       * @private
       */
      _calcBounds: function(onlyWidthHeight) {
        var aX = [],
            aY = [],
            o, prop,
            props = ['tr', 'br', 'bl', 'tl'];
  
        for (var i = 0, len = this._objects.length; i < len; ++i) {
          o = this._objects[i];
          o.setCoords();
          for (var j = 0; j < props.length; j++) {
            prop = props[j];
            aX.push(o.oCoords[prop].x);
            aY.push(o.oCoords[prop].y);
          }
        }
  
        this.set(this._getBounds(aX, aY, onlyWidthHeight));
      },
      /**
       * @private
       */
      _getBounds: function(aX, aY, onlyWidthHeight) {
        var ivt = fabric.util.invertTransform(this.getViewportTransform()),
            minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt),
            maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt),
            obj = {
              width: (maxXY.x - minXY.x) || 0,
              height: (maxXY.y - minXY.y) || 0
            };
  
        if (!onlyWidthHeight) {
          obj.left = minXY.x || 0;
          obj.top = minXY.y || 0;
          if (this.originX === 'center') {
            obj.left += obj.width / 2;
          }
          if (this.originX === 'right') {
            obj.left += obj.width;
          }
          if (this.originY === 'center') {
            obj.top += obj.height / 2;
          }
          if (this.originY === 'bottom') {
            obj.top += obj.height;
          }
        }
        return obj;
      },
      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function(reviver) {
        var markup = [
          //jscs:disable validateIndentation
          '<g ',
          'transform="', this.getSvgTransform(),
          '">\n'
          //jscs:enable validateIndentation
        ];
  
        for (var i = 0, len = this._objects.length; i < len; i++) {
          markup.push(this._objects[i].toSVG(reviver));
        }
  
        markup.push('</g>\n');
  
        return reviver ? reviver(markup.join('')) : markup.join('');
      },
      /* _TO_SVG_END_ */
  
      /**
       * Returns requested property
       * @param {String} prop Property to get
       * @return {Any}
       */
      get: function(prop) {
        if (prop in _lockProperties) {
          if (this[prop]) {
            return this[prop];
          }
          else {
            for (var i = 0, len = this._objects.length; i < len; i++) {
              if (this._objects[i][prop]) {
                return true;
              }
            }
            return false;
          }
        }
        else {
          if (prop in this.delegatedProperties) {
            return this._objects[0] && this._objects[0].get(prop);
          }
          return this[prop];
        }
      }
    });
  
    /**
     * Returns {@link fabric.Group} instance from an object representation
     * @static
     * @memberOf fabric.Group
     * @param {Object} object Object to create a group from
     * @param {Function} [callback] Callback to invoke when an group instance is created
     * @return {fabric.Group} An instance of fabric.Group
     */
    fabric.Group.fromObject = function(object, callback) {
      fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
        delete object.objects;
        callback && callback(new fabric.Group(enlivenedObjects, object));
      });
    };
  
    /**
     * Indicates that instances of this type are async
     * @static
     * @memberOf fabric.Group
     * @type Boolean
     * @default
     */
    fabric.Group.async = true;
  
  })(typeof exports !== 'undefined' ? exports : this);
  