// Copyright 2006 Google Inc.//// 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.// Known Issues://// * Patterns are not implemented.// * Radial gradient are not implemented. The VML version of these look very// different from the canvas one.// * Clipping paths are not implemented.// * Coordsize. The width and height attribute have higher priority than the// width and height style values which isn't correct.// * Painting mode isn't implemented.// * Canvas width/height should is using content-box by default. IE in// Quirks mode will draw the canvas using border-box. Either change your// doctype to HTML5// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)// or use Box Sizing Behavior from WebFX// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)// * Optimize. There is always room for speed improvements.// only add this code if we do not already have a canvas implementationif(!window.CanvasRenderingContext2D){(function(){// alias some functions to make (compiled) code shortervarm=Math;varmr=m.round;varms=m.sin;varmc=m.cos;// this is used for sub pixel precisionvarZ=10;varZ2=Z/2;varG_vmlCanvasManager_={init:function(opt_doc){vardoc=opt_doc||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){varself=this;doc.attachEvent("onreadystatechange",function(){self.init_(doc);});}},init_:function(doc){if(doc.readyState=="complete"){// create xmlnsif(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml");}// setup default cssvarss=doc.createStyleSheet();ss.cssText="canvas{display:inline-block;overflow:hidden;"+// default size is 300x150 in Gecko and Opera"text-align:left;width:300px;height:150px}"+"g_vml_\\:*{behavior:url(#default#VML)}";// find all canvas elementsvarels=doc.getElementsByTagName("canvas");for(vari=0;i<els.length;i++){if(!els[i].getContext){this.initElement(els[i]);}}}},fixElement_:function(el){// in IE before version 5.5 we would need to add HTML: to the tag name// but we do not care about IE before version 6varouterHTML=el.outerHTML;varnewEl=el.ownerDocument.createElement(outerHTML);// if the tag is still open IE has created the children as siblings and// it has also created a tag with the name "/FOO"if(outerHTML.slice(-2)!="/>"){vartagName="/"+el.tagName;varns;// remove contentwhile((ns=el.nextSibling)&&ns.tagName!=tagName){ns.removeNode();}// remove the incorrect closing tagif(ns){ns.removeNode();}}el.parentNode.replaceChild(newEl,el);returnnewEl;},/** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */initElement:function(el){el=this.fixElement_(el);el.getContext=function(){if(this.context_){returnthis.context_;}returnthis.context_=newCanvasRenderingContext2D_(this);};// do not use inline function because that will leak memoryel.attachEvent('onpropertychange',onPropertyChange);el.attachEvent('onresize',onResize);varattrs=el.attributes;if(attrs.width&&attrs.width.specified){// TODO: use runtimeStyle and coordsize// el.getContext().setWidth_(attrs.width.nodeValue);el.style.width=attrs.width.nodeValue+"px";}else{el.width=el.clientWidth;}if(attrs.height&&attrs.height.specified){// TODO: use runtimeStyle and coordsize// el.getContext().setHeight_(attrs.height.nodeValue);el.style.height=attrs.height.nodeValue+"px";}else{el.height=el.clientHeight;}//el.getContext().setCoordsize_()returnel;}};functiononPropertyChange(e){varel=e.srcElement;switch(e.propertyName){case'width':el.style.width=el.attributes.width.nodeValue+"px";el.getContext().clearRect();break;case'height':el.style.height=el.attributes.height.nodeValue+"px";el.getContext().clearRect();break;}}functiononResize(e){varel=e.srcElement;if(el.firstChild){el.firstChild.style.width=el.clientWidth+'px';el.firstChild.style.height=el.clientHeight+'px';}}G_vmlCanvasManager_.init();// precompute "00" to "FF"vardec2hex=[];for(vari=0;i<16;i++){for(varj=0;j<16;j++){dec2hex[i*16+j]=i.toString(16)+j.toString(16);}}functioncreateMatrixIdentity(){return[[1,0,0],[0,1,0],[0,0,1]];}functionmatrixMultiply(m1,m2){varresult=createMatrixIdentity();for(varx=0;x<3;x++){for(vary=0;y<3;y++){varsum=0;for(varz=0;z<3;z++){sum+=m1[x][z]*m2[z][y];}result[x][y]=sum;}}returnresult;}functioncopyState(o1,o2){o2.fillStyle=o1.fillStyle;o2.lineCap=o1.lineCap;o2.lineJoin=o1.lineJoin;o2.lineWidth=o1.lineWidth;o2.miterLimit=o1.miterLimit;o2.shadowBlur=o1.shadowBlur;o2.shadowColor=o1.shadowColor;o2.shadowOffsetX=o1.shadowOffsetX;o2.shadowOffsetY=o1.shadowOffsetY;o2.strokeStyle=o1.strokeStyle;o2.arcScaleX_=o1.arcScaleX_;o2.arcScaleY_=o1.arcScaleY_;}functionprocessStyle(styleString){varstr,alpha=1;styleString=String(styleString);if(styleString.substring(0,3)=="rgb"){varstart=styleString.indexOf("(",3);varend=styleString.indexOf(")",start+1);varguts=styleString.substring(start+1,end).split(",");str="#";for(vari=0;i<3;i++){str+=dec2hex[Number(guts[i])];}if((guts.length==4)&&(styleString.substr(3,1)=="a")){alpha=guts[3];}}else{str=styleString;}return[str,alpha];}functionprocessLineCap(lineCap){switch(lineCap){case"butt":return"flat";case"round":return"round";case"square":default:return"square";}}/** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */functionCanvasRenderingContext2D_(surfaceElement){this.m_=createMatrixIdentity();this.mStack_=[];this.aStack_=[];this.currentPath_=[];// Canvas context propertiesthis.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=Z*1;this.globalAlpha=1;this.canvas=surfaceElement;varel=surfaceElement.ownerDocument.createElement('div');el.style.width=surfaceElement.clientWidth+'px';el.style.height=surfaceElement.clientHeight+'px';el.style.overflow='hidden';el.style.position='absolute';surfaceElement.appendChild(el);this.element_=el;this.arcScaleX_=1;this.arcScaleY_=1;}varcontextPrototype=CanvasRenderingContext2D_.prototype;contextPrototype.clearRect=function(){this.element_.innerHTML="";this.currentPath_=[];};contextPrototype.beginPath=function(){// TODO: Branch current matrix so that save/restore has no effect// as per safari docs.this.currentPath_=[];};contextPrototype.moveTo=function(aX,aY){this.currentPath_.push({type:"moveTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY;};contextPrototype.lineTo=function(aX,aY){this.currentPath_.push({type:"lineTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY;};contextPrototype.bezierCurveTo=function(aCP1x,aCP1y,aCP2x,aCP2y,aX,aY){this.currentPath_.push({type:"bezierCurveTo",cp1x:aCP1x,cp1y:aCP1y,cp2x:aCP2x,cp2y:aCP2y,x:aX,y:aY});this.currentX_=aX;this.currentY_=aY;};contextPrototype.quadraticCurveTo=function(aCPx,aCPy,aX,aY){// the following is lifted almost directly from// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapesvarcp1x=this.currentX_+2.0/3.0*(aCPx-this.currentX_);varcp1y=this.currentY_+2.0/3.0*(aCPy-this.currentY_);varcp2x=cp1x+(aX-this.currentX_)/3.0;varcp2y=cp1y+(aY-this.currentY_)/3.0;this.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,aX,aY);};contextPrototype.arc=function(aX,aY,aRadius,aStartAngle,aEndAngle,aClockwise){aRadius*=Z;vararcType=aClockwise?"at":"wa";varxStart=aX+(mc(aStartAngle)*aRadius)-Z2;varyStart=aY+(ms(aStartAngle)*aRadius)-Z2;varxEnd=aX+(mc(aEndAngle)*aRadius)-Z2;varyEnd=aY+(ms(aEndAngle)*aRadius)-Z2;// IE won't render arches drawn counter clockwise if xStart == xEnd.if(xStart==xEnd&&!aClockwise){xStart+=0.125;// Offset xStart by 1/80 of a pixel. Use something// that can be represented in binary}this.currentPath_.push({type:arcType,x:aX,y:aY,radius:aRadius,xStart:xStart,yStart:yStart,xEnd:xEnd,yEnd:yEnd});};contextPrototype.rect=function(aX,aY,aWidth,aHeight){this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();};contextPrototype.strokeRect=function(aX,aY,aWidth,aHeight){// Will destroy any existing path (same as FF behaviour)this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.stroke();};contextPrototype.fillRect=function(aX,aY,aWidth,aHeight){// Will destroy any existing path (same as FF behaviour)this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.fill();};contextPrototype.createLinearGradient=function(aX0,aY0,aX1,aY1){vargradient=newCanvasGradient_("gradient");returngradient;};contextPrototype.createRadialGradient=function(aX0,aY0,aR0,aX1,aY1,aR1){vargradient=newCanvasGradient_("gradientradial");gradient.radius1_=aR0;gradient.radius2_=aR1;gradient.focus_.x=aX0;gradient.focus_.y=aY0;returngradient;};contextPrototype.drawImage=function(image,var_args){vardx,dy,dw,dh,sx,sy,sw,sh;// to find the original width we overide the width and heightvaroldRuntimeWidth=image.runtimeStyle.width;varoldRuntimeHeight=image.runtimeStyle.height;image.runtimeStyle.width='auto';image.runtimeStyle.height='auto';// get the original sizevarw=image.width;varh=image.height;// and remove overidesimage.runtimeStyle.width=oldRuntimeWidth;image.runtimeStyle.height=oldRuntimeHeight;if(arguments.length==3){dx=arguments[1];dy=arguments[2];sx=sy=0;sw=dw=w;sh=dh=h;}elseif(arguments.length==5){dx=arguments[1];dy=arguments[2];dw=arguments[3];dh=arguments[4];sx=sy=0;sw=w;sh=h;}elseif(arguments.length==9){sx=arguments[1];sy=arguments[2];sw=arguments[3];sh=arguments[4];dx=arguments[5];dy=arguments[6];dw=arguments[7];dh=arguments[8];}else{throw"Invalid number of arguments";}vard=this.getCoords_(dx,dy);varw2=sw/2;varh2=sh/2;varvmlStr=[];varW=10;varH=10;// For some reason that I've now forgotten, using divs didn't workvmlStr.push(' <g_vml_:group',' coordsize="',Z*W,',',Z*H,'"',' coordorigin="0,0"',' style="width:',W,';height:',H,';position:absolute;');// If filters are necessary (rotation exists), create them// filters are bog-slow, so only create them if abbsolutely necessary// The following check doesn't account for skews (which don't exist// in the canvas spec (yet) anyway.if(this.m_[0][0]!=1||this.m_[0][1]){varfilter=[];// Note the 12/21 reversalfilter.push("M11='",this.m_[0][0],"',","M12='",this.m_[1][0],"',","M21='",this.m_[0][1],"',","M22='",this.m_[1][1],"',","Dx='",mr(d.x/Z),"',","Dy='",mr(d.y/Z),"'");// Bounding box calculation (need to minimize displayed area so that// filters don't waste time on unused pixels.varmax=d;varc2=this.getCoords_(dx+dw,dy);varc3=this.getCoords_(dx,dy+dh);varc4=this.getCoords_(dx+dw,dy+dh);max.x=Math.max(max.x,c2.x,c3.x,c4.x);max.y=Math.max(max.y,c2.y,c3.y,c4.y);vmlStr.push("padding:0 ",mr(max.x/Z),"px ",mr(max.y/Z),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",filter.join(""),", sizingmethod='clip');");}else{vmlStr.push("top:",mr(d.y/Z),"px;left:",mr(d.x/Z),"px;");}vmlStr.push(' ">','<g_vml_:image src="',image.src,'"',' style="width:',Z*dw,';',' height:',Z*dh,';"',' cropleft="',sx/w,'"',' croptop="',sy/h,'"',' cropright="',(w-sx-sw)/w,'"',' cropbottom="',(h-sy-sh)/h,'"',' />','</g_vml_:group>');this.element_.insertAdjacentHTML("BeforeEnd",vmlStr.join(""));};contextPrototype.stroke=function(aFill){varlineStr=[];varlineOpen=false;vara=processStyle(aFill?this.fillStyle:this.strokeStyle);varcolor=a[0];varopacity=a[1]*this.globalAlpha;varW=10;varH=10;lineStr.push('<g_vml_:shape',' fillcolor="',color,'"',' filled="',Boolean(aFill),'"',' style="position:absolute;width:',W,';height:',H,';"',' coordorigin="0 0" coordsize="',Z*W,' ',Z*H,'"',' stroked="',!aFill,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',color,'"',' path="');varnewSeq=false;varmin={x:null,y:null};varmax={x:null,y:null};for(vari=0;i<this.currentPath_.length;i++){varp=this.currentPath_[i];if(p.type=="moveTo"){lineStr.push(" m ");varc=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y));}elseif(p.type=="lineTo"){lineStr.push(" l ");varc=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y));}elseif(p.type=="close"){lineStr.push(" x ");}elseif(p.type=="bezierCurveTo"){lineStr.push(" c ");varc=this.getCoords_(p.x,p.y);varc1=this.getCoords_(p.cp1x,p.cp1y);varc2=this.getCoords_(p.cp2x,p.cp2y);lineStr.push(mr(c1.x),",",mr(c1.y),",",mr(c2.x),",",mr(c2.y),",",mr(c.x),",",mr(c.y));}elseif(p.type=="at"||p.type=="wa"){lineStr.push(" ",p.type," ");varc=this.getCoords_(p.x,p.y);varcStart=this.getCoords_(p.xStart,p.yStart);varcEnd=this.getCoords_(p.xEnd,p.yEnd);lineStr.push(mr(c.x-this.arcScaleX_*p.radius),",",mr(c.y-this.arcScaleY_*p.radius)," ",mr(c.x+this.arcScaleX_*p.radius),",",mr(c.y+this.arcScaleY_*p.radius)," ",mr(cStart.x),",",mr(cStart.y)," ",mr(cEnd.x),",",mr(cEnd.y));}// TODO: Following is broken for curves due to// move to proper paths.// Figure out dimensions so we can do gradient fills// properlyif(c){if(min.x==null||c.x<min.x){min.x=c.x;}if(max.x==null||c.x>max.x){max.x=c.x;}if(min.y==null||c.y<min.y){min.y=c.y;}if(max.y==null||c.y>max.y){max.y=c.y;}}}lineStr.push(' ">');if(typeofthis.fillStyle=="object"){varfocus={x:"50%",y:"50%"};varwidth=(max.x-min.x);varheight=(max.y-min.y);vardimension=(width>height)?width:height;focus.x=mr((this.fillStyle.focus_.x/width)*100+50)+"%";focus.y=mr((this.fillStyle.focus_.y/height)*100+50)+"%";varcolors=[];// inside radius (%)if(this.fillStyle.type_=="gradientradial"){varinside=(this.fillStyle.radius1_/dimension*100);// percentage that outside radius exceeds inside radiusvarexpansion=(this.fillStyle.radius2_/dimension*100)-inside;}else{varinside=0;varexpansion=100;}varinsidecolor={offset:null,color:null};varoutsidecolor={offset:null,color:null};// We need to sort 'colors' by percentage, from 0 > 100 otherwise ie// won't interpret it correctlythis.fillStyle.colors_.sort(function(cs1,cs2){returncs1.offset-cs2.offset;});for(vari=0;i<this.fillStyle.colors_.length;i++){varfs=this.fillStyle.colors_[i];colors.push((fs.offset*expansion)+inside,"% ",fs.color,",");if(fs.offset>insidecolor.offset||insidecolor.offset==null){insidecolor.offset=fs.offset;insidecolor.color=fs.color;}if(fs.offset<outsidecolor.offset||outsidecolor.offset==null){outsidecolor.offset=fs.offset;outsidecolor.color=fs.color;}}colors.pop();lineStr.push('<g_vml_:fill',' color="',outsidecolor.color,'"',' color2="',insidecolor.color,'"',' type="',this.fillStyle.type_,'"',' focusposition="',focus.x,', ',focus.y,'"',' colors="',colors.join(""),'"',' opacity="',opacity,'" />');}elseif(aFill){lineStr.push('<g_vml_:fill color="',color,'" opacity="',opacity,'" />');}else{lineStr.push('<g_vml_:stroke',' opacity="',opacity,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',processLineCap(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',color,'" />');}lineStr.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",lineStr.join(""));//this.currentPath_ = [];};contextPrototype.fill=function(){this.stroke(true);};contextPrototype.closePath=function(){this.currentPath_.push({type:"close"});};/** * @private */contextPrototype.getCoords_=function(aX,aY){return{x:Z*(aX*this.m_[0][0]+aY*this.m_[1][0]+this.m_[2][0])-Z2,y:Z*(aX*this.m_[0][1]+aY*this.m_[1][1]+this.m_[2][1])-Z2}};contextPrototype.save=function(){varo={};copyState(this,o);this.aStack_.push(o);this.mStack_.push(this.m_);this.m_=matrixMultiply(createMatrixIdentity(),this.m_);};contextPrototype.restore=function(){copyState(this.aStack_.pop(),this);this.m_=this.mStack_.pop();};contextPrototype.translate=function(aX,aY){varm1=[[1,0,0],[0,1,0],[aX,aY,1]];this.m_=matrixMultiply(m1,this.m_);};contextPrototype.rotate=function(aRot){varc=mc(aRot);vars=ms(aRot);varm1=[[c,s,0],[-s,c,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_);};contextPrototype.scale=function(aX,aY){this.arcScaleX_*=aX;this.arcScaleY_*=aY;varm1=[[aX,0,0],[0,aY,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_);};/******** STUBS ********/contextPrototype.clip=function(){// TODO: Implement};contextPrototype.arcTo=function(){// TODO: Implement};contextPrototype.createPattern=function(){returnnewCanvasPattern_;};// Gradient / Pattern StubsfunctionCanvasGradient_(aType){this.type_=aType;this.radius1_=0;this.radius2_=0;this.colors_=[];this.focus_={x:0,y:0};}CanvasGradient_.prototype.addColorStop=function(aOffset,aColor){aColor=processStyle(aColor);this.colors_.push({offset:1-aOffset,color:aColor});};functionCanvasPattern_(){}// set up externsG_vmlCanvasManager=G_vmlCanvasManager_;CanvasRenderingContext2D=CanvasRenderingContext2D_;CanvasGradient=CanvasGradient_;CanvasPattern=CanvasPattern_;})();}// if