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 Objects representing shapes drawn on a canvas.
   */
  
  goog.provide('goog.graphics.CanvasEllipseElement');
  goog.provide('goog.graphics.CanvasGroupElement');
  goog.provide('goog.graphics.CanvasImageElement');
  goog.provide('goog.graphics.CanvasPathElement');
  goog.provide('goog.graphics.CanvasRectElement');
  goog.provide('goog.graphics.CanvasTextElement');
  
  
  goog.forwardDeclare('goog.graphics.CanvasGraphics');
  goog.require('goog.array');
  goog.require('goog.dom');
  goog.require('goog.dom.TagName');
  goog.require('goog.dom.safe');
  goog.require('goog.graphics.EllipseElement');
  goog.require('goog.graphics.Font');
  goog.require('goog.graphics.GroupElement');
  goog.require('goog.graphics.ImageElement');
  goog.require('goog.graphics.Path');
  goog.require('goog.graphics.PathElement');
  goog.require('goog.graphics.RectElement');
  goog.require('goog.graphics.TextElement');
  goog.require('goog.html.SafeHtml');
  goog.require('goog.html.uncheckedconversions');
  goog.require('goog.math');
  goog.require('goog.string');
  goog.require('goog.string.Const');
  
  
  
  /**
   * Object representing a group of objects in a canvas.
   * This is an implementation of the goog.graphics.GroupElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
   *     this element.
   * @constructor
   * @extends {goog.graphics.GroupElement}
   * @deprecated goog.graphics is deprecated. It existed to abstract over browser
   *     differences before the canvas tag was widely supported.  See
   *     http://en.wikipedia.org/wiki/Canvas_element for details.
   * @final
   */
  goog.graphics.CanvasGroupElement = function(graphics) {
    goog.graphics.GroupElement.call(this, null, graphics);
  
  
    /**
     * Children contained by this group.
     * @type {Array<goog.graphics.Element>}
     * @private
     */
    this.children_ = [];
  };
  goog.inherits(goog.graphics.CanvasGroupElement, goog.graphics.GroupElement);
  
  
  /**
   * Remove all drawing elements from the group.
   * @override
   */
  goog.graphics.CanvasGroupElement.prototype.clear = function() {
    if (this.children_.length) {
      this.children_.length = 0;
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Set the size of the group element.
   * @param {number|string} width The width of the group element.
   * @param {number|string} height The height of the group element.
   * @override
   */
  goog.graphics.CanvasGroupElement.prototype.setSize = function(width, height) {
    // Do nothing.
  };
  
  
  /**
   * Append a child to the group.  Does not draw it
   * @param {goog.graphics.Element} element The child to append.
   */
  goog.graphics.CanvasGroupElement.prototype.appendChild = function(element) {
    this.children_.push(element);
  };
  
  
  /**
   * Draw the group.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   */
  goog.graphics.CanvasGroupElement.prototype.draw = function(ctx) {
    for (var i = 0, len = this.children_.length; i < len; i++) {
      this.getGraphics().drawElement(this.children_[i]);
    }
  };
  
  
  /**
   * Removes an element from the group.
   * @param {!goog.graphics.Element} elem the element to remove.
   */
  goog.graphics.CanvasGroupElement.prototype.removeElement = function(elem) {
    goog.array.removeIf(this.children_, function(child) {
      // If the child has children (and thus is a group element)
      // call removeElement on that group
      if (child.children_) {
        child.removeElement(elem);
        return false;
      } else {
        return child === elem;
      }
    });
  };
  
  
  
  /**
   * Thin wrapper for canvas ellipse elements.
   * This is an implementation of the goog.graphics.EllipseElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {Element} element The DOM element to wrap.
   * @param {goog.graphics.CanvasGraphics} graphics  The graphics creating
   *     this element.
   * @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 The stroke to use for this element.
   * @param {goog.graphics.Fill} fill The fill to use for this element.
   * @constructor
   * @extends {goog.graphics.EllipseElement}
   * @final
   */
  goog.graphics.CanvasEllipseElement = function(
      element, graphics, cx, cy, rx, ry, stroke, fill) {
    goog.graphics.EllipseElement.call(this, element, graphics, stroke, fill);
  
    /**
     * X coordinate of the ellipse center.
     * @type {number}
     * @private
     */
    this.cx_ = cx;
  
  
    /**
     * Y coordinate of the ellipse center.
     * @type {number}
     * @private
     */
    this.cy_ = cy;
  
  
    /**
     * Radius length for the x-axis.
     * @type {number}
     * @private
     */
    this.rx_ = rx;
  
  
    /**
     * Radius length for the y-axis.
     * @type {number}
     * @private
     */
    this.ry_ = ry;
  
  
    /**
     * Internal path approximating an ellipse.
     * @type {goog.graphics.Path}
     * @private
     */
    this.path_ = new goog.graphics.Path();
    this.setUpPath_();
  
    /**
     * Internal path element that actually does the drawing.
     * @type {goog.graphics.CanvasPathElement}
     * @private
     */
    this.pathElement_ = new goog.graphics.CanvasPathElement(
        null, graphics, this.path_, stroke, fill);
  };
  goog.inherits(goog.graphics.CanvasEllipseElement, goog.graphics.EllipseElement);
  
  
  /**
   * Sets up the path.
   * @private
   */
  goog.graphics.CanvasEllipseElement.prototype.setUpPath_ = function() {
    this.path_.clear();
    this.path_.moveTo(
        this.cx_ + goog.math.angleDx(0, this.rx_),
        this.cy_ + goog.math.angleDy(0, this.ry_));
    this.path_.arcTo(this.rx_, this.ry_, 0, 360);
    this.path_.close();
  };
  
  
  /**
   * Update the center point of the ellipse.
   * @param {number} cx Center X coordinate.
   * @param {number} cy Center Y coordinate.
   * @override
   */
  goog.graphics.CanvasEllipseElement.prototype.setCenter = function(cx, cy) {
    this.cx_ = cx;
    this.cy_ = cy;
    this.setUpPath_();
    this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));
  };
  
  
  /**
   * Update the radius of the ellipse.
   * @param {number} rx Center X coordinate.
   * @param {number} ry Center Y coordinate.
   * @override
   */
  goog.graphics.CanvasEllipseElement.prototype.setRadius = function(rx, ry) {
    this.rx_ = rx;
    this.ry_ = ry;
    this.setUpPath_();
    this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));
  };
  
  
  /**
   * Draw the ellipse.  Should be treated as package scope.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   */
  goog.graphics.CanvasEllipseElement.prototype.draw = function(ctx) {
    this.pathElement_.draw(ctx);
  };
  
  
  
  /**
   * Thin wrapper for canvas rectangle elements.
   * This is an implementation of the goog.graphics.RectElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {Element} element The DOM element to wrap.
   * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
   *     this element.
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @param {number} w Width of rectangle.
   * @param {number} h Height of rectangle.
   * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
   * @param {goog.graphics.Fill} fill The fill to use for this element.
   * @constructor
   * @extends {goog.graphics.RectElement}
   * @final
   */
  goog.graphics.CanvasRectElement = function(
      element, graphics, x, y, w, h, stroke, fill) {
    goog.graphics.RectElement.call(this, element, graphics, stroke, fill);
  
    /**
     * X coordinate of the top left corner.
     * @type {number}
     * @private
     */
    this.x_ = x;
  
  
    /**
     * Y coordinate of the top left corner.
     * @type {number}
     * @private
     */
    this.y_ = y;
  
  
    /**
     * Width of the rectangle.
     * @type {number}
     * @private
     */
    this.w_ = w;
  
  
    /**
     * Height of the rectangle.
     * @type {number}
     * @private
     */
    this.h_ = h;
  };
  goog.inherits(goog.graphics.CanvasRectElement, goog.graphics.RectElement);
  
  
  /**
   * Update the position of the rectangle.
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @override
   */
  goog.graphics.CanvasRectElement.prototype.setPosition = function(x, y) {
    this.x_ = x;
    this.y_ = y;
    if (this.drawn_) {
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Whether the rectangle has been drawn yet.
   * @type {boolean}
   * @private
   */
  goog.graphics.CanvasRectElement.prototype.drawn_ = false;
  
  
  /**
   * Update the size of the rectangle.
   * @param {number} width Width of rectangle.
   * @param {number} height Height of rectangle.
   * @override
   */
  goog.graphics.CanvasRectElement.prototype.setSize = function(width, height) {
    this.w_ = width;
    this.h_ = height;
    if (this.drawn_) {
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Draw the rectangle.  Should be treated as package scope.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   */
  goog.graphics.CanvasRectElement.prototype.draw = function(ctx) {
    this.drawn_ = true;
    ctx.beginPath();
    ctx.moveTo(this.x_, this.y_);
    ctx.lineTo(this.x_, this.y_ + this.h_);
    ctx.lineTo(this.x_ + this.w_, this.y_ + this.h_);
    ctx.lineTo(this.x_ + this.w_, this.y_);
    ctx.closePath();
  };
  
  
  
  /**
   * Thin wrapper for canvas path elements.
   * This is an implementation of the goog.graphics.PathElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {Element} element The DOM element to wrap.
   * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
   *     this element.
   * @param {!goog.graphics.Path} path The path object to draw.
   * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
   * @param {goog.graphics.Fill} fill The fill to use for this element.
   * @constructor
   * @extends {goog.graphics.PathElement}
   * @final
   */
  goog.graphics.CanvasPathElement = function(
      element, graphics, path, stroke, fill) {
    goog.graphics.PathElement.call(this, element, graphics, stroke, fill);
  
    this.setPath(path);
  };
  goog.inherits(goog.graphics.CanvasPathElement, goog.graphics.PathElement);
  
  
  /**
   * Whether the shape has been drawn yet.
   * @type {boolean}
   * @private
   */
  goog.graphics.CanvasPathElement.prototype.drawn_ = false;
  
  
  /**
   * The path to draw.
   * @type {goog.graphics.Path}
   * @private
   */
  goog.graphics.CanvasPathElement.prototype.path_;
  
  
  /**
   * Update the underlying path.
   * @param {!goog.graphics.Path} path The path object to draw.
   * @override
   */
  goog.graphics.CanvasPathElement.prototype.setPath = function(path) {
    this.path_ =
        path.isSimple() ? path : goog.graphics.Path.createSimplifiedPath(path);
    if (this.drawn_) {
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Draw the path.  Should be treated as package scope.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   * @suppress {deprecated} goog.graphics is deprecated.
   */
  goog.graphics.CanvasPathElement.prototype.draw = function(ctx) {
    this.drawn_ = true;
  
    ctx.beginPath();
    this.path_.forEachSegment(function(segment, args) {
      switch (segment) {
        case goog.graphics.Path.Segment.MOVETO:
          ctx.moveTo(args[0], args[1]);
          break;
        case goog.graphics.Path.Segment.LINETO:
          for (var i = 0; i < args.length; i += 2) {
            ctx.lineTo(args[i], args[i + 1]);
          }
          break;
        case goog.graphics.Path.Segment.CURVETO:
          for (var i = 0; i < args.length; i += 6) {
            ctx.bezierCurveTo(
                args[i], args[i + 1], args[i + 2], args[i + 3], args[i + 4],
                args[i + 5]);
          }
          break;
        case goog.graphics.Path.Segment.ARCTO:
          throw new Error('Canvas paths cannot contain arcs');
        case goog.graphics.Path.Segment.CLOSE:
          ctx.closePath();
          break;
      }
    });
  };
  
  
  
  /**
   * Thin wrapper for canvas text elements.
   * This is an implementation of the goog.graphics.TextElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {!goog.graphics.CanvasGraphics} graphics The graphics creating
   *     this element.
   * @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 The stroke to use for this element.
   * @param {goog.graphics.Fill} fill The fill to use for this element.
   * @constructor
   * @extends {goog.graphics.TextElement}
   * @final
   */
  goog.graphics.CanvasTextElement = function(
      graphics, text, x1, y1, x2, y2, align, font, stroke, fill) {
    var element = goog.dom.createDom(
        goog.dom.TagName.DIV,
        {'style': 'display:table;position:absolute;padding:0;margin:0;border:0'});
    goog.graphics.TextElement.call(this, element, graphics, stroke, fill);
  
    /**
     * The text to draw.
     * @type {string}
     * @private
     */
    this.text_ = text;
  
    /**
     * X coordinate of the start of the line the text is drawn on.
     * @type {number}
     * @private
     */
    this.x1_ = x1;
  
    /**
     * Y coordinate of the start of the line the text is drawn on.
     * @type {number}
     * @private
     */
    this.y1_ = y1;
  
    /**
     * X coordinate of the end of the line the text is drawn on.
     * @type {number}
     * @private
     */
    this.x2_ = x2;
  
    /**
     * Y coordinate of the end of the line the text is drawn on.
     * @type {number}
     * @private
     */
    this.y2_ = y2;
  
    /**
     * Horizontal alignment: left (default), center, right.
     * @type {string}
     * @private
     */
    this.align_ = align || 'left';
  
    /**
     * Font object describing the font properties.
     * @type {goog.graphics.Font}
     * @private
     */
    this.font_ = font;
  
    /**
     * The inner element that contains the text.
     * @type {Element}
     * @private
     */
    this.innerElement_ = goog.dom.createDom(
        goog.dom.TagName.DIV,
        {'style': 'display:table-cell;padding: 0;margin: 0;border: 0'});
  
    this.updateStyle_();
    this.updateText_();
  
    // Append to the DOM.
    graphics.getElement().appendChild(element);
    element.appendChild(this.innerElement_);
  };
  goog.inherits(goog.graphics.CanvasTextElement, goog.graphics.TextElement);
  
  
  /**
   * Update the displayed text of the element.
   * @param {string} text The text to draw.
   * @override
   */
  goog.graphics.CanvasTextElement.prototype.setText = function(text) {
    this.text_ = text;
    this.updateText_();
  };
  
  
  /**
   * Sets the fill for this element.
   * @param {goog.graphics.Fill} fill The fill object.
   * @override
   */
  goog.graphics.CanvasTextElement.prototype.setFill = function(fill) {
    this.fill = fill;
    var element = this.getElement();
    if (element) {
      element.style.color = fill.getColor() || fill.getColor1();
    }
  };
  
  
  /**
   * Sets the stroke for this element.
   * @param {goog.graphics.Stroke} stroke The stroke object.
   * @override
   */
  goog.graphics.CanvasTextElement.prototype.setStroke = function(stroke) {
    // Ignore stroke
  };
  
  
  /**
   * Draw the text.  Should be treated as package scope.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   */
  goog.graphics.CanvasTextElement.prototype.draw = function(ctx) {
    // Do nothing - the text is already drawn.
  };
  
  
  /**
   * Update the styles of the DIVs.
   * @private
   */
  goog.graphics.CanvasTextElement.prototype.updateStyle_ = function() {
    var x1 = this.x1_;
    var x2 = this.x2_;
    var y1 = this.y1_;
    var y2 = this.y2_;
    var align = this.align_;
    var font = this.font_;
    var style = this.getElement().style;
    var scaleX = this.getGraphics().getPixelScaleX();
    var scaleY = this.getGraphics().getPixelScaleY();
  
    if (x1 == x2) {
      // Special case vertical text
      style.lineHeight = '90%';
  
      this.innerElement_.style.verticalAlign =
          align == 'center' ? 'middle' : align == 'left' ?
                              (y1 < y2 ? 'top' : 'bottom') :
                              y1 < y2 ? 'bottom' : 'top';
      style.textAlign = 'center';
  
      var w = font.size * scaleX;
      style.top = Math.round(Math.min(y1, y2) * scaleY) + 'px';
      style.left = Math.round((x1 - w / 2) * scaleX) + 'px';
      style.width = Math.round(w) + 'px';
      style.height = Math.abs(y1 - y2) * scaleY + 'px';
  
      style.fontSize = font.size * 0.6 * scaleY + 'pt';
    } else {
      style.lineHeight = '100%';
      this.innerElement_.style.verticalAlign = 'top';
      style.textAlign = align;
  
      style.top = Math.round(((y1 + y2) / 2 - font.size * 2 / 3) * scaleY) + 'px';
      style.left = Math.round(x1 * scaleX) + 'px';
      style.width = Math.round(Math.abs(x2 - x1) * scaleX) + 'px';
      style.height = 'auto';
  
      style.fontSize = font.size * scaleY + 'pt';
    }
  
    style.fontWeight = font.bold ? 'bold' : 'normal';
    style.fontStyle = font.italic ? 'italic' : 'normal';
    style.fontFamily = font.family;
  
    var fill = this.getFill();
    style.color = fill.getColor() || fill.getColor1();
  };
  
  
  /**
   * Update the text content.
   * @private
   */
  goog.graphics.CanvasTextElement.prototype.updateText_ = function() {
    if (this.x1_ == this.x2_) {
      // Special case vertical text
      var html =
          goog.array
              .map(
                  this.text_.split(''),
                  function(entry) { return goog.string.htmlEscape(entry); })
              .join('<br>');
      // Creating a SafeHtml for each character would be quite expensive, and it's
      // obvious that this is safe, so an unchecked conversion is appropriate.
      var safeHtml =
          goog.html.uncheckedconversions
              .safeHtmlFromStringKnownToSatisfyTypeContract(
                  goog.string.Const.from('Concatenate escaped chars and <br>'),
                  html);
      goog.dom.safe.setInnerHtml(
          /** @type {!Element} */ (this.innerElement_), safeHtml);
    } else {
      goog.dom.safe.setInnerHtml(
          /** @type {!Element} */ (this.innerElement_),
          goog.html.SafeHtml.htmlEscape(this.text_));
    }
  };
  
  
  
  /**
   * Thin wrapper for canvas image elements.
   * This is an implementation of the goog.graphics.ImageElement interface.
   * You should not construct objects from this constructor. The graphics
   * will return the object for you.
   * @param {Element} element The DOM element to wrap.
   * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
   *     this element.
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @param {number} w Width of rectangle.
   * @param {number} h Height of rectangle.
   * @param {string} src Source of the image.
   * @constructor
   * @extends {goog.graphics.ImageElement}
   * @final
   */
  goog.graphics.CanvasImageElement = function(
      element, graphics, x, y, w, h, src) {
    goog.graphics.ImageElement.call(this, element, graphics);
  
    /**
     * X coordinate of the top left corner.
     * @type {number}
     * @private
     */
    this.x_ = x;
  
  
    /**
     * Y coordinate of the top left corner.
     * @type {number}
     * @private
     */
    this.y_ = y;
  
  
    /**
     * Width of the rectangle.
     * @type {number}
     * @private
     */
    this.w_ = w;
  
  
    /**
     * Height of the rectangle.
     * @type {number}
     * @private
     */
    this.h_ = h;
  
  
    /**
     * URL of the image source.
     * @type {string}
     * @private
     */
    this.src_ = src;
  };
  goog.inherits(goog.graphics.CanvasImageElement, goog.graphics.ImageElement);
  
  
  /**
   * Whether the image has been drawn yet.
   * @type {boolean}
   * @private
   */
  goog.graphics.CanvasImageElement.prototype.drawn_ = false;
  
  
  /**
   * Update the position of the image.
   * @param {number} x X coordinate (left).
   * @param {number} y Y coordinate (top).
   * @override
   */
  goog.graphics.CanvasImageElement.prototype.setPosition = function(x, y) {
    this.x_ = x;
    this.y_ = y;
    if (this.drawn_) {
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Update the size of the image.
   * @param {number} width Width of rectangle.
   * @param {number} height Height of rectangle.
   * @override
   */
  goog.graphics.CanvasImageElement.prototype.setSize = function(width, height) {
    this.w_ = width;
    this.h_ = height;
    if (this.drawn_) {
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Update the source of the image.
   * @param {string} src Source of the image.
   * @override
   */
  goog.graphics.CanvasImageElement.prototype.setSource = function(src) {
    this.src_ = src;
    if (this.drawn_) {
      // TODO(user): Probably need to reload the image here.
      this.getGraphics().redraw();
    }
  };
  
  
  /**
   * Draw the image.  Should be treated as package scope.
   * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
   */
  goog.graphics.CanvasImageElement.prototype.draw = function(ctx) {
    if (this.img_) {
      if (this.w_ && this.h_) {
        // If the image is already loaded, draw it.
        ctx.drawImage(this.img_, this.x_, this.y_, this.w_, this.h_);
      }
      this.drawn_ = true;
  
    } else {
      // Otherwise, load it.
      var img = new Image();
      img.onload = goog.bind(this.handleImageLoad_, this, img);
      // TODO(user): Handle image load errors.
      img.src = this.src_;
    }
  };
  
  
  /**
   * Handle an image load.
   * @param {Element} img The image element that finished loading.
   * @private
   */
  goog.graphics.CanvasImageElement.prototype.handleImageLoad_ = function(img) {
    this.img_ = img;
  
    // TODO(user): Add a small delay to catch batched images
    this.getGraphics().redraw();
  };
  