jquery.excanvas.js
上传用户:stephen_wu
上传日期:2008-07-05
资源大小:1757k
文件大小:23k
源码类别:

网络

开发平台:

Unix_Linux

  1. // Copyright 2006 Google Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. //   http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Known Issues:
  15. //
  16. // * Patterns are not implemented.
  17. // * Radial gradient are not implemented. The VML version of these look very
  18. //   different from the canvas one.
  19. // * Clipping paths are not implemented.
  20. // * Coordsize. The width and height attribute have higher priority than the
  21. //   width and height style values which isn't correct.
  22. // * Painting mode isn't implemented.
  23. // * Canvas width/height should is using content-box by default. IE in
  24. //   Quirks mode will draw the canvas using border-box. Either change your
  25. //   doctype to HTML5
  26. //   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  27. //   or use Box Sizing Behavior from WebFX
  28. //   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  29. // * Optimize. There is always room for speed improvements.
  30. // only add this code if we do not already have a canvas implementation
  31. if (!window.CanvasRenderingContext2D) {
  32. (function () {
  33.   // alias some functions to make (compiled) code shorter
  34.   var m = Math;
  35.   var mr = m.round;
  36.   var ms = m.sin;
  37.   var mc = m.cos;
  38.   // this is used for sub pixel precision
  39.   var Z = 10;
  40.   var Z2 = Z / 2;
  41.   var G_vmlCanvasManager_ = {
  42.     init: function (opt_doc) {
  43.       var doc = opt_doc || document;
  44.       if (/MSIE/.test(navigator.userAgent) && !window.opera) {
  45.         var self = this;
  46.         doc.attachEvent("onreadystatechange", function () {
  47.           self.init_(doc);
  48.         });
  49.       }
  50.     },
  51.     init_: function (doc) {
  52.       if (doc.readyState == "complete") {
  53.         // create xmlns
  54.         if (!doc.namespaces["g_vml_"]) {
  55.           doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
  56.         }
  57.         // setup default css
  58.         var ss = doc.createStyleSheet();
  59.         ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
  60.             // default size is 300x150 in Gecko and Opera
  61.             "text-align:left;width:300px;height:150px}" +
  62.             "g_vml_\:*{behavior:url(#default#VML)}";
  63.         // find all canvas elements
  64.         var els = doc.getElementsByTagName("canvas");
  65.         for (var i = 0; i < els.length; i++) {
  66.           if (!els[i].getContext) {
  67.             this.initElement(els[i]);
  68.           }
  69.         }
  70.       }
  71.     },
  72.     fixElement_: function (el) {
  73.       // in IE before version 5.5 we would need to add HTML: to the tag name
  74.       // but we do not care about IE before version 6
  75.       var outerHTML = el.outerHTML;
  76.       var newEl = el.ownerDocument.createElement(outerHTML);
  77.       // if the tag is still open IE has created the children as siblings and
  78.       // it has also created a tag with the name "/FOO"
  79.       if (outerHTML.slice(-2) != "/>") {
  80.         var tagName = "/" + el.tagName;
  81.         var ns;
  82.         // remove content
  83.         while ((ns = el.nextSibling) && ns.tagName != tagName) {
  84.           ns.removeNode();
  85.         }
  86.         // remove the incorrect closing tag
  87.         if (ns) {
  88.           ns.removeNode();
  89.         }
  90.       }
  91.       el.parentNode.replaceChild(newEl, el);
  92.       return newEl;
  93.     },
  94.     /**
  95.      * Public initializes a canvas element so that it can be used as canvas
  96.      * element from now on. This is called automatically before the page is
  97.      * loaded but if you are creating elements using createElement you need to
  98.      * make sure this is called on the element.
  99.      * @param {HTMLElement} el The canvas element to initialize.
  100.      * @return {HTMLElement} the element that was created.
  101.      */
  102.     initElement: function (el) {
  103.       el = this.fixElement_(el);
  104.       el.getContext = function () {
  105.         if (this.context_) {
  106.           return this.context_;
  107.         }
  108.         return this.context_ = new CanvasRenderingContext2D_(this);
  109.       };
  110.       // do not use inline function because that will leak memory
  111.       el.attachEvent('onpropertychange', onPropertyChange);
  112.       el.attachEvent('onresize', onResize);
  113.       var attrs = el.attributes;
  114.       if (attrs.width && attrs.width.specified) {
  115.         // TODO: use runtimeStyle and coordsize
  116.         // el.getContext().setWidth_(attrs.width.nodeValue);
  117.         el.style.width = attrs.width.nodeValue + "px";
  118.       } else {
  119.         el.width = el.clientWidth;
  120.       }
  121.       if (attrs.height && attrs.height.specified) {
  122.         // TODO: use runtimeStyle and coordsize
  123.         // el.getContext().setHeight_(attrs.height.nodeValue);
  124.         el.style.height = attrs.height.nodeValue + "px";
  125.       } else {
  126.         el.height = el.clientHeight;
  127.       }
  128.       //el.getContext().setCoordsize_()
  129.       return el;
  130.     }
  131.   };
  132.   function onPropertyChange(e) {
  133.     var el = e.srcElement;
  134.     switch (e.propertyName) {
  135.       case 'width':
  136.         el.style.width = el.attributes.width.nodeValue + "px";
  137.         el.getContext().clearRect();
  138.         break;
  139.       case 'height':
  140.         el.style.height = el.attributes.height.nodeValue + "px";
  141.         el.getContext().clearRect();
  142.         break;
  143.     }
  144.   }
  145.   function onResize(e) {
  146.     var el = e.srcElement;
  147.     if (el.firstChild) {
  148.       el.firstChild.style.width =  el.clientWidth + 'px';
  149.       el.firstChild.style.height = el.clientHeight + 'px';
  150.     }
  151.   }
  152.   G_vmlCanvasManager_.init();
  153.   // precompute "00" to "FF"
  154.   var dec2hex = [];
  155.   for (var i = 0; i < 16; i++) {
  156.     for (var j = 0; j < 16; j++) {
  157.       dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
  158.     }
  159.   }
  160.   function createMatrixIdentity() {
  161.     return [
  162.       [1, 0, 0],
  163.       [0, 1, 0],
  164.       [0, 0, 1]
  165.     ];
  166.   }
  167.   function matrixMultiply(m1, m2) {
  168.     var result = createMatrixIdentity();
  169.     for (var x = 0; x < 3; x++) {
  170.       for (var y = 0; y < 3; y++) {
  171.         var sum = 0;
  172.         for (var z = 0; z < 3; z++) {
  173.           sum += m1[x][z] * m2[z][y];
  174.         }
  175.         result[x][y] = sum;
  176.       }
  177.     }
  178.     return result;
  179.   }
  180.   function copyState(o1, o2) {
  181.     o2.fillStyle     = o1.fillStyle;
  182.     o2.lineCap       = o1.lineCap;
  183.     o2.lineJoin      = o1.lineJoin;
  184.     o2.lineWidth     = o1.lineWidth;
  185.     o2.miterLimit    = o1.miterLimit;
  186.     o2.shadowBlur    = o1.shadowBlur;
  187.     o2.shadowColor   = o1.shadowColor;
  188.     o2.shadowOffsetX = o1.shadowOffsetX;
  189.     o2.shadowOffsetY = o1.shadowOffsetY;
  190.     o2.strokeStyle   = o1.strokeStyle;
  191.     o2.arcScaleX_    = o1.arcScaleX_;
  192.     o2.arcScaleY_    = o1.arcScaleY_;
  193.   }
  194.   function processStyle(styleString) {
  195.     var str, alpha = 1;
  196.     styleString = String(styleString);
  197.     if (styleString.substring(0, 3) == "rgb") {
  198.       var start = styleString.indexOf("(", 3);
  199.       var end = styleString.indexOf(")", start + 1);
  200.       var guts = styleString.substring(start + 1, end).split(",");
  201.       str = "#";
  202.       for (var i = 0; i < 3; i++) {
  203.         str += dec2hex[Number(guts[i])];
  204.       }
  205.       if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
  206.         alpha = guts[3];
  207.       }
  208.     } else {
  209.       str = styleString;
  210.     }
  211.     return [str, alpha];
  212.   }
  213.   function processLineCap(lineCap) {
  214.     switch (lineCap) {
  215.       case "butt":
  216.         return "flat";
  217.       case "round":
  218.         return "round";
  219.       case "square":
  220.       default:
  221.         return "square";
  222.     }
  223.   }
  224.   /**
  225.    * This class implements CanvasRenderingContext2D interface as described by
  226.    * the WHATWG.
  227.    * @param {HTMLElement} surfaceElement The element that the 2D context should
  228.    * be associated with
  229.    */
  230.    function CanvasRenderingContext2D_(surfaceElement) {
  231.     this.m_ = createMatrixIdentity();
  232.     this.mStack_ = [];
  233.     this.aStack_ = [];
  234.     this.currentPath_ = [];
  235.     // Canvas context properties
  236.     this.strokeStyle = "#000";
  237.     this.fillStyle = "#000";
  238.     this.lineWidth = 1;
  239.     this.lineJoin = "miter";
  240.     this.lineCap = "butt";
  241.     this.miterLimit = Z * 1;
  242.     this.globalAlpha = 1;
  243.     this.canvas = surfaceElement;
  244.     var el = surfaceElement.ownerDocument.createElement('div');
  245.     el.style.width =  surfaceElement.clientWidth + 'px';
  246.     el.style.height = surfaceElement.clientHeight + 'px';
  247.     el.style.overflow = 'hidden';
  248.     el.style.position = 'absolute';
  249.     surfaceElement.appendChild(el);
  250.     this.element_ = el;
  251.     this.arcScaleX_ = 1;
  252.     this.arcScaleY_ = 1;
  253.   }
  254.   var contextPrototype = CanvasRenderingContext2D_.prototype;
  255.   contextPrototype.clearRect = function() {
  256.     this.element_.innerHTML = "";
  257.     this.currentPath_ = [];
  258.   };
  259.   contextPrototype.beginPath = function() {
  260.     // TODO: Branch current matrix so that save/restore has no effect
  261.     //       as per safari docs.
  262.     this.currentPath_ = [];
  263.   };
  264.   contextPrototype.moveTo = function(aX, aY) {
  265.     this.currentPath_.push({type: "moveTo", x: aX, y: aY});
  266.     this.currentX_ = aX;
  267.     this.currentY_ = aY;
  268.   };
  269.   contextPrototype.lineTo = function(aX, aY) {
  270.     this.currentPath_.push({type: "lineTo", x: aX, y: aY});
  271.     this.currentX_ = aX;
  272.     this.currentY_ = aY;
  273.   };
  274.   contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
  275.                                             aCP2x, aCP2y,
  276.                                             aX, aY) {
  277.     this.currentPath_.push({type: "bezierCurveTo",
  278.                            cp1x: aCP1x,
  279.                            cp1y: aCP1y,
  280.                            cp2x: aCP2x,
  281.                            cp2y: aCP2y,
  282.                            x: aX,
  283.                            y: aY});
  284.     this.currentX_ = aX;
  285.     this.currentY_ = aY;
  286.   };
  287.   contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
  288.     // the following is lifted almost directly from
  289.     // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  290.     var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
  291.     var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
  292.     var cp2x = cp1x + (aX - this.currentX_) / 3.0;
  293.     var cp2y = cp1y + (aY - this.currentY_) / 3.0;
  294.     this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
  295.   };
  296.   contextPrototype.arc = function(aX, aY, aRadius,
  297.                                   aStartAngle, aEndAngle, aClockwise) {
  298.     aRadius *= Z;
  299.     var arcType = aClockwise ? "at" : "wa";
  300.     var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
  301.     var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
  302.     var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
  303.     var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
  304.     // IE won't render arches drawn counter clockwise if xStart == xEnd.
  305.     if (xStart == xEnd && !aClockwise) {
  306.       xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
  307.                        // that can be represented in binary
  308.     }
  309.     this.currentPath_.push({type: arcType,
  310.                            x: aX,
  311.                            y: aY,
  312.                            radius: aRadius,
  313.                            xStart: xStart,
  314.                            yStart: yStart,
  315.                            xEnd: xEnd,
  316.                            yEnd: yEnd});
  317.   };
  318.   contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
  319.     this.moveTo(aX, aY);
  320.     this.lineTo(aX + aWidth, aY);
  321.     this.lineTo(aX + aWidth, aY + aHeight);
  322.     this.lineTo(aX, aY + aHeight);
  323.     this.closePath();
  324.   };
  325.   contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
  326.     // Will destroy any existing path (same as FF behaviour)
  327.     this.beginPath();
  328.     this.moveTo(aX, aY);
  329.     this.lineTo(aX + aWidth, aY);
  330.     this.lineTo(aX + aWidth, aY + aHeight);
  331.     this.lineTo(aX, aY + aHeight);
  332.     this.closePath();
  333.     this.stroke();
  334.   };
  335.   contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
  336.     // Will destroy any existing path (same as FF behaviour)
  337.     this.beginPath();
  338.     this.moveTo(aX, aY);
  339.     this.lineTo(aX + aWidth, aY);
  340.     this.lineTo(aX + aWidth, aY + aHeight);
  341.     this.lineTo(aX, aY + aHeight);
  342.     this.closePath();
  343.     this.fill();
  344.   };
  345.   contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
  346.     var gradient = new CanvasGradient_("gradient");
  347.     return gradient;
  348.   };
  349.   contextPrototype.createRadialGradient = function(aX0, aY0,
  350.                                                    aR0, aX1,
  351.                                                    aY1, aR1) {
  352.     var gradient = new CanvasGradient_("gradientradial");
  353.     gradient.radius1_ = aR0;
  354.     gradient.radius2_ = aR1;
  355.     gradient.focus_.x = aX0;
  356.     gradient.focus_.y = aY0;
  357.     return gradient;
  358.   };
  359.   contextPrototype.drawImage = function (image, var_args) {
  360.     var dx, dy, dw, dh, sx, sy, sw, sh;
  361.     // to find the original width we overide the width and height
  362.     var oldRuntimeWidth = image.runtimeStyle.width;
  363.     var oldRuntimeHeight = image.runtimeStyle.height;
  364.     image.runtimeStyle.width = 'auto';
  365.     image.runtimeStyle.height = 'auto';
  366.     // get the original size
  367.     var w = image.width;
  368.     var h = image.height;
  369.     // and remove overides
  370.     image.runtimeStyle.width = oldRuntimeWidth;
  371.     image.runtimeStyle.height = oldRuntimeHeight;
  372.     if (arguments.length == 3) {
  373.       dx = arguments[1];
  374.       dy = arguments[2];
  375.       sx = sy = 0;
  376.       sw = dw = w;
  377.       sh = dh = h;
  378.     } else if (arguments.length == 5) {
  379.       dx = arguments[1];
  380.       dy = arguments[2];
  381.       dw = arguments[3];
  382.       dh = arguments[4];
  383.       sx = sy = 0;
  384.       sw = w;
  385.       sh = h;
  386.     } else if (arguments.length == 9) {
  387.       sx = arguments[1];
  388.       sy = arguments[2];
  389.       sw = arguments[3];
  390.       sh = arguments[4];
  391.       dx = arguments[5];
  392.       dy = arguments[6];
  393.       dw = arguments[7];
  394.       dh = arguments[8];
  395.     } else {
  396.       throw "Invalid number of arguments";
  397.     }
  398.     var d = this.getCoords_(dx, dy);
  399.     var w2 = sw / 2;
  400.     var h2 = sh / 2;
  401.     var vmlStr = [];
  402.     var W = 10;
  403.     var H = 10;
  404.     // For some reason that I've now forgotten, using divs didn't work
  405.     vmlStr.push(' <g_vml_:group',
  406.                 ' coordsize="', Z * W, ',', Z * H, '"',
  407.                 ' coordorigin="0,0"' ,
  408.                 ' style="width:', W, ';height:', H, ';position:absolute;');
  409.     // If filters are necessary (rotation exists), create them
  410.     // filters are bog-slow, so only create them if abbsolutely necessary
  411.     // The following check doesn't account for skews (which don't exist
  412.     // in the canvas spec (yet) anyway.
  413.     if (this.m_[0][0] != 1 || this.m_[0][1]) {
  414.       var filter = [];
  415.       // Note the 12/21 reversal
  416.       filter.push("M11='", this.m_[0][0], "',",
  417.                   "M12='", this.m_[1][0], "',",
  418.                   "M21='", this.m_[0][1], "',",
  419.                   "M22='", this.m_[1][1], "',",
  420.                   "Dx='", mr(d.x / Z), "',",
  421.                   "Dy='", mr(d.y / Z), "'");
  422.       // Bounding box calculation (need to minimize displayed area so that
  423.       // filters don't waste time on unused pixels.
  424.       var max = d;
  425.       var c2 = this.getCoords_(dx + dw, dy);
  426.       var c3 = this.getCoords_(dx, dy + dh);
  427.       var c4 = this.getCoords_(dx + dw, dy + dh);
  428.       max.x = Math.max(max.x, c2.x, c3.x, c4.x);
  429.       max.y = Math.max(max.y, c2.y, c3.y, c4.y);
  430.       vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
  431.                   "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
  432.                   filter.join(""), ", sizingmethod='clip');");
  433.     } else {
  434.       vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;");
  435.     }
  436.     vmlStr.push(' ">' ,
  437.                 '<g_vml_:image src="', image.src, '"',
  438.                 ' style="width:', Z * dw, ';',
  439.                 ' height:', Z * dh, ';"',
  440.                 ' cropleft="', sx / w, '"',
  441.                 ' croptop="', sy / h, '"',
  442.                 ' cropright="', (w - sx - sw) / w, '"',
  443.                 ' cropbottom="', (h - sy - sh) / h, '"',
  444.                 ' />',
  445.                 '</g_vml_:group>');
  446.     this.element_.insertAdjacentHTML("BeforeEnd",
  447.                                     vmlStr.join(""));
  448.   };
  449.   contextPrototype.stroke = function(aFill) {
  450.     var lineStr = [];
  451.     var lineOpen = false;
  452.     var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
  453.     var color = a[0];
  454.     var opacity = a[1] * this.globalAlpha;
  455.     var W = 10;
  456.     var H = 10;
  457.     lineStr.push('<g_vml_:shape',
  458.                  ' fillcolor="', color, '"',
  459.                  ' filled="', Boolean(aFill), '"',
  460.                  ' style="position:absolute;width:', W, ';height:', H, ';"',
  461.                  ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
  462.                  ' stroked="', !aFill, '"',
  463.                  ' strokeweight="', this.lineWidth, '"',
  464.                  ' strokecolor="', color, '"',
  465.                  ' path="');
  466.     var newSeq = false;
  467.     var min = {x: null, y: null};
  468.     var max = {x: null, y: null};
  469.     for (var i = 0; i < this.currentPath_.length; i++) {
  470.       var p = this.currentPath_[i];
  471.       if (p.type == "moveTo") {
  472.         lineStr.push(" m ");
  473.         var c = this.getCoords_(p.x, p.y);
  474.         lineStr.push(mr(c.x), ",", mr(c.y));
  475.       } else if (p.type == "lineTo") {
  476.         lineStr.push(" l ");
  477.         var c = this.getCoords_(p.x, p.y);
  478.         lineStr.push(mr(c.x), ",", mr(c.y));
  479.       } else if (p.type == "close") {
  480.         lineStr.push(" x ");
  481.       } else if (p.type == "bezierCurveTo") {
  482.         lineStr.push(" c ");
  483.         var c = this.getCoords_(p.x, p.y);
  484.         var c1 = this.getCoords_(p.cp1x, p.cp1y);
  485.         var c2 = this.getCoords_(p.cp2x, p.cp2y);
  486.         lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
  487.                      mr(c2.x), ",", mr(c2.y), ",",
  488.                      mr(c.x), ",", mr(c.y));
  489.       } else if (p.type == "at" || p.type == "wa") {
  490.         lineStr.push(" ", p.type, " ");
  491.         var c  = this.getCoords_(p.x, p.y);
  492.         var cStart = this.getCoords_(p.xStart, p.yStart);
  493.         var cEnd = this.getCoords_(p.xEnd, p.yEnd);
  494.         lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
  495.                      mr(c.y - this.arcScaleY_ * p.radius), " ",
  496.                      mr(c.x + this.arcScaleX_ * p.radius), ",",
  497.                      mr(c.y + this.arcScaleY_ * p.radius), " ",
  498.                      mr(cStart.x), ",", mr(cStart.y), " ",
  499.                      mr(cEnd.x), ",", mr(cEnd.y));
  500.       }
  501.       // TODO: Following is broken for curves due to
  502.       //       move to proper paths.
  503.       // Figure out dimensions so we can do gradient fills
  504.       // properly
  505.       if(c) {
  506.         if (min.x == null || c.x < min.x) {
  507.           min.x = c.x;
  508.         }
  509.         if (max.x == null || c.x > max.x) {
  510.           max.x = c.x;
  511.         }
  512.         if (min.y == null || c.y < min.y) {
  513.           min.y = c.y;
  514.         }
  515.         if (max.y == null || c.y > max.y) {
  516.           max.y = c.y;
  517.         }
  518.       }
  519.     }
  520.     lineStr.push(' ">');
  521.     if (typeof this.fillStyle == "object") {
  522.       var focus = {x: "50%", y: "50%"};
  523.       var width = (max.x - min.x);
  524.       var height = (max.y - min.y);
  525.       var dimension = (width > height) ? width : height;
  526.       focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
  527.       focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
  528.       var colors = [];
  529.       // inside radius (%)
  530.       if (this.fillStyle.type_ == "gradientradial") {
  531.         var inside = (this.fillStyle.radius1_ / dimension * 100);
  532.         // percentage that outside radius exceeds inside radius
  533.         var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
  534.       } else {
  535.         var inside = 0;
  536.         var expansion = 100;
  537.       }
  538.       var insidecolor = {offset: null, color: null};
  539.       var outsidecolor = {offset: null, color: null};
  540.       // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
  541.       // won't interpret it correctly
  542.       this.fillStyle.colors_.sort(function (cs1, cs2) {
  543.         return cs1.offset - cs2.offset;
  544.       });
  545.       for (var i = 0; i < this.fillStyle.colors_.length; i++) {
  546.         var fs = this.fillStyle.colors_[i];
  547.         colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
  548.         if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
  549.           insidecolor.offset = fs.offset;
  550.           insidecolor.color = fs.color;
  551.         }
  552.         if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
  553.           outsidecolor.offset = fs.offset;
  554.           outsidecolor.color = fs.color;
  555.         }
  556.       }
  557.       colors.pop();
  558.       lineStr.push('<g_vml_:fill',
  559.                    ' color="', outsidecolor.color, '"',
  560.                    ' color2="', insidecolor.color, '"',
  561.                    ' type="', this.fillStyle.type_, '"',
  562.                    ' focusposition="', focus.x, ', ', focus.y, '"',
  563.                    ' colors="', colors.join(""), '"',
  564.                    ' opacity="', opacity, '" />');
  565.     } else if (aFill) {
  566.       lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
  567.     } else {
  568.       lineStr.push(
  569.         '<g_vml_:stroke',
  570.         ' opacity="', opacity,'"',
  571.         ' joinstyle="', this.lineJoin, '"',
  572.         ' miterlimit="', this.miterLimit, '"',
  573.         ' endcap="', processLineCap(this.lineCap) ,'"',
  574.         ' weight="', this.lineWidth, 'px"',
  575.         ' color="', color,'" />'
  576.       );
  577.     }
  578.     lineStr.push("</g_vml_:shape>");
  579.     this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
  580.     //this.currentPath_ = [];
  581.   };
  582.   contextPrototype.fill = function() {
  583.     this.stroke(true);
  584.   };
  585.   contextPrototype.closePath = function() {
  586.     this.currentPath_.push({type: "close"});
  587.   };
  588.   /**
  589.    * @private
  590.    */
  591.   contextPrototype.getCoords_ = function(aX, aY) {
  592.     return {
  593.       x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
  594.       y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
  595.     }
  596.   };
  597.   contextPrototype.save = function() {
  598.     var o = {};
  599.     copyState(this, o);
  600.     this.aStack_.push(o);
  601.     this.mStack_.push(this.m_);
  602.     this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
  603.   };
  604.   contextPrototype.restore = function() {
  605.     copyState(this.aStack_.pop(), this);
  606.     this.m_ = this.mStack_.pop();
  607.   };
  608.   contextPrototype.translate = function(aX, aY) {
  609.     var m1 = [
  610.       [1,  0,  0],
  611.       [0,  1,  0],
  612.       [aX, aY, 1]
  613.     ];
  614.     this.m_ = matrixMultiply(m1, this.m_);
  615.   };
  616.   contextPrototype.rotate = function(aRot) {
  617.     var c = mc(aRot);
  618.     var s = ms(aRot);
  619.     var m1 = [
  620.       [c,  s, 0],
  621.       [-s, c, 0],
  622.       [0,  0, 1]
  623.     ];
  624.     this.m_ = matrixMultiply(m1, this.m_);
  625.   };
  626.   contextPrototype.scale = function(aX, aY) {
  627.     this.arcScaleX_ *= aX;
  628.     this.arcScaleY_ *= aY;
  629.     var m1 = [
  630.       [aX, 0,  0],
  631.       [0,  aY, 0],
  632.       [0,  0,  1]
  633.     ];
  634.     this.m_ = matrixMultiply(m1, this.m_);
  635.   };
  636.   /******** STUBS ********/
  637.   contextPrototype.clip = function() {
  638.     // TODO: Implement
  639.   };
  640.   contextPrototype.arcTo = function() {
  641.     // TODO: Implement
  642.   };
  643.   contextPrototype.createPattern = function() {
  644.     return new CanvasPattern_;
  645.   };
  646.   // Gradient / Pattern Stubs
  647.   function CanvasGradient_(aType) {
  648.     this.type_ = aType;
  649.     this.radius1_ = 0;
  650.     this.radius2_ = 0;
  651.     this.colors_ = [];
  652.     this.focus_ = {x: 0, y: 0};
  653.   }
  654.   CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
  655.     aColor = processStyle(aColor);
  656.     this.colors_.push({offset: 1-aOffset, color: aColor});
  657.   };
  658.   function CanvasPattern_() {}
  659.   // set up externs
  660.   G_vmlCanvasManager = G_vmlCanvasManager_;
  661.   CanvasRenderingContext2D = CanvasRenderingContext2D_;
  662.   CanvasGradient = CanvasGradient_;
  663.   CanvasPattern = CanvasPattern_;
  664. })();
  665. } // if