Path: blob/trunk/third_party/closure/goog/graphics/canvaselement.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.131415/**16* @fileoverview Objects representing shapes drawn on a canvas.17* @author [email protected] (Robby Walker)18*/1920goog.provide('goog.graphics.CanvasEllipseElement');21goog.provide('goog.graphics.CanvasGroupElement');22goog.provide('goog.graphics.CanvasImageElement');23goog.provide('goog.graphics.CanvasPathElement');24goog.provide('goog.graphics.CanvasRectElement');25goog.provide('goog.graphics.CanvasTextElement');262728goog.require('goog.array');29goog.require('goog.dom');30goog.require('goog.dom.TagName');31goog.require('goog.dom.safe');32goog.require('goog.graphics.EllipseElement');33goog.require('goog.graphics.Font');34goog.require('goog.graphics.GroupElement');35goog.require('goog.graphics.ImageElement');36goog.require('goog.graphics.Path');37goog.require('goog.graphics.PathElement');38goog.require('goog.graphics.RectElement');39goog.require('goog.graphics.TextElement');40goog.require('goog.html.SafeHtml');41goog.require('goog.html.uncheckedconversions');42goog.require('goog.math');43goog.require('goog.string');44goog.require('goog.string.Const');45464748/**49* Object representing a group of objects in a canvas.50* This is an implementation of the goog.graphics.GroupElement interface.51* You should not construct objects from this constructor. The graphics52* will return the object for you.53* @param {goog.graphics.CanvasGraphics} graphics The graphics creating54* this element.55* @constructor56* @extends {goog.graphics.GroupElement}57* @deprecated goog.graphics is deprecated. It existed to abstract over browser58* differences before the canvas tag was widely supported. See59* http://en.wikipedia.org/wiki/Canvas_element for details.60* @final61*/62goog.graphics.CanvasGroupElement = function(graphics) {63goog.graphics.GroupElement.call(this, null, graphics);646566/**67* Children contained by this group.68* @type {Array<goog.graphics.Element>}69* @private70*/71this.children_ = [];72};73goog.inherits(goog.graphics.CanvasGroupElement, goog.graphics.GroupElement);747576/**77* Remove all drawing elements from the group.78* @override79*/80goog.graphics.CanvasGroupElement.prototype.clear = function() {81if (this.children_.length) {82this.children_.length = 0;83this.getGraphics().redraw();84}85};868788/**89* Set the size of the group element.90* @param {number|string} width The width of the group element.91* @param {number|string} height The height of the group element.92* @override93*/94goog.graphics.CanvasGroupElement.prototype.setSize = function(width, height) {95// Do nothing.96};979899/**100* Append a child to the group. Does not draw it101* @param {goog.graphics.Element} element The child to append.102*/103goog.graphics.CanvasGroupElement.prototype.appendChild = function(element) {104this.children_.push(element);105};106107108/**109* Draw the group.110* @param {CanvasRenderingContext2D} ctx The context to draw the element in.111*/112goog.graphics.CanvasGroupElement.prototype.draw = function(ctx) {113for (var i = 0, len = this.children_.length; i < len; i++) {114this.getGraphics().drawElement(this.children_[i]);115}116};117118119/**120* Removes an element from the group.121* @param {!goog.graphics.Element} elem the element to remove.122*/123goog.graphics.CanvasGroupElement.prototype.removeElement = function(elem) {124goog.array.removeIf(this.children_, function(child) {125// If the child has children (and thus is a group element)126// call removeElement on that group127if (child.children_) {128child.removeElement(elem);129return false;130} else {131return child === elem;132}133});134};135136137138/**139* Thin wrapper for canvas ellipse elements.140* This is an implementation of the goog.graphics.EllipseElement interface.141* You should not construct objects from this constructor. The graphics142* will return the object for you.143* @param {Element} element The DOM element to wrap.144* @param {goog.graphics.CanvasGraphics} graphics The graphics creating145* this element.146* @param {number} cx Center X coordinate.147* @param {number} cy Center Y coordinate.148* @param {number} rx Radius length for the x-axis.149* @param {number} ry Radius length for the y-axis.150* @param {goog.graphics.Stroke} stroke The stroke to use for this element.151* @param {goog.graphics.Fill} fill The fill to use for this element.152* @constructor153* @extends {goog.graphics.EllipseElement}154* @final155*/156goog.graphics.CanvasEllipseElement = function(157element, graphics, cx, cy, rx, ry, stroke, fill) {158goog.graphics.EllipseElement.call(this, element, graphics, stroke, fill);159160/**161* X coordinate of the ellipse center.162* @type {number}163* @private164*/165this.cx_ = cx;166167168/**169* Y coordinate of the ellipse center.170* @type {number}171* @private172*/173this.cy_ = cy;174175176/**177* Radius length for the x-axis.178* @type {number}179* @private180*/181this.rx_ = rx;182183184/**185* Radius length for the y-axis.186* @type {number}187* @private188*/189this.ry_ = ry;190191192/**193* Internal path approximating an ellipse.194* @type {goog.graphics.Path}195* @private196*/197this.path_ = new goog.graphics.Path();198this.setUpPath_();199200/**201* Internal path element that actually does the drawing.202* @type {goog.graphics.CanvasPathElement}203* @private204*/205this.pathElement_ = new goog.graphics.CanvasPathElement(206null, graphics, this.path_, stroke, fill);207};208goog.inherits(goog.graphics.CanvasEllipseElement, goog.graphics.EllipseElement);209210211/**212* Sets up the path.213* @private214*/215goog.graphics.CanvasEllipseElement.prototype.setUpPath_ = function() {216this.path_.clear();217this.path_.moveTo(218this.cx_ + goog.math.angleDx(0, this.rx_),219this.cy_ + goog.math.angleDy(0, this.ry_));220this.path_.arcTo(this.rx_, this.ry_, 0, 360);221this.path_.close();222};223224225/**226* Update the center point of the ellipse.227* @param {number} cx Center X coordinate.228* @param {number} cy Center Y coordinate.229* @override230*/231goog.graphics.CanvasEllipseElement.prototype.setCenter = function(cx, cy) {232this.cx_ = cx;233this.cy_ = cy;234this.setUpPath_();235this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));236};237238239/**240* Update the radius of the ellipse.241* @param {number} rx Center X coordinate.242* @param {number} ry Center Y coordinate.243* @override244*/245goog.graphics.CanvasEllipseElement.prototype.setRadius = function(rx, ry) {246this.rx_ = rx;247this.ry_ = ry;248this.setUpPath_();249this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));250};251252253/**254* Draw the ellipse. Should be treated as package scope.255* @param {CanvasRenderingContext2D} ctx The context to draw the element in.256*/257goog.graphics.CanvasEllipseElement.prototype.draw = function(ctx) {258this.pathElement_.draw(ctx);259};260261262263/**264* Thin wrapper for canvas rectangle elements.265* This is an implementation of the goog.graphics.RectElement interface.266* You should not construct objects from this constructor. The graphics267* will return the object for you.268* @param {Element} element The DOM element to wrap.269* @param {goog.graphics.CanvasGraphics} graphics The graphics creating270* this element.271* @param {number} x X coordinate (left).272* @param {number} y Y coordinate (top).273* @param {number} w Width of rectangle.274* @param {number} h Height of rectangle.275* @param {goog.graphics.Stroke} stroke The stroke to use for this element.276* @param {goog.graphics.Fill} fill The fill to use for this element.277* @constructor278* @extends {goog.graphics.RectElement}279* @final280*/281goog.graphics.CanvasRectElement = function(282element, graphics, x, y, w, h, stroke, fill) {283goog.graphics.RectElement.call(this, element, graphics, stroke, fill);284285/**286* X coordinate of the top left corner.287* @type {number}288* @private289*/290this.x_ = x;291292293/**294* Y coordinate of the top left corner.295* @type {number}296* @private297*/298this.y_ = y;299300301/**302* Width of the rectangle.303* @type {number}304* @private305*/306this.w_ = w;307308309/**310* Height of the rectangle.311* @type {number}312* @private313*/314this.h_ = h;315};316goog.inherits(goog.graphics.CanvasRectElement, goog.graphics.RectElement);317318319/**320* Update the position of the rectangle.321* @param {number} x X coordinate (left).322* @param {number} y Y coordinate (top).323* @override324*/325goog.graphics.CanvasRectElement.prototype.setPosition = function(x, y) {326this.x_ = x;327this.y_ = y;328if (this.drawn_) {329this.getGraphics().redraw();330}331};332333334/**335* Whether the rectangle has been drawn yet.336* @type {boolean}337* @private338*/339goog.graphics.CanvasRectElement.prototype.drawn_ = false;340341342/**343* Update the size of the rectangle.344* @param {number} width Width of rectangle.345* @param {number} height Height of rectangle.346* @override347*/348goog.graphics.CanvasRectElement.prototype.setSize = function(width, height) {349this.w_ = width;350this.h_ = height;351if (this.drawn_) {352this.getGraphics().redraw();353}354};355356357/**358* Draw the rectangle. Should be treated as package scope.359* @param {CanvasRenderingContext2D} ctx The context to draw the element in.360*/361goog.graphics.CanvasRectElement.prototype.draw = function(ctx) {362this.drawn_ = true;363ctx.beginPath();364ctx.moveTo(this.x_, this.y_);365ctx.lineTo(this.x_, this.y_ + this.h_);366ctx.lineTo(this.x_ + this.w_, this.y_ + this.h_);367ctx.lineTo(this.x_ + this.w_, this.y_);368ctx.closePath();369};370371372373/**374* Thin wrapper for canvas path elements.375* This is an implementation of the goog.graphics.PathElement interface.376* You should not construct objects from this constructor. The graphics377* will return the object for you.378* @param {Element} element The DOM element to wrap.379* @param {goog.graphics.CanvasGraphics} graphics The graphics creating380* this element.381* @param {!goog.graphics.Path} path The path object to draw.382* @param {goog.graphics.Stroke} stroke The stroke to use for this element.383* @param {goog.graphics.Fill} fill The fill to use for this element.384* @constructor385* @extends {goog.graphics.PathElement}386* @final387*/388goog.graphics.CanvasPathElement = function(389element, graphics, path, stroke, fill) {390goog.graphics.PathElement.call(this, element, graphics, stroke, fill);391392this.setPath(path);393};394goog.inherits(goog.graphics.CanvasPathElement, goog.graphics.PathElement);395396397/**398* Whether the shape has been drawn yet.399* @type {boolean}400* @private401*/402goog.graphics.CanvasPathElement.prototype.drawn_ = false;403404405/**406* The path to draw.407* @type {goog.graphics.Path}408* @private409*/410goog.graphics.CanvasPathElement.prototype.path_;411412413/**414* Update the underlying path.415* @param {!goog.graphics.Path} path The path object to draw.416* @override417*/418goog.graphics.CanvasPathElement.prototype.setPath = function(path) {419this.path_ =420path.isSimple() ? path : goog.graphics.Path.createSimplifiedPath(path);421if (this.drawn_) {422this.getGraphics().redraw();423}424};425426427/**428* Draw the path. Should be treated as package scope.429* @param {CanvasRenderingContext2D} ctx The context to draw the element in.430* @suppress {deprecated} goog.graphics is deprecated.431*/432goog.graphics.CanvasPathElement.prototype.draw = function(ctx) {433this.drawn_ = true;434435ctx.beginPath();436this.path_.forEachSegment(function(segment, args) {437switch (segment) {438case goog.graphics.Path.Segment.MOVETO:439ctx.moveTo(args[0], args[1]);440break;441case goog.graphics.Path.Segment.LINETO:442for (var i = 0; i < args.length; i += 2) {443ctx.lineTo(args[i], args[i + 1]);444}445break;446case goog.graphics.Path.Segment.CURVETO:447for (var i = 0; i < args.length; i += 6) {448ctx.bezierCurveTo(449args[i], args[i + 1], args[i + 2], args[i + 3], args[i + 4],450args[i + 5]);451}452break;453case goog.graphics.Path.Segment.ARCTO:454throw Error('Canvas paths cannot contain arcs');455case goog.graphics.Path.Segment.CLOSE:456ctx.closePath();457break;458}459});460};461462463464/**465* Thin wrapper for canvas text elements.466* This is an implementation of the goog.graphics.TextElement interface.467* You should not construct objects from this constructor. The graphics468* will return the object for you.469* @param {!goog.graphics.CanvasGraphics} graphics The graphics creating470* this element.471* @param {string} text The text to draw.472* @param {number} x1 X coordinate of start of line.473* @param {number} y1 Y coordinate of start of line.474* @param {number} x2 X coordinate of end of line.475* @param {number} y2 Y coordinate of end of line.476* @param {?string} align Horizontal alignment: left (default), center, right.477* @param {!goog.graphics.Font} font Font describing the font properties.478* @param {goog.graphics.Stroke} stroke The stroke to use for this element.479* @param {goog.graphics.Fill} fill The fill to use for this element.480* @constructor481* @extends {goog.graphics.TextElement}482* @final483*/484goog.graphics.CanvasTextElement = function(485graphics, text, x1, y1, x2, y2, align, font, stroke, fill) {486var element = goog.dom.createDom(487goog.dom.TagName.DIV,488{'style': 'display:table;position:absolute;padding:0;margin:0;border:0'});489goog.graphics.TextElement.call(this, element, graphics, stroke, fill);490491/**492* The text to draw.493* @type {string}494* @private495*/496this.text_ = text;497498/**499* X coordinate of the start of the line the text is drawn on.500* @type {number}501* @private502*/503this.x1_ = x1;504505/**506* Y coordinate of the start of the line the text is drawn on.507* @type {number}508* @private509*/510this.y1_ = y1;511512/**513* X coordinate of the end of the line the text is drawn on.514* @type {number}515* @private516*/517this.x2_ = x2;518519/**520* Y coordinate of the end of the line the text is drawn on.521* @type {number}522* @private523*/524this.y2_ = y2;525526/**527* Horizontal alignment: left (default), center, right.528* @type {string}529* @private530*/531this.align_ = align || 'left';532533/**534* Font object describing the font properties.535* @type {goog.graphics.Font}536* @private537*/538this.font_ = font;539540/**541* The inner element that contains the text.542* @type {Element}543* @private544*/545this.innerElement_ = goog.dom.createDom(546goog.dom.TagName.DIV,547{'style': 'display:table-cell;padding: 0;margin: 0;border: 0'});548549this.updateStyle_();550this.updateText_();551552// Append to the DOM.553graphics.getElement().appendChild(element);554element.appendChild(this.innerElement_);555};556goog.inherits(goog.graphics.CanvasTextElement, goog.graphics.TextElement);557558559/**560* Update the displayed text of the element.561* @param {string} text The text to draw.562* @override563*/564goog.graphics.CanvasTextElement.prototype.setText = function(text) {565this.text_ = text;566this.updateText_();567};568569570/**571* Sets the fill for this element.572* @param {goog.graphics.Fill} fill The fill object.573* @override574*/575goog.graphics.CanvasTextElement.prototype.setFill = function(fill) {576this.fill = fill;577var element = this.getElement();578if (element) {579element.style.color = fill.getColor() || fill.getColor1();580}581};582583584/**585* Sets the stroke for this element.586* @param {goog.graphics.Stroke} stroke The stroke object.587* @override588*/589goog.graphics.CanvasTextElement.prototype.setStroke = function(stroke) {590// Ignore stroke591};592593594/**595* Draw the text. Should be treated as package scope.596* @param {CanvasRenderingContext2D} ctx The context to draw the element in.597*/598goog.graphics.CanvasTextElement.prototype.draw = function(ctx) {599// Do nothing - the text is already drawn.600};601602603/**604* Update the styles of the DIVs.605* @private606*/607goog.graphics.CanvasTextElement.prototype.updateStyle_ = function() {608var x1 = this.x1_;609var x2 = this.x2_;610var y1 = this.y1_;611var y2 = this.y2_;612var align = this.align_;613var font = this.font_;614var style = this.getElement().style;615var scaleX = this.getGraphics().getPixelScaleX();616var scaleY = this.getGraphics().getPixelScaleY();617618if (x1 == x2) {619// Special case vertical text620style.lineHeight = '90%';621622this.innerElement_.style.verticalAlign =623align == 'center' ? 'middle' : align == 'left' ?624(y1 < y2 ? 'top' : 'bottom') :625y1 < y2 ? 'bottom' : 'top';626style.textAlign = 'center';627628var w = font.size * scaleX;629style.top = Math.round(Math.min(y1, y2) * scaleY) + 'px';630style.left = Math.round((x1 - w / 2) * scaleX) + 'px';631style.width = Math.round(w) + 'px';632style.height = Math.abs(y1 - y2) * scaleY + 'px';633634style.fontSize = font.size * 0.6 * scaleY + 'pt';635} else {636style.lineHeight = '100%';637this.innerElement_.style.verticalAlign = 'top';638style.textAlign = align;639640style.top = Math.round(((y1 + y2) / 2 - font.size * 2 / 3) * scaleY) + 'px';641style.left = Math.round(x1 * scaleX) + 'px';642style.width = Math.round(Math.abs(x2 - x1) * scaleX) + 'px';643style.height = 'auto';644645style.fontSize = font.size * scaleY + 'pt';646}647648style.fontWeight = font.bold ? 'bold' : 'normal';649style.fontStyle = font.italic ? 'italic' : 'normal';650style.fontFamily = font.family;651652var fill = this.getFill();653style.color = fill.getColor() || fill.getColor1();654};655656657/**658* Update the text content.659* @private660*/661goog.graphics.CanvasTextElement.prototype.updateText_ = function() {662if (this.x1_ == this.x2_) {663// Special case vertical text664var html =665goog.array666.map(667this.text_.split(''),668function(entry) { return goog.string.htmlEscape(entry); })669.join('<br>');670// Creating a SafeHtml for each character would be quite expensive, and it's671// obvious that this is safe, so an unchecked conversion is appropriate.672var safeHtml =673goog.html.uncheckedconversions674.safeHtmlFromStringKnownToSatisfyTypeContract(675goog.string.Const.from('Concatenate escaped chars and <br>'),676html);677goog.dom.safe.setInnerHtml(678/** @type {!Element} */ (this.innerElement_), safeHtml);679} else {680goog.dom.safe.setInnerHtml(681/** @type {!Element} */ (this.innerElement_),682goog.html.SafeHtml.htmlEscape(this.text_));683}684};685686687688/**689* Thin wrapper for canvas image elements.690* This is an implementation of the goog.graphics.ImageElement interface.691* You should not construct objects from this constructor. The graphics692* will return the object for you.693* @param {Element} element The DOM element to wrap.694* @param {goog.graphics.CanvasGraphics} graphics The graphics creating695* this element.696* @param {number} x X coordinate (left).697* @param {number} y Y coordinate (top).698* @param {number} w Width of rectangle.699* @param {number} h Height of rectangle.700* @param {string} src Source of the image.701* @constructor702* @extends {goog.graphics.ImageElement}703* @final704*/705goog.graphics.CanvasImageElement = function(706element, graphics, x, y, w, h, src) {707goog.graphics.ImageElement.call(this, element, graphics);708709/**710* X coordinate of the top left corner.711* @type {number}712* @private713*/714this.x_ = x;715716717/**718* Y coordinate of the top left corner.719* @type {number}720* @private721*/722this.y_ = y;723724725/**726* Width of the rectangle.727* @type {number}728* @private729*/730this.w_ = w;731732733/**734* Height of the rectangle.735* @type {number}736* @private737*/738this.h_ = h;739740741/**742* URL of the image source.743* @type {string}744* @private745*/746this.src_ = src;747};748goog.inherits(goog.graphics.CanvasImageElement, goog.graphics.ImageElement);749750751/**752* Whether the image has been drawn yet.753* @type {boolean}754* @private755*/756goog.graphics.CanvasImageElement.prototype.drawn_ = false;757758759/**760* Update the position of the image.761* @param {number} x X coordinate (left).762* @param {number} y Y coordinate (top).763* @override764*/765goog.graphics.CanvasImageElement.prototype.setPosition = function(x, y) {766this.x_ = x;767this.y_ = y;768if (this.drawn_) {769this.getGraphics().redraw();770}771};772773774/**775* Update the size of the image.776* @param {number} width Width of rectangle.777* @param {number} height Height of rectangle.778* @override779*/780goog.graphics.CanvasImageElement.prototype.setSize = function(width, height) {781this.w_ = width;782this.h_ = height;783if (this.drawn_) {784this.getGraphics().redraw();785}786};787788789/**790* Update the source of the image.791* @param {string} src Source of the image.792* @override793*/794goog.graphics.CanvasImageElement.prototype.setSource = function(src) {795this.src_ = src;796if (this.drawn_) {797// TODO(robbyw): Probably need to reload the image here.798this.getGraphics().redraw();799}800};801802803/**804* Draw the image. Should be treated as package scope.805* @param {CanvasRenderingContext2D} ctx The context to draw the element in.806*/807goog.graphics.CanvasImageElement.prototype.draw = function(ctx) {808if (this.img_) {809if (this.w_ && this.h_) {810// If the image is already loaded, draw it.811ctx.drawImage(this.img_, this.x_, this.y_, this.w_, this.h_);812}813this.drawn_ = true;814815} else {816// Otherwise, load it.817var img = new Image();818img.onload = goog.bind(this.handleImageLoad_, this, img);819// TODO(robbyw): Handle image load errors.820img.src = this.src_;821}822};823824825/**826* Handle an image load.827* @param {Element} img The image element that finished loading.828* @private829*/830goog.graphics.CanvasImageElement.prototype.handleImageLoad_ = function(img) {831this.img_ = img;832833// TODO(robbyw): Add a small delay to catch batched images834this.getGraphics().redraw();835};836837838