    /**
     * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
     * A Surface contains methods to render sprites, get bounding boxes of sprites, add
     * sprites to the canvas, initialize other graphic components, etc. One of the most used
     * methods for this class is the `add` method, to add Sprites to the surface.
     *
     * Most of the Surface methods are abstract and they have a concrete implementation
     * in VML or SVG engines.
     *
     * A Surface instance can be accessed as a property of a draw component. For example:
     *
     *     drawComponent.surface.add({
     *         type: 'circle',
     *         fill: '#ffc',
     *         radius: 100,
     *         x: 100,
     *         y: 100
     *     });
     *
     * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
     * class documentation.
     *
     * # Listeners
     *
     * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
     *
     * - mousedown
     * - mouseup
     * - mouseover
     * - mouseout
     * - mousemove
     * - mouseenter
     * - mouseleave
     * - click
     * - dblclick
     *
     * For example:
     *
     *     drawComponent.surface.on({
     *        'mousemove': function() {
     *             console.log('moving the mouse over the surface');
     *         }
     *     });
     *
     * # Example
     *
     *     var drawComponent = Ext.create('Ext.draw.Component', {
     *         width: 800,
     *         height: 600,
     *         renderTo: document.body
     *     }), surface = drawComponent.surface;
     *
     *     surface.add([{
     *         type: 'circle',
     *         radius: 10,
     *         fill: '#f00',
     *         x: 10,
     *         y: 10,
     *         group: 'circles'
     *     }, {
     *         type: 'circle',
     *         radius: 10,
     *         fill: '#0f0',
     *         x: 50,
     *         y: 50,
     *         group: 'circles'
     *     }, {
     *         type: 'circle',
     *         radius: 10,
     *         fill: '#00f',
     *         x: 100,
     *         y: 100,
     *         group: 'circles'
     *     }, {
     *         type: 'rect',
     *         width: 20,
     *         height: 20,
     *         fill: '#f00',
     *         x: 10,
     *         y: 10,
     *         group: 'rectangles'
     *     }, {
     *         type: 'rect',
     *         width: 20,
     *         height: 20,
     *         fill: '#0f0',
     *         x: 50,
     *         y: 50,
     *         group: 'rectangles'
     *     }, {
     *         type: 'rect',
     *         width: 20,
     *         height: 20,
     *         fill: '#00f',
     *         x: 100,
     *         y: 100,
     *         group: 'rectangles'
     *     }]);
     *
     *     // Get references to my groups
     *     circles = surface.getGroup('circles');
     *     rectangles = surface.getGroup('rectangles');
     *
     *     // Animate the circles down
     *     circles.animate({
     *         duration: 1000,
     *         to: {
     *             translate: {
     *                 y: 200
     *             }
     *         }
     *     });
     *
     *     // Animate the rectangles across
     *     rectangles.animate({
     *         duration: 1000,
     *         to: {
     *             translate: {
     *                 x: 200
     *             }
     *         }
     *     });
     */
    Ext.define('Ext.draw.Surface', {

        /* Begin Definitions */

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

        requires: ['Ext.draw.CompositeSprite'],
        uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml', 'Ext.draw.engine.SvgExporter', 'Ext.draw.engine.ImageExporter'],

        separatorRe: /[, ]+/,

        statics: {
            /**
             * Creates and returns a new concrete Surface instance appropriate for the current environment.
             * @param {Object} config Initial configuration for the Surface instance
             * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
             * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
             * @return {Object} The created Surface or false.
             * @static
             */
            create: function(config, enginePriority) {
                enginePriority = enginePriority || ['Svg', 'Vml'];

                var i = 0,
                    len = enginePriority.length,
                    surfaceClass;

                for (; i < len; i++) {
                    if (Ext.supports[enginePriority[i]] !== false) {
                        return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
                    }
                }
                return false;
            },

            save: function(config, surface){
                var exportTypes = {
                        /*
                        example adds
                        'image/png': 'Image',
                        'image/jpeg': 'Image',
                        */
                        'image/png': 'Image',
                        'image/jpeg': 'Image',
                        'image/svg+xml': 'Svg'
                    },
                    prefix = exportTypes[config.type] || 'Svg';

                return (Ext.draw.engine[prefix + 'Exporter']).self.generate(config, surface);

            }
        },

        /* End Definitions */

        // @private
        availableAttrs: {
            blur: 0,
            "clip-rect": "0 0 1e9 1e9",
            cursor: "default",
            cx: 0,
            cy: 0,
            'dominant-baseline': 'auto',
            fill: "none",
            "fill-opacity": 1,
            font: '10px "Arial"',
            "font-family": '"Arial"',
            "font-size": "10",
            "font-style": "normal",
            "font-weight": 400,
            gradient: "",
            height: 0,
            hidden: false,
            href: "http://sencha.com/",
            opacity: 1,
            path: "M0,0",
            radius: 0,
            rx: 0,
            ry: 0,
            scale: "1 1",
            src: "",
            stroke: "none",
            "stroke-dasharray": "",
            "stroke-linecap": "butt",
            "stroke-linejoin": "butt",
            "stroke-miterlimit": 0,
            "stroke-opacity": 1,
            "stroke-width": 1,
            target: "_blank",
            text: "",
            "text-anchor": "middle",
            title: "Ext Draw",
            width: 0,
            x: 0,
            y: 0,
            zIndex: 0
        },

        /**
         * @cfg {Number} height
         * The height of this component in pixels (defaults to auto).
         */
        /**
         * @cfg {Number} width
         * The width of this component in pixels (defaults to auto).
         */

        container: undefined,
        height: 352,
        width: 512,
        x: 0,
        y: 0,

        /**
         * @private Flag indicating that the surface implementation requires sprites to be maintained
         * in order of their zIndex. Impls that don't require this can set it to false.
         */
        orderSpritesByZIndex: true,

        /**
         * Creates new Surface.
         * @param {Object} config (optional) Config object.
         */
        constructor: function(config) {
            var me = this;
            config = config || {};
            Ext.apply(me, config);

            me.domRef = Ext.getDoc().dom;

            me.customAttributes = {};

            me.addEvents(
                'mousedown',
                'mouseup',
                'mouseover',
                'mouseout',
                'mousemove',
                'mouseenter',
                'mouseleave',
                'click',
                'dblclick'
            );

            me.mixins.observable.constructor.call(me);

            me.getId();
            me.initGradients();
            me.initItems();
            if (me.renderTo) {
                me.render(me.renderTo);
                delete me.renderTo;
            }
            me.initBackground(config.background);
        },

        // @private called to initialize components in the surface
        // this is dependent on the underlying implementation.
        initSurface: Ext.emptyFn,

        // @private called to setup the surface to render an item
        //this is dependent on the underlying implementation.
        renderItem: Ext.emptyFn,

        // @private
        renderItems: Ext.emptyFn,

        // @private
        setViewBox: function (x, y, width, height) {
            if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
                this.viewBox = {x: x, y: y, width: width, height: height};
                this.applyViewBox();
            }
        },

        /**
         * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
         *
         * For example:
         *
         *     drawComponent.surface.addCls(sprite, 'x-visible');
         *
         * @param {Object} sprite The sprite to add the class to.
         * @param {String/String[]} className The CSS class to add, or an array of classes
         * @method
         */
        addCls: Ext.emptyFn,

        /**
         * Removes one or more CSS classes from the element.
         *
         * For example:
         *
         *     drawComponent.surface.removeCls(sprite, 'x-visible');
         *
         * @param {Object} sprite The sprite to remove the class from.
         * @param {String/String[]} className The CSS class to remove, or an array of classes
         * @method
         */
        removeCls: Ext.emptyFn,

        /**
         * Sets CSS style attributes to an element.
         *
         * For example:
         *
         *     drawComponent.surface.setStyle(sprite, {
         *         'cursor': 'pointer'
         *     });
         *
         * @param {Object} sprite The sprite to add, or an array of classes to
         * @param {Object} styles An Object with CSS styles.
         * @method
         */
        setStyle: Ext.emptyFn,

        // @private
        initGradients: function() {
            if (this.hasOwnProperty('gradients')) {
                var gradients = this.gradients,
                    gLen      = gradients.length,
                    fn        = this.addGradient,
                    g;

                if (gradients) {
                    for (g = 0; g < gLen; g++) {
                        if (fn.call(this, gradients[g], g, gLen) === false) {
                            break;
                        }
                    }
                }
            }
        },

        // @private
        initItems: function() {
            var items = this.items;
            this.items = new Ext.draw.CompositeSprite();
            this.items.autoDestroy = true;
            this.groups = new Ext.draw.CompositeSprite();
            if (items) {
                this.add(items);
            }
        },

        // @private
        initBackground: function(config) {
            var me = this,
                width = me.width,
                height = me.height,
                gradientId, gradient, backgroundSprite;
            if (Ext.isString(config)) {
                config = {
                    fill : config
                };
            }
            if (config) {
                if (config.gradient) {
                    gradient = config.gradient;
                    gradientId = gradient.id;
                    me.addGradient(gradient);
                    me.background = me.add({
                        type: 'rect',
                        x: 0,
                        y: 0,
                        width: width,
                        height: height,
                        fill: 'url(#' + gradientId + ')',
                        zIndex: -1
                    });
                } else if (config.fill) {
                    me.background = me.add({
                        type: 'rect',
                        x: 0,
                        y: 0,
                        width: width,
                        height: height,
                        fill: config.fill,
                        zIndex: -1
                    });
                } else if (config.image) {
                    me.background = me.add({
                        type: 'image',
                        x: 0,
                        y: 0,
                        width: width,
                        height: height,
                        src: config.image,
                        zIndex: -1
                    });
                }
                // prevent me.background to jeopardize me.items.getBBox
                me.background.bboxExcluded = true;
            }
        },

        /**
         * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
         *
         * For example:
         *
         *     drawComponent.surface.setSize(500, 500);
         *
         * This method is generally called when also setting the size of the draw Component.
         *
         * @param {Number} w The new width of the canvas.
         * @param {Number} h The new height of the canvas.
         */
        setSize: function(w, h) {
            this.applyViewBox();
        },

        // @private
        scrubAttrs: function(sprite) {
            var i,
                attrs = {},
                exclude = {},
                sattr = sprite.attr;
            for (i in sattr) {
                // Narrow down attributes to the main set
                if (this.translateAttrs.hasOwnProperty(i)) {
                    // Translated attr
                    attrs[this.translateAttrs[i]] = sattr[i];
                    exclude[this.translateAttrs[i]] = true;
                }
                else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
                    // Passtrhough attr
                    attrs[i] = sattr[i];
                }
            }
            return attrs;
        },

        // @private
        onClick: function(e) {
            this.processEvent('click', e);
        },

        // @private
        onDblClick: function(e) {
            this.processEvent('dblclick', e);
        },

        // @private
        onMouseUp: function(e) {
            this.processEvent('mouseup', e);
        },

        // @private
        onMouseDown: function(e) {
            this.processEvent('mousedown', e);
        },

        // @private
        onMouseOver: function(e) {
            this.processEvent('mouseover', e);
        },

        // @private
        onMouseOut: function(e) {
            this.processEvent('mouseout', e);
        },

        // @private
        onMouseMove: function(e) {
            this.fireEvent('mousemove', e);
        },

        // @private
        onMouseEnter: Ext.emptyFn,

        // @private
        onMouseLeave: Ext.emptyFn,

        /**
         * Adds a gradient definition to the Surface. Note that in some surface engines, adding
         * a gradient via this method will not take effect if the surface has already been rendered.
         * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
         * than calling this method, especially if the surface is rendered immediately (e.g. due to
         * 'renderTo' in its config). For more information on how to create gradients in the Chart
         * configuration object please refer to {@link Ext.chart.Chart}.
         *
         * The gradient object to be passed into this method is composed by:
         *
         * - **id** - string - The unique name of the gradient.
         * - **angle** - number, optional - The angle of the gradient in degrees.
         * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
         *
         * For example:
         *
         *    drawComponent.surface.addGradient({
         *        id: 'gradientId',
         *        angle: 45,
         *        stops: {
         *            0: {
         *                color: '#555'
         *            },
         *            100: {
         *                color: '#ddd'
         *            }
         *        }
         *    });
         *
         * @param {Object} gradient A gradient config.
         * @method
         */
        addGradient: Ext.emptyFn,

        /**
         * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
         * passed into this method.
         *
         * For example:
         *
         *     drawComponent.surface.add({
         *         type: 'circle',
         *         fill: '#ffc',
         *         radius: 100,
         *         x: 100,
         *         y: 100
         *     });
         *
         * @param {Ext.draw.Sprite[]/Ext.draw.Sprite...} args One or more Sprite objects of configs.
         * @return {Ext.draw.Sprite[]/Ext.draw.Sprite} The sprites added.
         */
        add: function() {
            var args = Array.prototype.slice.call(arguments),
                sprite,
                index,
                hasMultipleArgs = args.length > 1,
                items,
                results,
                i,
                ln,
                item;

            if (hasMultipleArgs || Ext.isArray(args[0])) {
                items = hasMultipleArgs ? args : args[0];
                results = [];

                for (i = 0, ln = items.length; i < ln; i++) {
                    item = items[i];
                    item = this.add(item);
                    results.push(item);
                }

                return results;
            }
            sprite = this.prepareItems(args[0], true)[0];
            this.insertByZIndex(sprite);
            this.onAdd(sprite);
            return sprite;
        },

        /**
         * @private
         * Inserts a given sprite into the correct position in the items collection, according to
         * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
         * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
         * the sprites in the correct order for proper z-index stacking.
         * @param {Ext.draw.Sprite} sprite
         * @return {Number} the sprite's new index in the list
         */
        insertByZIndex: function(sprite) {
            var me = this,
                sprites = me.items.items,
                len = sprites.length,
                ceil = Math.ceil,
                zIndex = sprite.attr.zIndex,
                idx = len,
                high = idx - 1,
                low = 0,
                otherZIndex;

            if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
                // Find the target index via a binary search for speed
                while (low <= high) {
                    idx = ceil((low + high) / 2);
                    otherZIndex = sprites[idx].attr.zIndex;
                    if (otherZIndex > zIndex) {
                        high = idx - 1;
                    }
                    else if (otherZIndex < zIndex) {
                        low = idx + 1;
                    }
                    else {
                        break;
                    }
                }
                // Step forward to the end of a sequence of the same or lower z-index
                while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
                    idx++;
                }
            }

            me.items.insert(idx, sprite);
            return idx;
        },

        onAdd: function(sprite) {
            var group = sprite.group,
                draggable = sprite.draggable,
                groups, ln, i;
            if (group) {
                groups = [].concat(group);
                ln = groups.length;
                for (i = 0; i < ln; i++) {
                    group = groups[i];
                    this.getGroup(group).add(sprite);
                }
                delete sprite.group;
            }
            if (draggable) {
                sprite.initDraggable();
            }
        },

        /**
         * Removes a given sprite from the surface, optionally destroying the sprite in the process.
         * You can also call the sprite own `remove` method.
         *
         * For example:
         *
         *     drawComponent.surface.remove(sprite);
         *     //or...
         *     sprite.remove();
         *
         * @param {Ext.draw.Sprite} sprite
         * @param {Boolean} destroySprite
         */
        remove: function(sprite, destroySprite) {
            if (sprite) {
                this.items.remove(sprite);

                var groups = [].concat(this.groups.items),
                    gLen   = groups.length,
                    g;

                for (g = 0; g < gLen; g++) {
                    groups[g].remove(sprite);
                }

                sprite.onRemove();
                if (destroySprite === true) {
                    sprite.destroy();
                }
            }
        },

        /**
         * Removes all sprites from the surface, optionally destroying the sprites in the process.
         *
         * For example:
         *
         *     drawComponent.surface.removeAll();
         *
         * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
         */
        removeAll: function(destroySprites) {
            var items = this.items.items,
                ln = items.length,
                i;
            for (i = ln - 1; i > -1; i--) {
                this.remove(items[i], destroySprites);
            }
        },

        onRemove: Ext.emptyFn,

        onDestroy: Ext.emptyFn,

        /**
         * @private Using the current viewBox property and the surface's width and height, calculate the
         * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
         */
        applyViewBox: function() {
            var me = this,
                viewBox = me.viewBox,
                width = me.width || 1, // Avoid problems in division
                height = me.height || 1,
                viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
                relativeHeight, relativeWidth, size;

            if (viewBox && (width || height)) {
                viewBoxX = viewBox.x;
                viewBoxY = viewBox.y;
                viewBoxWidth = viewBox.width;
                viewBoxHeight = viewBox.height;
                relativeHeight = height / viewBoxHeight;
                relativeWidth = width / viewBoxWidth;
                size = Math.min(relativeWidth, relativeHeight);

                if (viewBoxWidth * size < width) {
                    viewBoxX -= (width - viewBoxWidth * size) / 2 / size;
                }
                if (viewBoxHeight * size < height) {
                    viewBoxY -= (height - viewBoxHeight * size) / 2 / size;
                }

                me.viewBoxShift = {
                    dx: -viewBoxX,
                    dy: -viewBoxY,
                    scale: size
                };

                if (me.background) {
                    me.background.setAttributes(Ext.apply({}, {
                        x: viewBoxX,
                        y: viewBoxY,
                        width: width / size,
                        height: height / size
                    }, { hidden: false }), true);
                }
            } else {
                if (me.background && width && height) {
                    me.background.setAttributes(Ext.apply({x: 0, y: 0, width: width, height: height}, { hidden: false }), true);
                }
            }
        },

        getBBox: function (sprite, isWithoutTransform) {
            var realPath = this["getPath" + sprite.type](sprite);
            if (isWithoutTransform) {
                sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
                return sprite.bbox.plain;
            }
            if (sprite.dirtyTransform) {
                this.applyTransformations(sprite, true);
            }
            sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
            return sprite.bbox.transform;
        },

        transformToViewBox: function (x, y) {
            if (this.viewBoxShift) {
                var me = this, shift = me.viewBoxShift;
                return [x / shift.scale - shift.dx, y / shift.scale - shift.dy];
            } else {
                return [x, y];
            }
        },

        // @private
        applyTransformations: function(sprite, onlyMatrix) {
            if (sprite.type == 'text') {
                // TODO: getTextBBox function always take matrix into account no matter whether `isWithoutTransform` is true. Fix that.
                sprite.bbox.transform = 0;
                this.transform(sprite, false);
            }

            sprite.dirtyTransform = false;

            var me = this,
                attr = sprite.attr;

            if (attr.translation.x != null || attr.translation.y != null) {
                me.translate(sprite);
            }
            if (attr.scaling.x != null || attr.scaling.y != null) {
                me.scale(sprite);
            }
            if (attr.rotation.degrees != null) {
                me.rotate(sprite);
            }

            sprite.bbox.transform = 0;
            this.transform(sprite, onlyMatrix);
            sprite.transformations = [];
        },

        // @private
        rotate: function (sprite) {
            var bbox,
                deg = sprite.attr.rotation.degrees,
                centerX = sprite.attr.rotation.x,
                centerY = sprite.attr.rotation.y;
            if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
                bbox = this.getBBox(sprite, true);
                centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
                centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
            }
            sprite.transformations.push({
                type: "rotate",
                degrees: deg,
                x: centerX,
                y: centerY
            });
        },

        // @private
        translate: function(sprite) {
            var x = sprite.attr.translation.x || 0,
                y = sprite.attr.translation.y || 0;
            sprite.transformations.push({
                type: "translate",
                x: x,
                y: y
            });
        },

        // @private
        scale: function(sprite) {
            var bbox,
                x = sprite.attr.scaling.x || 1,
                y = sprite.attr.scaling.y || 1,
                centerX = sprite.attr.scaling.centerX,
                centerY = sprite.attr.scaling.centerY;

            if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
                bbox = this.getBBox(sprite, true);
                centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
                centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
            }
            sprite.transformations.push({
                type: "scale",
                x: x,
                y: y,
                centerX: centerX,
                centerY: centerY
            });
        },

        // @private
        rectPath: function (x, y, w, h, r) {
            if (r) {
                return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
            }
            return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
        },

        // @private
        ellipsePath: function (x, y, rx, ry) {
            if (ry == null) {
                ry = rx;
            }
            return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
        },

        // @private
        getPathpath: function (el) {
            return el.attr.path;
        },

        // @private
        getPathcircle: function (el) {
            var a = el.attr;
            return this.ellipsePath(a.x, a.y, a.radius, a.radius);
        },

        // @private
        getPathellipse: function (el) {
            var a = el.attr;
            return this.ellipsePath(a.x, a.y,
                                    a.radiusX || (a.width / 2) || 0,
                                    a.radiusY || (a.height / 2) || 0);
        },

        // @private
        getPathrect: function (el) {
            var a = el.attr;
            return this.rectPath(a.x || 0, a.y || 0, a.width || 0, a.height || 0, a.r || 0);
        },

        // @private
        getPathimage: function (el) {
            var a = el.attr;
            return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
        },

        // @private
        getPathtext: function (el) {
            var bbox = this.getBBoxText(el);
            return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
        },

        createGroup: function(id) {
            var group = this.groups.get(id);
            if (!group) {
                group = new Ext.draw.CompositeSprite({
                    surface: this
                });
                group.id = id || Ext.id(null, 'ext-surface-group-');
                this.groups.add(group);
            }
            return group;
        },

        /**
         * Returns a new group or an existent group associated with the current surface.
         * The group returned is a {@link Ext.draw.CompositeSprite} group.
         *
         * For example:
         *
         *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
         *
         * @param {String} id The unique identifier of the group.
         * @return {Object} The {@link Ext.draw.CompositeSprite}.
         */
        getGroup: function(id) {
            var group;
            if (typeof id == "string") {
                group = this.groups.get(id);
                if (!group) {
                    group = this.createGroup(id);
                }
            } else {
                group = id;
            }
            return group;
        },

        // @private
        prepareItems: function(items, applyDefaults) {
            items = [].concat(items);
            // Make sure defaults are applied and item is initialized
            var item, i, ln;
            for (i = 0, ln = items.length; i < ln; i++) {
                item = items[i];
                if (!(item instanceof Ext.draw.Sprite)) {
                    // Temporary, just take in configs...
                    item.surface = this;
                    items[i] = this.createItem(item);
                } else {
                    item.surface = this;
                }
            }
            return items;
        },

        /**
         * Changes the text in the sprite element. The sprite must be a `text` sprite.
         * This method can also be called from {@link Ext.draw.Sprite}.
         *
         * For example:
         *
         *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
         *
         * @param {Object} sprite The Sprite to change the text.
         * @param {String} text The new text to be set.
         * @method
         */
        setText: Ext.emptyFn,

        // @private Creates an item and appends it to the surface. Called
        // as an internal method when calling `add`.
        createItem: Ext.emptyFn,

        /**
         * Retrieves the id of this component.
         * Will autogenerate an id if one has not already been set.
         */
        getId: function() {
            return this.id || (this.id = Ext.id(null, 'ext-surface-'));
        },

        /**
         * Destroys the surface. This is done by removing all components from it and
         * also removing its reference to a DOM element.
         *
         * For example:
         *
         *      drawComponent.surface.destroy();
         */
        destroy: function() {
            var me = this;
            delete me.domRef;
            if (me.background) {
                me.background.destroy();
            }
            me.removeAll(true);
            Ext.destroy(me.groups.items);
        }
    });