Path: blob/trunk/third_party/closure/goog/graphics/canvasgraphics.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 CanvasGraphics sub class that uses the canvas tag for drawing.17* @author [email protected] (Robby Walker)18*/192021goog.provide('goog.graphics.CanvasGraphics');222324goog.require('goog.dom.TagName');25goog.require('goog.events.EventType');26goog.require('goog.graphics.AbstractGraphics');27goog.require('goog.graphics.CanvasEllipseElement');28goog.require('goog.graphics.CanvasGroupElement');29goog.require('goog.graphics.CanvasImageElement');30goog.require('goog.graphics.CanvasPathElement');31goog.require('goog.graphics.CanvasRectElement');32goog.require('goog.graphics.CanvasTextElement');33goog.require('goog.graphics.Font');34goog.require('goog.graphics.SolidFill');35goog.require('goog.math.Size');36goog.require('goog.style');37383940/**41* A Graphics implementation for drawing using canvas.42* @param {string|number} width The (non-zero) width in pixels. Strings43* expressing percentages of parent with (e.g. '80%') are also accepted.44* @param {string|number} height The (non-zero) height in pixels. Strings45* expressing percentages of parent with (e.g. '80%') are also accepted.46* @param {?number=} opt_coordWidth The coordinate width - if47* omitted or null, defaults to same as width.48* @param {?number=} opt_coordHeight The coordinate height - if49* omitted or null, defaults to same as height.50* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the51* document we want to render in.52* @constructor53* @extends {goog.graphics.AbstractGraphics}54* @deprecated goog.graphics is deprecated. It existed to abstract over browser55* differences before the canvas tag was widely supported. See56* http://en.wikipedia.org/wiki/Canvas_element for details.57*/58goog.graphics.CanvasGraphics = function(59width, height, opt_coordWidth, opt_coordHeight, opt_domHelper) {60goog.graphics.AbstractGraphics.call(61this, width, height, opt_coordWidth, opt_coordHeight, opt_domHelper);62};63goog.inherits(goog.graphics.CanvasGraphics, goog.graphics.AbstractGraphics);646566/**67* Sets the fill for the given element.68* @param {goog.graphics.StrokeAndFillElement} element The element69* wrapper.70* @param {goog.graphics.Fill} fill The fill object.71* @override72*/73goog.graphics.CanvasGraphics.prototype.setElementFill = function(74element, fill) {75this.redraw();76};777879/**80* Sets the stroke for the given element.81* @param {goog.graphics.StrokeAndFillElement} element The element82* wrapper.83* @param {goog.graphics.Stroke} stroke The stroke object.84* @override85*/86goog.graphics.CanvasGraphics.prototype.setElementStroke = function(87element, stroke) {88this.redraw();89};909192/**93* Set the translation and rotation of an element.94*95* If a more general affine transform is needed than this provides96* (e.g. skew and scale) then use setElementAffineTransform.97* @param {goog.graphics.Element} element The element wrapper.98* @param {number} x The x coordinate of the translation transform.99* @param {number} y The y coordinate of the translation transform.100* @param {number} angle The angle of the rotation transform.101* @param {number} centerX The horizontal center of the rotation transform.102* @param {number} centerY The vertical center of the rotation transform.103* @override104*/105goog.graphics.CanvasGraphics.prototype.setElementTransform = function(106element, x, y, angle, centerX, centerY) {107this.redraw();108};109110111/**112* Set the transformation of an element.113*114* Note that in this implementation this method just calls this.redraw()115* and the affineTransform param is unused.116* @param {!goog.graphics.Element} element The element wrapper.117* @param {!goog.graphics.AffineTransform} affineTransform The118* transformation applied to this element.119* @override120*/121goog.graphics.CanvasGraphics.prototype.setElementAffineTransform = function(122element, affineTransform) {123this.redraw();124};125126127/**128* Push an element transform on to the transform stack.129* @param {goog.graphics.Element} element The transformed element.130*/131goog.graphics.CanvasGraphics.prototype.pushElementTransform = function(132element) {133var ctx = this.getContext();134ctx.save();135136var transform = element.getTransform();137138// TODO(robbyw): Test for unsupported transforms i.e. skews.139var tx = transform.getTranslateX();140var ty = transform.getTranslateY();141if (tx || ty) {142ctx.translate(tx, ty);143}144145var sinTheta = transform.getShearY();146if (sinTheta) {147ctx.rotate(Math.asin(sinTheta));148}149};150151152/**153* Pop an element transform off of the transform stack.154*/155goog.graphics.CanvasGraphics.prototype.popElementTransform = function() {156this.getContext().restore();157};158159160/**161* Creates the DOM representation of the graphics area.162* @override163*/164goog.graphics.CanvasGraphics.prototype.createDom = function() {165var element = this.dom_.createDom(166goog.dom.TagName.DIV, {'style': 'position:relative;overflow:hidden'});167this.setElementInternal(element);168169this.canvas_ = this.dom_.createDom(goog.dom.TagName.CANVAS);170element.appendChild(this.canvas_);171172/**173* The main canvas element.174* @type {goog.graphics.CanvasGroupElement}175*/176this.canvasElement = new goog.graphics.CanvasGroupElement(this);177178this.lastGroup_ = this.canvasElement;179this.redrawTimeout_ = 0;180181this.updateSize();182};183184185/**186* Clears the drawing context object in response to actions that make the old187* context invalid - namely resize of the canvas element.188* @private189*/190goog.graphics.CanvasGraphics.prototype.clearContext_ = function() {191this.context_ = null;192};193194195/**196* Returns the drawing context.197* @return {Object} The canvas element rendering context.198*/199goog.graphics.CanvasGraphics.prototype.getContext = function() {200if (!this.getElement()) {201this.createDom();202}203if (!this.context_) {204this.context_ = this.canvas_.getContext('2d');205this.context_.save();206}207return this.context_;208};209210211/**212* Changes the coordinate system position.213* @param {number} left The coordinate system left bound.214* @param {number} top The coordinate system top bound.215* @override216*/217goog.graphics.CanvasGraphics.prototype.setCoordOrigin = function(left, top) {218this.coordLeft = left;219this.coordTop = top;220this.redraw();221};222223224/**225* Changes the coordinate size.226* @param {number} coordWidth The coordinate width.227* @param {number} coordHeight The coordinate height.228* @override229*/230goog.graphics.CanvasGraphics.prototype.setCoordSize = function(231coordWidth, coordHeight) {232goog.graphics.CanvasGraphics.superClass_.setCoordSize.apply(this, arguments);233this.redraw();234};235236237/**238* Change the size of the canvas.239* @param {number} pixelWidth The width in pixels.240* @param {number} pixelHeight The height in pixels.241* @override242*/243goog.graphics.CanvasGraphics.prototype.setSize = function(244pixelWidth, pixelHeight) {245this.width = pixelWidth;246this.height = pixelHeight;247248this.updateSize();249this.redraw();250};251252253/** @override */254goog.graphics.CanvasGraphics.prototype.getPixelSize = function() {255// goog.style.getSize does not work for Canvas elements. We256// have to compute the size manually if it is percentage based.257var width = this.width;258var height = this.height;259var computeWidth = goog.isString(width) && width.indexOf('%') != -1;260var computeHeight = goog.isString(height) && height.indexOf('%') != -1;261262if (!this.isInDocument() && (computeWidth || computeHeight)) {263return null;264}265266var parent;267var parentSize;268269if (computeWidth) {270parent = /** @type {Element} */ (this.getElement().parentNode);271parentSize = goog.style.getSize(parent);272width = parseFloat(/** @type {string} */ (width)) * parentSize.width / 100;273}274275if (computeHeight) {276parent = parent || /** @type {Element} */ (this.getElement().parentNode);277parentSize = parentSize || goog.style.getSize(parent);278height =279parseFloat(/** @type {string} */ (height)) * parentSize.height / 100;280}281282return new goog.math.Size(283/** @type {number} */ (width),284/** @type {number} */ (height));285};286287288/**289* Update the size of the canvas.290*/291goog.graphics.CanvasGraphics.prototype.updateSize = function() {292goog.style.setSize(this.getElement(), this.width, this.height);293294var pixels = this.getPixelSize();295if (pixels) {296goog.style.setSize(297this.canvas_,298/** @type {number} */ (pixels.width),299/** @type {number} */ (pixels.height));300this.canvas_.width = pixels.width;301this.canvas_.height = pixels.height;302this.clearContext_();303}304};305306307/**308* Reset the canvas.309*/310goog.graphics.CanvasGraphics.prototype.reset = function() {311var ctx = this.getContext();312ctx.restore();313var size = this.getPixelSize();314if (size.width && size.height) {315ctx.clearRect(0, 0, size.width, size.height);316}317ctx.save();318};319320321/**322* Remove all drawing elements from the graphics.323* @override324*/325goog.graphics.CanvasGraphics.prototype.clear = function() {326this.reset();327this.canvasElement.clear();328var el = this.getElement();329330// Remove all children (text nodes) except the canvas (which is at index 0)331while (el.childNodes.length > 1) {332el.removeChild(el.lastChild);333}334};335336337/**338* Redraw the entire canvas.339*/340goog.graphics.CanvasGraphics.prototype.redraw = function() {341if (this.preventRedraw_) {342this.needsRedraw_ = true;343return;344}345346if (this.isInDocument()) {347this.reset();348349if (this.coordWidth) {350var pixels = this.getPixelSize();351this.getContext().scale(352pixels.width / this.coordWidth, pixels.height / this.coordHeight);353}354if (this.coordLeft || this.coordTop) {355this.getContext().translate(-this.coordLeft, -this.coordTop);356}357this.pushElementTransform(this.canvasElement);358this.canvasElement.draw(this.context_);359this.popElementTransform();360}361};362363364/**365* Draw an element, including any stroke or fill.366* @param {goog.graphics.Element} element The element to draw.367*/368goog.graphics.CanvasGraphics.prototype.drawElement = function(element) {369if (element instanceof goog.graphics.CanvasTextElement) {370// Don't draw text since that is not implemented using canvas.371return;372}373374var ctx = this.getContext();375this.pushElementTransform(element);376377if (!element.getFill || !element.getStroke) {378// Draw without stroke or fill (e.g. the element is an image or group).379element.draw(ctx);380this.popElementTransform();381return;382}383384var fill = element.getFill();385if (fill) {386if (fill instanceof goog.graphics.SolidFill) {387if (fill.getOpacity() != 0) {388ctx.globalAlpha = fill.getOpacity();389ctx.fillStyle = fill.getColor();390element.draw(ctx);391ctx.fill();392ctx.globalAlpha = 1;393}394} else { // (fill instanceof goog.graphics.LinearGradient)395var linearGradient = ctx.createLinearGradient(396fill.getX1(), fill.getY1(), fill.getX2(), fill.getY2());397linearGradient.addColorStop(0.0, fill.getColor1());398linearGradient.addColorStop(1.0, fill.getColor2());399400ctx.fillStyle = linearGradient;401element.draw(ctx);402ctx.fill();403}404}405406var stroke = element.getStroke();407if (stroke) {408element.draw(ctx);409ctx.strokeStyle = stroke.getColor();410411var width = stroke.getWidth();412if (goog.isString(width) && width.indexOf('px') != -1) {413width = parseFloat(width) / this.getPixelScaleX();414}415ctx.lineWidth = width;416417ctx.stroke();418}419420this.popElementTransform();421};422423424/**425* Append an element.426*427* @param {goog.graphics.Element} element The element to draw.428* @param {goog.graphics.GroupElement|undefined} group The group to draw429* it in. If null or undefined, defaults to the root group.430* @protected431*/432goog.graphics.CanvasGraphics.prototype.append = function(element, group) {433group = group || this.canvasElement;434group.appendChild(element);435436if (this.isDrawable(group)) {437this.drawElement(element);438}439};440441442/**443* Draw an ellipse.444*445* @param {number} cx Center X coordinate.446* @param {number} cy Center Y coordinate.447* @param {number} rx Radius length for the x-axis.448* @param {number} ry Radius length for the y-axis.449* @param {goog.graphics.Stroke} stroke Stroke object describing the450* stroke.451* @param {goog.graphics.Fill} fill Fill object describing the fill.452* @param {goog.graphics.GroupElement=} opt_group The group wrapper453* element to append to. If not specified, appends to the main canvas.454*455* @return {!goog.graphics.EllipseElement} The newly created element.456* @override457*/458goog.graphics.CanvasGraphics.prototype.drawEllipse = function(459cx, cy, rx, ry, stroke, fill, opt_group) {460var element = new goog.graphics.CanvasEllipseElement(461null, this, cx, cy, rx, ry, stroke, fill);462this.append(element, opt_group);463return element;464};465466467/**468* Draw a rectangle.469*470* @param {number} x X coordinate (left).471* @param {number} y Y coordinate (top).472* @param {number} width Width of rectangle.473* @param {number} height Height of rectangle.474* @param {goog.graphics.Stroke} stroke Stroke object describing the475* stroke.476* @param {goog.graphics.Fill} fill Fill object describing the fill.477* @param {goog.graphics.GroupElement=} opt_group The group wrapper478* element to append to. If not specified, appends to the main canvas.479*480* @return {!goog.graphics.RectElement} The newly created element.481* @override482*/483goog.graphics.CanvasGraphics.prototype.drawRect = function(484x, y, width, height, stroke, fill, opt_group) {485var element = new goog.graphics.CanvasRectElement(486null, this, x, y, width, height, stroke, fill);487this.append(element, opt_group);488return element;489};490491492/**493* Draw an image.494*495* @param {number} x X coordinate (left).496* @param {number} y Y coordinate (top).497* @param {number} width Width of image.498* @param {number} height Height of image.499* @param {string} src Source of the image.500* @param {goog.graphics.GroupElement=} opt_group The group wrapper501* element to append to. If not specified, appends to the main canvas.502*503* @return {!goog.graphics.ImageElement} The newly created element.504*/505goog.graphics.CanvasGraphics.prototype.drawImage = function(506x, y, width, height, src, opt_group) {507var element = new goog.graphics.CanvasImageElement(508null, this, x, y, width, height, src);509this.append(element, opt_group);510return element;511};512513514/**515* Draw a text string vertically centered on a given line.516*517* @param {string} text The text to draw.518* @param {number} x1 X coordinate of start of line.519* @param {number} y1 Y coordinate of start of line.520* @param {number} x2 X coordinate of end of line.521* @param {number} y2 Y coordinate of end of line.522* @param {?string} align Horizontal alignment: left (default), center, right.523* @param {goog.graphics.Font} font Font describing the font properties.524* @param {goog.graphics.Stroke} stroke Stroke object describing the stroke.525* @param {goog.graphics.Fill} fill Fill object describing the fill.526* @param {goog.graphics.GroupElement=} opt_group The group wrapper527* element to append to. If not specified, appends to the main canvas.528*529* @return {!goog.graphics.TextElement} The newly created element.530* @override531*/532goog.graphics.CanvasGraphics.prototype.drawTextOnLine = function(533text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {534var element = new goog.graphics.CanvasTextElement(535this, text, x1, y1, x2, y2, align,536/** @type {!goog.graphics.Font} */ (font), stroke, fill);537this.append(element, opt_group);538return element;539};540541542/**543* Draw a path.544* @param {!goog.graphics.Path} path The path object to draw.545* @param {goog.graphics.Stroke} stroke Stroke object describing the stroke.546* @param {goog.graphics.Fill} fill Fill object describing the fill.547* @param {goog.graphics.GroupElement=} opt_group The group wrapper548* element to append to. If not specified, appends to the main canvas.549*550* @return {!goog.graphics.PathElement} The newly created element.551* @override552*/553goog.graphics.CanvasGraphics.prototype.drawPath = function(554path, stroke, fill, opt_group) {555var element =556new goog.graphics.CanvasPathElement(null, this, path, stroke, fill);557this.append(element, opt_group);558return element;559};560561562/**563* @param {goog.graphics.GroupElement} group The group to possibly564* draw to.565* @return {boolean} Whether drawing can occur now.566*/567goog.graphics.CanvasGraphics.prototype.isDrawable = function(group) {568return this.isInDocument() && !this.redrawTimeout_ &&569!this.isRedrawRequired(group);570};571572573/**574* Returns true if drawing to the given group means a redraw is required.575* @param {goog.graphics.GroupElement} group The group to draw to.576* @return {boolean} Whether drawing to this group should force a redraw.577*/578goog.graphics.CanvasGraphics.prototype.isRedrawRequired = function(group) {579// TODO(robbyw): Moving up to any parent of lastGroup should not force redraw.580return group != this.canvasElement && group != this.lastGroup_;581};582583584/**585* Create an empty group of drawing elements.586*587* @param {goog.graphics.GroupElement=} opt_group The group wrapper588* element to append to. If not specified, appends to the main canvas.589*590* @return {!goog.graphics.CanvasGroupElement} The newly created group.591* @override592*/593goog.graphics.CanvasGraphics.prototype.createGroup = function(opt_group) {594var group = new goog.graphics.CanvasGroupElement(this);595596opt_group = opt_group || this.canvasElement;597598// TODO(robbyw): Moving up to any parent group should not force redraw.599if (opt_group == this.canvasElement || opt_group == this.lastGroup_) {600this.lastGroup_ = group;601}602603this.append(group, opt_group);604605return group;606};607608609/**610* Measure and return the width (in pixels) of a given text string.611* Text measurement is needed to make sure a text can fit in the allocated612* area. The way text length is measured is by writing it into a div that is613* after the visible area, measure the div width, and immediately erase the614* written value.615*616* @param {string} text The text string to measure.617* @param {goog.graphics.Font} font The font object describing the font style.618* @override619*/620goog.graphics.CanvasGraphics.prototype.getTextWidth = goog.abstractMethod;621622623/**624* Disposes of the component by removing event handlers, detacing DOM nodes from625* the document body, and removing references to them.626* @override627* @protected628*/629goog.graphics.CanvasGraphics.prototype.disposeInternal = function() {630this.context_ = null;631goog.graphics.CanvasGraphics.superClass_.disposeInternal.call(this);632};633634635/** @override */636goog.graphics.CanvasGraphics.prototype.enterDocument = function() {637var oldPixelSize = this.getPixelSize();638goog.graphics.CanvasGraphics.superClass_.enterDocument.call(this);639if (!oldPixelSize) {640this.updateSize();641this.dispatchEvent(goog.events.EventType.RESIZE);642}643this.redraw();644};645646647/**648* Start preventing redraws - useful for chaining large numbers of changes649* together. Not guaranteed to do anything - i.e. only use this for650* optimization of a single code path.651* @override652*/653goog.graphics.CanvasGraphics.prototype.suspend = function() {654this.preventRedraw_ = true;655};656657658/**659* Stop preventing redraws. If any redraws had been prevented, a redraw will660* be done now.661* @override662*/663goog.graphics.CanvasGraphics.prototype.resume = function() {664this.preventRedraw_ = false;665666if (this.needsRedraw_) {667this.redraw();668this.needsRedraw_ = false;669}670};671672673/**674* Removes an element from the Canvas.675* @param {goog.graphics.Element} elem the element to remove.676* @override677*/678goog.graphics.CanvasGraphics.prototype.removeElement = function(elem) {679if (!elem) {680return;681}682this.canvasElement.removeElement(elem);683this.redraw();684};685686687