text: // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  
  /**
   * @fileoverview SvgGraphics sub class that uses SVG to draw the graphics.
   */
  
  goog.provide('goog.graphics.SvgGraphics');
  
  goog.require('goog.Timer');
  goog.require('goog.dom');
  goog.require('goog.events.EventHandler');
  goog.require('goog.events.EventType');
  goog.require('goog.graphics.AbstractGraphics');
  goog.require('goog.graphics.Font');
  goog.require('goog.graphics.LinearGradient');
  goog.require('goog.graphics.SolidFill');
  goog.require('goog.graphics.Stroke');
  goog.require('goog.graphics.SvgEllipseElement');
  goog.require('goog.graphics.SvgGroupElement');
  goog.require('goog.graphics.SvgImageElement');
  goog.require('goog.graphics.SvgPathElement');
  goog.require('goog.graphics.SvgRectElement');
  goog.require('goog.graphics.SvgTextElement');
  goog.require('goog.math.Size');
  goog.require('goog.style');
  goog.require('goog.userAgent');
  
  
  
  /**
   * A Graphics implementation for drawing using SVG.
   * @param {string|number} width The width in pixels.  Strings
   *     expressing percentages of parent with (e.g. '80%') are also accepted.
   * @param {string|number} height The height in pixels.  Strings
   *     expressing percentages of parent with (e.g. '80%') are also accepted.
   * @param {?number=} opt_coordWidth The coordinate width - if
   *     omitted or null, defaults to same as width.
   * @param {?number=} opt_coordHeight The coordinate height - if
   *     omitted or null, defaults to same as height.
   * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
   *     document we want to render in.
   * @constructor
   * @extends {goog.graphics.AbstractGraphics}
   */
  goog.graphics.SvgGraphics = function(width, height,
                                       opt_coordWidth, opt_coordHeight,
                                       opt_domHelper) {
    goog.graphics.AbstractGraphics.call(this, width, height,
                                        opt_coordWidth, opt_coordHeight,
                                        opt_domHelper);
  
    /**
     * Map from def key to id of def root element.
     * Defs are global "defines" of svg that are used to share common attributes,
     * for example gradients.
     * @type {Object}
     * @private
     */
    this.defs_ = {};
  
    /**
     * Whether to manually implement viewBox by using a coordinate transform.
     * As of 1/11/08 this is necessary for Safari 3 but not for the nightly
     * WebKit build. Apply to webkit versions < 526. 525 is the
     * last version used by Safari 3.1.
     * @type {boolean}
     * @private
     */
    this.useManualViewbox_ = goog.userAgent.WEBKIT &&
                             !goog.userAgent.isVersion(526);
  
    /**
     * Event handler.
     * @type {goog.events.EventHandler}
     * @private
     */
    this.handler_ = new goog.events.EventHandler(this);
  };
  goog.inherits(goog.graphics.SvgGraphics, goog.graphics.AbstractGraphics);
  
  
  /**
   * The SVG namespace URN
   * @private
   * @type {string}
   */
  goog.graphics.SvgGraphics.SVG_NS_ = 'http://www.w3.org/2000/svg';
  
  
  /**
   * The name prefix for def entries
   * @private
   * @type {string}
   */
  goog.graphics.SvgGraphics.DEF_ID_PREFIX_ = '_svgdef_';
  
  
  /**
   * The next available unique identifier for a def entry.
   * This is a static variable, so that when multiple graphics are used in one
   * document, the same def id can not be re-defined by another SvgGraphics.
   * @type {number}
   * @private
   */
  goog.graphics.SvgGraphics.nextDefId_ = 0;
  
  
  /**
   * Svg element for definitions for other elements, e.g. linear gradients.
   * @type {Element}
   * @private
   */
  goog.graphics.SvgGraphics.prototype.defsElement_;
  
  
  /**
   * Creates an SVG element. Used internally and by different SVG classes.
   * @param {string} tagName The type of element to create.
   * @param {Object=} opt_attributes Map of name-value pairs for attributes.
   * @return {Element} The created element.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.createSvgElement_ = function(tagName,
      opt_attributes) {
    var element = this.dom_.getDocument().createElementNS(
        goog.graphics.SvgGraphics.SVG_NS_, tagName);
  
    if (opt_attributes) {
      this.setElementAttributes(element, opt_attributes);
    }
  
    return element;
  };
  
  
  /**
   * Sets properties to an SVG element. Used internally and by different
   * SVG elements.
   * @param {Element} element The svg element.
   * @param {Object} attributes Map of name-value pairs for attributes.
   */
  goog.graphics.SvgGraphics.prototype.setElementAttributes = function(element,
      attributes) {
    for (var key in attributes) {
      element.setAttribute(key, attributes[key]);
    }
  };
  
  
  /**
   * Appends an element.
   *
   * @param {goog.graphics.Element} element The element wrapper.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.append_ = function(element, opt_group) {
    var parent = opt_group || this.canvasElement;
    parent.getElement().appendChild(element.getElement());
  };
  
  
  /**
   * Sets the fill of the given element.
   * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
   * @param {goog.graphics.Fill?} fill The fill object.
   */
  goog.graphics.SvgGraphics.prototype.setElementFill = function(element, fill) {
    var svgElement = element.getElement();
    if (fill instanceof goog.graphics.SolidFill) {
      svgElement.setAttribute('fill', fill.getColor());
      svgElement.setAttribute('fill-opacity', fill.getOpacity());
    } else if (fill instanceof goog.graphics.LinearGradient) {
      // create a def key which is just a concat of all the relevant fields
      var defKey = 'lg-' +
                   fill.getX1() + '-' + fill.getY1() + '-' +
                   fill.getX2() + '-' + fill.getY2() + '-' +
                   fill.getColor1() + '-' + fill.getColor2();
      // It seems that the SVG version accepts opacity where the VML does not
  
      var id = this.getDef_(defKey);
  
      if (!id) { // No def for this yet, create it
        // Create the gradient def entry (only linear gradient are supported)
        var gradient = this.createSvgElement_('linearGradient', {
          'x1': fill.getX1(),
          'y1': fill.getY1(),
          'x2': fill.getX2(),
          'y2': fill.getY2(),
          'gradientUnits': 'userSpaceOnUse'
        });
  
        var gstyle = 'stop-color:' + fill.getColor1();
        if (goog.isNumber(fill.getOpacity1())) {
          gstyle += ';stop-opacity:' + fill.getOpacity1();
        }
        var stop1 = this.createSvgElement_(
            'stop', {'offset': '0%', 'style': gstyle});
        gradient.appendChild(stop1);
  
        // LinearGradients don't have opacity in VML so implement that before
        // enabling the following code.
        // if (fill.getOpacity() != null) {
        //   gstyles += 'opacity:' + fill.getOpacity() + ';'
        // }
        gstyle = 'stop-color:' + fill.getColor2();
        if (goog.isNumber(fill.getOpacity2())) {
          gstyle += ';stop-opacity:' + fill.getOpacity2();
        }
        var stop2 = this.createSvgElement_(
            'stop', {'offset': '100%', 'style': gstyle});
        gradient.appendChild(stop2);
  
        // LinearGradients don't have opacity in VML so implement that before
        // enabling the following code.
        // if (fill.getOpacity() != null) {
        //   gstyles += 'opacity:' + fill.getOpacity() + ';'
        // }
  
        id = this.addDef_(defKey, gradient);
      }
  
      // Link element to linearGradient definition
      svgElement.setAttribute('fill', 'url(#' + id + ')');
    } else {
      svgElement.setAttribute('fill', 'none');
    }
  };
  
  
  /**
  * Sets the stroke of the given element.
   * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
   * @param {goog.graphics.Stroke?} stroke The stroke object.
   */
  goog.graphics.SvgGraphics.prototype.setElementStroke = function(element,
      stroke) {
    var svgElement = element.getElement();
    if (stroke) {
      svgElement.setAttribute('stroke', stroke.getColor());
  
      var width = stroke.getWidth();
      if (goog.isString(width) && width.indexOf('px') != -1) {
        svgElement.setAttribute('stroke-width',
            parseFloat(width) / this.getPixelScaleX());
      } else {
        svgElement.setAttribute('stroke-width', width);
      }
    } else {
      svgElement.setAttribute('stroke', 'none');
    }
  };
  
  
  /**
   * Set the transformation of an element.
   * @param {goog.graphics.Element} element The element wrapper.
   * @param {number} x The x coordinate of the translation transform.
   * @param {number} y The y coordinate of the translation transform.
   * @param {number} angle The angle of the rotation transform.
   * @param {number} centerX The horizontal center of the rotation transform.
   * @param {number} centerY The vertical center of the rotation transform.
   */
  goog.graphics.SvgGraphics.prototype.setElementTransform = function(element, x,
      y, angle, centerX, centerY) {
    element.getElement().setAttribute('transform', 'translate(' + x + ',' + y +
        ') rotate(' + angle + ' ' + centerX + ' ' + centerY + ')');
  };
  
  
  /**
   * Creates the DOM representation of the graphics area.
   */
  goog.graphics.SvgGraphics.prototype.createDom = function() {
    // Set up the standard attributes.
    var attributes = {
      'width': this.width,
      'height': this.height,
      'overflow': 'hidden'
    };
  
    var svgElement = this.createSvgElement_('svg', attributes);
  
    var groupElement = this.createSvgElement_('g');
  
    this.defsElement_ = this.createSvgElement_('defs');
    this.canvasElement = new goog.graphics.SvgGroupElement(groupElement, this);
  
    svgElement.appendChild(this.defsElement_);
    svgElement.appendChild(groupElement);
  
    // Use the svgElement as the root element.
    this.setElementInternal(svgElement);
  
    // Set up the coordinate system.
    this.setViewBox_();
  };
  
  
  /**
   * Changes the coordinate system position.
   * @param {number} left The coordinate system left bound.
   * @param {number} top The coordinate system top bound.
   */
  goog.graphics.SvgGraphics.prototype.setCoordOrigin = function(left, top) {
    this.coordLeft = left;
    this.coordTop = top;
  
    this.setViewBox_();
  };
  
  
  /**
   * Changes the coordinate size.
   * @param {number} coordWidth The coordinate width.
   * @param {number} coordHeight The coordinate height.
   */
  goog.graphics.SvgGraphics.prototype.setCoordSize = function(coordWidth,
      coordHeight) {
    goog.graphics.SvgGraphics.superClass_.setCoordSize.apply(
        this, arguments);
    this.setViewBox_();
  };
  
  
  /**
   * @return {string} The view box string.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.getViewBox_ = function() {
    return this.coordLeft + ' ' + this.coordTop + ' ' +
        (this.coordWidth ? this.coordWidth + ' ' + this.coordHeight : '');
  };
  
  
  /**
   * Sets up the view box.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.setViewBox_ = function() {
    if (this.coordWidth || this.coordLeft || this.coordTop) {
      this.getElement().setAttribute('preserveAspectRatio', 'none');
      if (this.useManualViewbox_) {
        this.updateManualViewBox_();
      } else {
        this.getElement().setAttribute('viewBox', this.getViewBox_());
      }
    }
  };
  
  
  /**
   * Updates the transform of the root element to fake a viewBox.  Should only
   * be called when useManualViewbox_ is set.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.updateManualViewBox_ = function() {
    if (!this.isInDocument() ||
        !(this.coordWidth || this.coordLeft || !this.coordTop)) {
      return;
    }
  
    var size = this.getPixelSize();
    if (size.width == 0) {
      // In Safari, invisible SVG is sometimes shown.  Explicitly hide it.
      this.getElement().style.visibility = 'hidden';
      return;
    }
  
    this.getElement().style.visibility = '';
  
    var offsetX = - this.coordLeft;
    var offsetY = - this.coordTop;
    var scaleX = size.width / this.coordWidth;
    var scaleY = size.height / this.coordHeight;
  
    this.canvasElement.getElement().setAttribute('transform',
        'scale(' + scaleX + ' ' + scaleY + ') ' +
        'translate(' + offsetX + ' ' + offsetY + ')');
  };
  
  
  /**
   * Change the size of the canvas.
   * @param {number} pixelWidth The width in pixels.
   * @param {number} pixelHeight The height in pixels.
   */
  goog.graphics.SvgGraphics.prototype.setSize = function(pixelWidth,
      pixelHeight) {
    goog.style.setSize(this.getElement(), pixelWidth, pixelHeight);
  };
  
  
  /** @inheritDoc */
  goog.graphics.SvgGraphics.prototype.getPixelSize = function() {
    if (!goog.userAgent.GECKO) {
      return this.isInDocument() ?
          goog.style.getSize(this.getElement()) :
          goog.base(this, 'getPixelSize');
    }
  
    // In Gecko, goog.style.getSize does not work for SVG elements.  We have to
    // compute the size manually if it is percentage based.
    var width = this.width;
    var height = this.height;
    var computeWidth = goog.isString(width) && width.indexOf('%') != -1;
    var computeHeight = goog.isString(height) && height.indexOf('%') != -1;
  
    if (!this.isInDocument() && (computeWidth || computeHeight)) {
      return null;
    }
  
    var parent;
    var parentSize;
  
    if (computeWidth) {
      parent = /** @type {Element} */ (this.getElement().parentNode);
      parentSize = goog.style.getSize(parent);
      width = parseFloat(/** @type {string} */ (width)) * parentSize.width / 100;
    }
  
    if (computeHeight) {
      parent = parent || /** @type {Element} */ (this.getElement().parentNode);
      parentSize = parentSize || goog.style.getSize(parent);
      height = parseFloat(/** @type {string} */ (height)) * parentSize.height /
          100;
    }
  
    return new goog.math.Size(/** @type {number} */ (width),
        /** @type {number} */ (height));
  };
  
  
  /**
   * Remove all drawing elements from the graphics.
   */
  goog.graphics.SvgGraphics.prototype.clear = function() {
    this.canvasElement.clear();
    goog.dom.removeChildren(this.defsElement_);
    this.defs_ = {};
  };
  
  
  /**
   * Draw an ellipse.
   *
   * @param {number} cx Center X coordinate.
   * @param {number} cy Center Y coordinate.
   * @param {number} rx Radius length for the x-axis.
   * @param {number} ry Radius length for the y-axis.
   * @param {goog.graphics.Stroke?} stroke Stroke object describing the
   *    stroke.
   * @param {goog.graphics.Fill?} fill Fill object describing the fill.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.EllipseElement} The newly created element.
   */
  goog.graphics.SvgGraphics.prototype.drawEllipse = function(
      cx, cy, rx, ry, stroke, fill, opt_group) {
    var element = this.createSvgElement_('ellipse',
        {'cx': cx, 'cy': cy, 'rx': rx, 'ry': ry});
    var wrapper = new goog.graphics.SvgEllipseElement(element, this, stroke,
        fill);
    this.append_(wrapper, opt_group);
    return wrapper;
  };
  
  
  /**
   * Draw a rectangle.
   *
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @param {number} width Width of rectangle.
   * @param {number} height Height of rectangle.
   * @param {goog.graphics.Stroke?} stroke Stroke object describing the
   *    stroke.
   * @param {goog.graphics.Fill?} fill Fill object describing the fill.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.RectElement} The newly created element.
   */
  goog.graphics.SvgGraphics.prototype.drawRect = function(x, y, width, height,
      stroke, fill, opt_group) {
    var element = this.createSvgElement_('rect',
        {'x': x, 'y': y, 'width': width, 'height': height});
    var wrapper = new goog.graphics.SvgRectElement(element, this, stroke, fill);
    this.append_(wrapper, opt_group);
    return wrapper;
  };
  
  
  /**
   * Draw an image.
   *
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @param {number} width Width of the image.
   * @param {number} height Height of the image.
   * @param {string} src The source fo the image.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.ImageElement} The newly created image wrapped in a
   *     rectangle element.
   */
  goog.graphics.SvgGraphics.prototype.drawImage = function(x, y, width, height,
      src, opt_group) {
    var element = this.createSvgElement_('image', {
      'x': x,
      'y': y,
      'width': width,
      'height': height,
      'image-rendering': 'optimizeQuality',
      'preserveAspectRatio': 'none'
    });
    element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src);
    var wrapper = new goog.graphics.SvgImageElement(element, this);
    this.append_(wrapper, opt_group);
    return wrapper;
  };
  
  
  /**
   * Draw a text string vertically centered on a given line.
   *
   * @param {string} text The text to draw.
   * @param {number} x1 X coordinate of start of line.
   * @param {number} y1 Y coordinate of start of line.
   * @param {number} x2 X coordinate of end of line.
   * @param {number} y2 Y coordinate of end of line.
   * @param {string} align Horizontal alignment: left (default), center, right.
   * @param {goog.graphics.Font} font Font describing the font properties.
   * @param {goog.graphics.Stroke?} stroke Stroke object describing the
   *    stroke.
   * @param {goog.graphics.Fill?} fill Fill object describing the fill.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.TextElement} The newly created element.
   */
  goog.graphics.SvgGraphics.prototype.drawTextOnLine = function(
      text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {
    var angle = Math.round(goog.math.angle(x1, y1, x2, y2));
    var dx = x2 - x1;
    var dy = y2 - y1;
    var lineLength = Math.round(Math.sqrt(dx * dx + dy * dy)); // Length of line
  
    // SVG baseline is on the glyph's base line. We estimate it as 85% of the
    // font height. This is just a rough estimate, but do not have a better way.
    var fontSize = font.size;
    var attributes = {'font-family': font.family, 'font-size': fontSize};
    var baseline = Math.round(fontSize * 0.85);
    var textY = Math.round(y1 - (fontSize / 2) + baseline);
    var textX = x1;
    if (align == 'center') {
      textX += Math.round(lineLength / 2);
      attributes['text-anchor'] = 'middle';
    } else if (align == 'right') {
      textX += lineLength;
      attributes['text-anchor'] = 'end';
    }
    attributes['x'] = textX;
    attributes['y'] = textY;
    if (font.bold) {
      attributes['font-weight'] = 'bold';
    }
    if (font.italic) {
      attributes['font-style'] = 'italic';
    }
    if (angle != 0) {
      attributes['transform'] = 'rotate(' + angle + ' ' + x1 + ' ' + y1 + ')';
    }
  
    var element = this.createSvgElement_('text', attributes);
    element.appendChild(this.dom_.getDocument().createTextNode(text));
  
    // Bypass a Firefox-Mac bug where text fill is ignored. If text has no stroke,
    // set a stroke, otherwise the text will not be visible.
    if (stroke == null && goog.userAgent.GECKO && goog.userAgent.MAC) {
      var color = 'black';
      // For solid fills, use the fill color
      if (fill instanceof goog.graphics.SolidFill) {
        color = fill.getColor();
      }
      stroke = new goog.graphics.Stroke(1, color);
    }
  
    var wrapper = new goog.graphics.SvgTextElement(element, this, stroke, fill);
    this.append_(wrapper, opt_group);
    return wrapper;
  };
  
  
  /**
   * Draw a path.
   *
   * @param {goog.graphics.Path} path The path object to draw.
   * @param {goog.graphics.Stroke?} stroke Stroke object describing the
   *    stroke.
   * @param {goog.graphics.Fill?} fill Fill object describing the fill.
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.PathElement} The newly created element.
   */
  goog.graphics.SvgGraphics.prototype.drawPath = function(
      path, stroke, fill, opt_group) {
  
    var element = this.createSvgElement_('path',
        {'d': goog.graphics.SvgGraphics.getSvgPath(path)});
    var wrapper = new goog.graphics.SvgPathElement(element, this, stroke, fill);
    this.append_(wrapper, opt_group);
    return wrapper;
  };
  
  
  /**
   * Returns a string representation of a logical path suitable for use in
   * an SVG element.
   *
   * @param {goog.graphics.Path} path The logical path.
   * @return {string} The SVG path representation.
   */
  goog.graphics.SvgGraphics.getSvgPath = function(path) {
    var list = [];
    path.forEachSegment(function(segment, args) {
      switch (segment) {
        case goog.graphics.Path.Segment.MOVETO:
          list.push('M');
          Array.prototype.push.apply(list, args);
          break;
        case goog.graphics.Path.Segment.LINETO:
          list.push('L');
          Array.prototype.push.apply(list, args);
          break;
        case goog.graphics.Path.Segment.CURVETO:
          list.push('C');
          Array.prototype.push.apply(list, args);
          break;
        case goog.graphics.Path.Segment.ARCTO:
          var extent = args[3];
          var toAngle = args[2] + extent;
          list.push('A', args[0], args[1],
              0, Math.abs(extent) > 180 ? 1 : 0, extent > 0 ? 1 : 0,
              args[4], args[5]);
          break;
        case goog.graphics.Path.Segment.CLOSE:
          list.push('Z');
          break;
      }
    });
    return list.join(' ');
  };
  
  
  /**
   * Create an empty group of drawing elements.
   *
   * @param {goog.graphics.SvgGroupElement=} opt_group The group wrapper element
   *     to append to. If not specified, appends to the main canvas.
   *
   * @return {goog.graphics.GroupElement} The newly created group.
   */
  goog.graphics.SvgGraphics.prototype.createGroup = function(opt_group) {
    var element = this.createSvgElement_('g');
    var parent = opt_group || this.canvasElement;
    parent.getElement().appendChild(element);
    return new goog.graphics.SvgGroupElement(element, this);
  };
  
  
  /**
   * Measure and return the width (in pixels) of a given text string.
   * Text measurement is needed to make sure a text can fit in the allocated area.
   * The way text length is measured is by writing it into a div that is after
   * the visible area, measure the div width, and immediatly erase the written
   * value.
   *
   * @param {string} text The text string to measure.
   * @param {goog.graphics.Font} font The font object describing the font style.
   */
  goog.graphics.SvgGraphics.prototype.getTextWidth = function(text, font) {
    // TODO(user) Implement
  };
  
  
  /**
   * Adds a defintion of an element to the global definitions.
   * @param {string} defKey This is a key that should be unique in a way that
   *     if two definitions are equal the should have the same key.
   * @param {Element} defElement DOM element to add as a definition. It must
   *     have an id attribute set.
   * @return {string} The assigned id of the defElement.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.addDef_ = function(defKey, defElement) {
    if (defKey in this.defs_) {
      return this.defs_[defKey];
    }
    var id = goog.graphics.SvgGraphics.DEF_ID_PREFIX_ +
        goog.graphics.SvgGraphics.nextDefId_++;
    defElement.setAttribute('id', id);
    this.defs_[defKey] = id;
  
    // Add the def defElement of the defs list.
    var defs = this.defsElement_;
    defs.appendChild(defElement);
    return id;
  };
  
  
  /**
   * Returns the id of a definition element.
   * @param {string} defKey This is a key that should be unique in a way that
   *     if two definitions are equal the should have the same key.
   * @return {string} The id of the found definition element or null if
   *     not found.
   * @private
   */
  goog.graphics.SvgGraphics.prototype.getDef_ = function(defKey) {
    return defKey in this.defs_ ? this.defs_[defKey] : null;
  };
  
  
  /** @inheritDoc */
  goog.graphics.SvgGraphics.prototype.enterDocument = function() {
    var oldPixelSize = this.getPixelSize();
    goog.graphics.SvgGraphics.superClass_.enterDocument.call(this);
  
    // Dispatch a resize if this is the first time the size value is accurate.
    if (!oldPixelSize) {
      this.dispatchEvent(goog.events.EventType.RESIZE);
    }
  
  
    // For percentage based heights, listen for changes to size.
    if (this.useManualViewbox_) {
      var width = this.width;
      var height = this.height;
  
      if (typeof width == 'string' && width.indexOf('%') != -1 &&
          typeof height == 'string' && height.indexOf('%') != -1) {
        // SVG elements don't behave well with respect to size events, so we
        // resort to polling.
        this.handler_.listen(goog.graphics.SvgGraphics.getResizeCheckTimer_(),
            goog.Timer.TICK, this.updateManualViewBox_);
      }
  
      this.updateManualViewBox_();
    }
  };
  
  
  /** @inheritDoc */
  goog.graphics.SvgGraphics.prototype.exitDocument = function() {
    goog.graphics.SvgGraphics.superClass_.exitDocument.call(this);
  
    // Stop polling.
    if (this.useManualViewbox_) {
      this.handler_.unlisten(goog.graphics.SvgGraphics.getResizeCheckTimer_(),
          goog.Timer.TICK, this.updateManualViewBox_);
    }
  };
  
  
  /**
   * Disposes of the component by removing event handlers, detacing DOM nodes from
   * the document body, and removing references to them.
   * @override
   * @protected
   */
  goog.graphics.SvgGraphics.prototype.disposeInternal = function() {
    delete this.defs_;
    delete this.defsElement_;
    delete this.canvasElement;
    goog.graphics.SvgGraphics.superClass_.disposeInternal.call(this);
  };
  
  
  /**
   * The centralized resize checking timer.
   * @type {goog.Timer|undefined}
   * @private
   */
  goog.graphics.SvgGraphics.resizeCheckTimer_;
  
  
  /**
   * @return {goog.Timer} The centralized timer object used for interval timing.
   * @private
   */
  goog.graphics.SvgGraphics.getResizeCheckTimer_ = function() {
    if (!goog.graphics.SvgGraphics.resizeCheckTimer_) {
      goog.graphics.SvgGraphics.resizeCheckTimer_ = new goog.Timer(400);
      goog.graphics.SvgGraphics.resizeCheckTimer_.start();
    }
  
    return /** @type {goog.Timer} */ (
        goog.graphics.SvgGraphics.resizeCheckTimer_);
  };
  
  
  /** @inheritDoc */
  goog.graphics.SvgGraphics.prototype.isDomClonable = function() {
    return true;
  };
  