    /**
     * A Sprite is an object rendered in a Drawing surface.
     *
     * ## Types
     *
     * The following sprite types are supported:
     *
     * ### Rect
     *
     * Rectangle requires `width` and `height` attributes:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: 'rect',
     *             width: 100,
     *             height: 50,
     *             radius: 10,
     *             fill: 'green',
     *             opacity: 0.5,
     *             stroke: 'red',
     *             'stroke-width': 2
     *         }]
     *     });
     *
     * ### Circle
     *
     * Circle requires `x`, `y` and `radius` attributes:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: 'circle',
     *             radius: 90,
     *             x: 100,
     *             y: 100,
     *             fill: 'blue'
     *         }]
     *     });
     *
     * ### Ellipse
     *
     * Ellipse requires `x`, `y`, `radiusX` and `radiusY` attributes:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: "ellipse",
     *             radiusX: 100,
     *             radiusY: 50,
     *             x: 100,
     *             y: 100,
     *             fill: 'red'
     *         }]
     *     });
     *
     * ### Path
     *
     * Path requires the `path` attribute:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: "path",
     *             path: "M-66.6 26C-66.6 26 -75 22 -78.2 18.4C-81.4 14.8 -80.948 19.966 " +
     *                   "-85.8 19.6C-91.647 19.159 -90.6 3.2 -90.6 3.2L-94.6 10.8C-94.6 " +
     *                   "10.8 -95.8 25.2 -87.8 22.8C-83.893 21.628 -82.6 23.2 -84.2 " +
     *                   "24C-85.8 24.8 -78.6 25.2 -81.4 26.8C-84.2 28.4 -69.8 23.2 -72.2 " +
     *                   "33.6L-66.6 26z",
     *             fill: "purple"
     *         }]
     *     });
     *
     * ### Text
     *
     * Text requires the `text` attribute:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: "text",
     *             text: "Hello, Sprite!",
     *             fill: "green",
     *             font: "18px monospace"
     *         }]
     *     });
     *
     * ### Image
     *
     * Image requires `width`, `height` and `src` attributes:
     *
     *     @example
     *     Ext.create('Ext.draw.Component', {
     *         renderTo: Ext.getBody(),
     *         width: 200,
     *         height: 200,
     *         items: [{
     *             type: "image",
     *             src: "http://www.sencha.com/img/apple-touch-icon.png",
     *             width: 200,
     *             height: 200
     *         }]
     *     });
     *
     * ## Creating and adding a Sprite to a Surface
     *
     * See {@link Ext.draw.Surface} documentation.
     *
     * ## Transforming sprites
     *
     * See {@link #setAttributes} method documentation for examples on how to translate, scale and rotate the sprites.
     *
     */
    Ext.define('Ext.draw.Sprite', {

        /* Begin Definitions */

        mixins: {
            observable: 'Ext.util.Observable',
            animate: 'Ext.util.Animate'
        },

        requires: ['Ext.draw.SpriteDD'],

        /* End Definitions */

        /**
         * @cfg {String} type The type of the sprite.
         * Possible options are 'circle', 'ellipse', 'path', 'rect', 'text', 'image'.
         *
         * See {@link Ext.draw.Sprite} class documentation for examples of all types.
         */

        /**
         * @cfg {Number} width The width of the rect or image sprite.
         */

        /**
         * @cfg {Number} height The height of the rect or image sprite.
         */

        /**
         * @cfg {Number} radius The radius of the circle sprite. Or in case of rect sprite, the border radius.
         */

        /**
         * @cfg {Number} radiusX The radius of the ellipse sprite along x-axis.
         */

        /**
         * @cfg {Number} radiusY The radius of the ellipse sprite along y-axis.
         */

        /**
         * @cfg {Number} x Sprite position along the x-axis.
         */

        /**
         * @cfg {Number} y Sprite position along the y-axis.
         */

        /**
         * @cfg {String} path The path of the path sprite written in SVG-like path syntax.
         */

        /**
         * @cfg {Number} opacity The opacity of the sprite. A number between 0 and 1.
         */

        /**
         * @cfg {String} fill The fill color.
         */

        /**
         * @cfg {String} stroke The stroke color.
         */

        /**
         * @cfg {Number} stroke-width The width of the stroke.
         *
         * Note that this attribute needs to be quoted when used.  Like so:
         *
         *     "stroke-width": 12,
         */

        /**
         * @cfg {String} font Used with text type sprites. The full font description.
         * Uses the same syntax as the CSS font parameter
         */

        /**
         * @cfg {String} text The actual text to render in text sprites.
         */

        /**
         * @cfg {String} src Path to the image to show in image sprites.
         */

        /**
         * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups.
         * Only relevant when added to a {@link Ext.draw.Surface Surface}.
         */

        /**
         * @cfg {Boolean} draggable True to make the sprite draggable.
         */

        dirty: false,
        dirtyHidden: false,
        dirtyTransform: false,
        dirtyPath: true,
        dirtyFont: true,
        zIndexDirty: true,

        /**
         * @property {Boolean} isSprite
         * `true` in this class to identify an object as an instantiated Sprite, or subclass thereof.
         */
        isSprite: true,
        zIndex: 0,
        fontProperties: [
            'font',
            'font-size',
            'font-weight',
            'font-style',
            'font-family',
            'text-anchor',
            'text'
        ],
        pathProperties: [
            'x',
            'y',
            'd',
            'path',
            'height',
            'width',
            'radius',
            'r',
            'rx',
            'ry',
            'cx',
            'cy'
        ],
        constructor: function(config) {
            var me = this;
            config = Ext.merge({}, config || {});
            me.id = Ext.id(null, 'ext-sprite-');
            me.transformations = [];
            Ext.copyTo(this, config, 'surface,group,type,draggable');
            //attribute bucket
            me.bbox = {};
            me.attr = {
                zIndex: 0,
                translation: {
                    x: null,
                    y: null
                },
                rotation: {
                    degrees: null,
                    x: null,
                    y: null
                },
                scaling: {
                    x: null,
                    y: null,
                    cx: null,
                    cy: null
                }
            };
            //delete not bucket attributes
            delete config.surface;
            delete config.group;
            delete config.type;
            delete config.draggable;
            me.setAttributes(config);
            me.addEvents(
                /**
                 * @event
                 * Fires before the sprite is destroyed. Return false from an event handler to stop the destroy.
                 * @param {Ext.draw.Sprite} this
                 */
                'beforedestroy',
                /**
                 * @event
                 * Fires after the sprite is destroyed.
                 * @param {Ext.draw.Sprite} this
                 */
                'destroy',
                /**
                 * @event
                 * Fires after the sprite markup is rendered.
                 * @param {Ext.draw.Sprite} this
                 */
                'render',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#mousedown
                 */
                'mousedown',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#mouseup
                 */
                'mouseup',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#mouseover
                 */
                'mouseover',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#mouseout
                 */
                'mouseout',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#mousemove
                 */
                'mousemove',
                /**
                 * @event
                 * @inheritdoc Ext.dom.Element#click
                 */
                'click'
            );
            me.mixins.observable.constructor.apply(this, arguments);
        },

        /**
         * @property {Ext.dd.DragSource} dd
         * If this Sprite is configured {@link #draggable}, this property will contain
         * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
         *
         * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
         * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
         */

        initDraggable: function() {
            var me = this;
            //create element if it doesn't exist.
            if (!me.el) {
                me.surface.createSpriteElement(me);
            }
            me.dd = new Ext.draw.SpriteDD(me, Ext.isBoolean(me.draggable) ? null : me.draggable);
            me.on('beforedestroy', me.dd.destroy, me.dd);
        },

        /**
         * Change the attributes of the sprite.
         *
         * ## Translation
         *
         * For translate, the configuration object contains x and y attributes that indicate where to
         * translate the object. For example:
         *
         *     sprite.setAttributes({
         *       translate: {
         *        x: 10,
         *        y: 10
         *       }
         *     }, true);
         *
         *
         * ## Rotation
         *
         * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
         * and a `degrees` attribute that specifies the rotation in degrees. For example:
         *
         *     sprite.setAttributes({
         *       rotate: {
         *        degrees: 90
         *       }
         *     }, true);
         *
         * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
         *
         *     sprite.setAttributes({
         *       rotate: {
         *        x: 0,
         *        y: 0,
         *        degrees: 90
         *       }
         *     }, true);
         *
         * will create a rotation around the `(0, 0)` axis.
         *
         *
         * ## Scaling
         *
         * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
         *
         *     sprite.setAttributes({
         *       scale: {
         *        x: 10,
         *        y: 3
         *       }
         *     }, true);
         *
         * You can also specify the center of scaling by adding `cx` and `cy` as properties:
         *
         *     sprite.setAttributes({
         *       scale: {
         *        cx: 0,
         *        cy: 0,
         *        x: 10,
         *        y: 3
         *       }
         *     }, true);
         *
         * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
         *
         * @param {Object} attrs attributes to be changed on the sprite.
         * @param {Boolean} redraw Flag to immediately draw the change.
         * @return {Ext.draw.Sprite} this
         */
        setAttributes: function(attrs, redraw) {
            var me = this,
                fontProps = me.fontProperties,
                fontPropsLength = fontProps.length,
                pathProps = me.pathProperties,
                pathPropsLength = pathProps.length,
                hasSurface = !!me.surface,
                custom = hasSurface && me.surface.customAttributes || {},
                spriteAttrs = me.attr,
                dirtyBBox = false,
                attr, i, newTranslation, translation, newRotate, rotation, newScaling, scaling;

            attrs = Ext.apply({}, attrs);
            for (attr in custom) {
                if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
                    Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
                }
            }

            // Flag a change in hidden
            if (!!attrs.hidden !== !!spriteAttrs.hidden) {
                me.dirtyHidden = true;
            }

            // Flag path change
            for (i = 0; i < pathPropsLength; i++) {
                attr = pathProps[i];
                if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
                    me.dirtyPath = true;
                    dirtyBBox = true;
                    break;
                }
            }

            // Flag zIndex change
            if ('zIndex' in attrs) {
                me.zIndexDirty = true;
            }

            // Flag font/text change
            if ('text' in attrs) {
                me.dirtyFont = true;
                dirtyBBox = true;
            }

            for (i = 0; i < fontPropsLength; i++) {
                attr = fontProps[i];
                if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
                    me.dirtyFont = true;
                    dirtyBBox = true;
                    break;
                }
            }

            newTranslation = attrs.translation || attrs.translate;
            delete attrs.translate;
            delete attrs.translation;
            translation = spriteAttrs.translation;
            if (newTranslation) {
                if (('x' in newTranslation && newTranslation.x !== translation.x) ||
                    ('y' in newTranslation && newTranslation.y !== translation.y)) {
                    me.dirtyTransform = true;
                    translation.x = newTranslation.x;
                    translation.y = newTranslation.y;
                }
            }

            newRotate = attrs.rotation || attrs.rotate;
            rotation = spriteAttrs.rotation;
            delete attrs.rotate;
            delete attrs.rotation;
            if (newRotate) {
                if (('x' in newRotate && newRotate.x !== rotation.x) ||
                    ('y' in newRotate && newRotate.y !== rotation.y) ||
                    ('degrees' in newRotate && newRotate.degrees !== rotation.degrees)) {
                    me.dirtyTransform = true;
                    rotation.x = newRotate.x;
                    rotation.y = newRotate.y;
                    rotation.degrees = newRotate.degrees;
                }
            }

            newScaling = attrs.scaling || attrs.scale;
            scaling = spriteAttrs.scaling;
            delete attrs.scale;
            delete attrs.scaling;
            if (newScaling) {
                if (('x' in newScaling && newScaling.x !== scaling.x) ||
                    ('y' in newScaling && newScaling.y !== scaling.y) ||
                    ('cx' in newScaling && newScaling.cx !== scaling.cx) ||
                    ('cy' in newScaling && newScaling.cy !== scaling.cy)) {
                    me.dirtyTransform = true;
                    scaling.x = newScaling.x;
                    scaling.y = newScaling.y;
                    scaling.cx = newScaling.cx;
                    scaling.cy = newScaling.cy;
                }
            }

            // If the bbox is changed, then the bbox based transforms should be invalidated.
            if (!me.dirtyTransform && dirtyBBox) {
                if (spriteAttrs.scaling.x === null ||
                    spriteAttrs.scaling.y === null ||
                    spriteAttrs.rotation.y === null ||
                    spriteAttrs.rotation.y === null) {
                    me.dirtyTransform = true;
                }
            }

            Ext.apply(spriteAttrs, attrs);
            me.dirty = true;

            if (redraw === true && hasSurface) {
                me.redraw();
            }
            return this;
        },

        /**
         * Retrieves the bounding box of the sprite.
         * This will be returned as an object with x, y, width, and height properties.
         * @return {Object} bbox
         */
        getBBox: function() {
            return this.surface.getBBox(this);
        },

        setText: function(text) {
            return this.surface.setText(this, text);
        },

        /**
         * Hides the sprite.
         * @param {Boolean} redraw Flag to immediately draw the change.
         * @return {Ext.draw.Sprite} this
         */
        hide: function(redraw) {
            this.setAttributes({
                hidden: true
            }, redraw);
            return this;
        },

        /**
         * Shows the sprite.
         * @param {Boolean} redraw Flag to immediately draw the change.
         * @return {Ext.draw.Sprite} this
         */
        show: function(redraw) {
            this.setAttributes({
                hidden: false
            }, redraw);
            return this;
        },

        /**
         * Removes the sprite.
         * @return {Boolean} True if sprite was successfully removed.
         * False when there was no surface to remove it from.
         */
        remove: function() {
            if (this.surface) {
                this.surface.remove(this);
                return true;
            }
            return false;
        },

        onRemove: function() {
            this.surface.onRemove(this);
        },

        /**
         * Removes the sprite and clears all listeners.
         */
        destroy: function() {
            var me = this;
            if (me.fireEvent('beforedestroy', me) !== false) {
                me.remove();
                me.surface.onDestroy(me);
                me.clearListeners();
                me.fireEvent('destroy');
            }
        },

        /**
         * Redraws the sprite.
         * @return {Ext.draw.Sprite} this
         */
        redraw: function() {
            this.surface.renderItem(this);
            return this;
        },

        /**
         * Wrapper for setting style properties, also takes single object parameter of multiple styles.
         * @param {String/Object} property The style property to be set, or an object of multiple styles.
         * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
         * @return {Ext.draw.Sprite} this
         */
        setStyle: function() {
            this.el.setStyle.apply(this.el, arguments);
            return this;
        },

        /**
         * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
         * is severly limited in VML.
         * @param {String/String[]} className The CSS class to add, or an array of classes
         * @return {Ext.draw.Sprite} this
         */
        addCls: function(obj) {
            this.surface.addCls(this, obj);
            return this;
        },

        /**
         * Removes one or more CSS classes from the element.
         * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
         * is severly limited in VML.
         * @return {Ext.draw.Sprite} this
         */
        removeCls: function(obj) {
            this.surface.removeCls(this, obj);
            return this;
        }
    });