src/pyams_skin/resources/js/ext/flot/excanvas.js
changeset 98 89a28618a327
equal deleted inserted replaced
97:8337e31c9edb 98:89a28618a327
       
     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 
       
    15 
       
    16 // Known Issues:
       
    17 //
       
    18 // * Patterns only support repeat.
       
    19 // * Radial gradient are not implemented. The VML version of these look very
       
    20 //   different from the canvas one.
       
    21 // * Clipping paths are not implemented.
       
    22 // * Coordsize. The width and height attribute have higher priority than the
       
    23 //   width and height style values which isn't correct.
       
    24 // * Painting mode isn't implemented.
       
    25 // * Canvas width/height should is using content-box by default. IE in
       
    26 //   Quirks mode will draw the canvas using border-box. Either change your
       
    27 //   doctype to HTML5
       
    28 //   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
       
    29 //   or use Box Sizing Behavior from WebFX
       
    30 //   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
       
    31 // * Non uniform scaling does not correctly scale strokes.
       
    32 // * Filling very large shapes (above 5000 points) is buggy.
       
    33 // * Optimize. There is always room for speed improvements.
       
    34 
       
    35 // Only add this code if we do not already have a canvas implementation
       
    36 if (!document.createElement('canvas').getContext) {
       
    37 
       
    38 (function() {
       
    39 
       
    40   // alias some functions to make (compiled) code shorter
       
    41   var m = Math;
       
    42   var mr = m.round;
       
    43   var ms = m.sin;
       
    44   var mc = m.cos;
       
    45   var abs = m.abs;
       
    46   var sqrt = m.sqrt;
       
    47 
       
    48   // this is used for sub pixel precision
       
    49   var Z = 10;
       
    50   var Z2 = Z / 2;
       
    51 
       
    52   var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
       
    53 
       
    54   /**
       
    55    * This funtion is assigned to the <canvas> elements as element.getContext().
       
    56    * @this {HTMLElement}
       
    57    * @return {CanvasRenderingContext2D_}
       
    58    */
       
    59   function getContext() {
       
    60     return this.context_ ||
       
    61         (this.context_ = new CanvasRenderingContext2D_(this));
       
    62   }
       
    63 
       
    64   var slice = Array.prototype.slice;
       
    65 
       
    66   /**
       
    67    * Binds a function to an object. The returned function will always use the
       
    68    * passed in {@code obj} as {@code this}.
       
    69    *
       
    70    * Example:
       
    71    *
       
    72    *   g = bind(f, obj, a, b)
       
    73    *   g(c, d) // will do f.call(obj, a, b, c, d)
       
    74    *
       
    75    * @param {Function} f The function to bind the object to
       
    76    * @param {Object} obj The object that should act as this when the function
       
    77    *     is called
       
    78    * @param {*} var_args Rest arguments that will be used as the initial
       
    79    *     arguments when the function is called
       
    80    * @return {Function} A new function that has bound this
       
    81    */
       
    82   function bind(f, obj, var_args) {
       
    83     var a = slice.call(arguments, 2);
       
    84     return function() {
       
    85       return f.apply(obj, a.concat(slice.call(arguments)));
       
    86     };
       
    87   }
       
    88 
       
    89   function encodeHtmlAttribute(s) {
       
    90     return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
       
    91   }
       
    92 
       
    93   function addNamespace(doc, prefix, urn) {
       
    94     if (!doc.namespaces[prefix]) {
       
    95       doc.namespaces.add(prefix, urn, '#default#VML');
       
    96     }
       
    97   }
       
    98 
       
    99   function addNamespacesAndStylesheet(doc) {
       
   100     addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
       
   101     addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
       
   102 
       
   103     // Setup default CSS.  Only add one style sheet per document
       
   104     if (!doc.styleSheets['ex_canvas_']) {
       
   105       var ss = doc.createStyleSheet();
       
   106       ss.owningElement.id = 'ex_canvas_';
       
   107       ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
       
   108           // default size is 300x150 in Gecko and Opera
       
   109           'text-align:left;width:300px;height:150px}';
       
   110     }
       
   111   }
       
   112 
       
   113   // Add namespaces and stylesheet at startup.
       
   114   addNamespacesAndStylesheet(document);
       
   115 
       
   116   var G_vmlCanvasManager_ = {
       
   117     init: function(opt_doc) {
       
   118       var doc = opt_doc || document;
       
   119       // Create a dummy element so that IE will allow canvas elements to be
       
   120       // recognized.
       
   121       doc.createElement('canvas');
       
   122       doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
       
   123     },
       
   124 
       
   125     init_: function(doc) {
       
   126       // find all canvas elements
       
   127       var els = doc.getElementsByTagName('canvas');
       
   128       for (var i = 0; i < els.length; i++) {
       
   129         this.initElement(els[i]);
       
   130       }
       
   131     },
       
   132 
       
   133     /**
       
   134      * Public initializes a canvas element so that it can be used as canvas
       
   135      * element from now on. This is called automatically before the page is
       
   136      * loaded but if you are creating elements using createElement you need to
       
   137      * make sure this is called on the element.
       
   138      * @param {HTMLElement} el The canvas element to initialize.
       
   139      * @return {HTMLElement} the element that was created.
       
   140      */
       
   141     initElement: function(el) {
       
   142       if (!el.getContext) {
       
   143         el.getContext = getContext;
       
   144 
       
   145         // Add namespaces and stylesheet to document of the element.
       
   146         addNamespacesAndStylesheet(el.ownerDocument);
       
   147 
       
   148         // Remove fallback content. There is no way to hide text nodes so we
       
   149         // just remove all childNodes. We could hide all elements and remove
       
   150         // text nodes but who really cares about the fallback content.
       
   151         el.innerHTML = '';
       
   152 
       
   153         // do not use inline function because that will leak memory
       
   154         el.attachEvent('onpropertychange', onPropertyChange);
       
   155         el.attachEvent('onresize', onResize);
       
   156 
       
   157         var attrs = el.attributes;
       
   158         if (attrs.width && attrs.width.specified) {
       
   159           // TODO: use runtimeStyle and coordsize
       
   160           // el.getContext().setWidth_(attrs.width.nodeValue);
       
   161           el.style.width = attrs.width.nodeValue + 'px';
       
   162         } else {
       
   163           el.width = el.clientWidth;
       
   164         }
       
   165         if (attrs.height && attrs.height.specified) {
       
   166           // TODO: use runtimeStyle and coordsize
       
   167           // el.getContext().setHeight_(attrs.height.nodeValue);
       
   168           el.style.height = attrs.height.nodeValue + 'px';
       
   169         } else {
       
   170           el.height = el.clientHeight;
       
   171         }
       
   172         //el.getContext().setCoordsize_()
       
   173       }
       
   174       return el;
       
   175     }
       
   176   };
       
   177 
       
   178   function onPropertyChange(e) {
       
   179     var el = e.srcElement;
       
   180 
       
   181     switch (e.propertyName) {
       
   182       case 'width':
       
   183         el.getContext().clearRect();
       
   184         el.style.width = el.attributes.width.nodeValue + 'px';
       
   185         // In IE8 this does not trigger onresize.
       
   186         el.firstChild.style.width =  el.clientWidth + 'px';
       
   187         break;
       
   188       case 'height':
       
   189         el.getContext().clearRect();
       
   190         el.style.height = el.attributes.height.nodeValue + 'px';
       
   191         el.firstChild.style.height = el.clientHeight + 'px';
       
   192         break;
       
   193     }
       
   194   }
       
   195 
       
   196   function onResize(e) {
       
   197     var el = e.srcElement;
       
   198     if (el.firstChild) {
       
   199       el.firstChild.style.width =  el.clientWidth + 'px';
       
   200       el.firstChild.style.height = el.clientHeight + 'px';
       
   201     }
       
   202   }
       
   203 
       
   204   G_vmlCanvasManager_.init();
       
   205 
       
   206   // precompute "00" to "FF"
       
   207   var decToHex = [];
       
   208   for (var i = 0; i < 16; i++) {
       
   209     for (var j = 0; j < 16; j++) {
       
   210       decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
       
   211     }
       
   212   }
       
   213 
       
   214   function createMatrixIdentity() {
       
   215     return [
       
   216       [1, 0, 0],
       
   217       [0, 1, 0],
       
   218       [0, 0, 1]
       
   219     ];
       
   220   }
       
   221 
       
   222   function matrixMultiply(m1, m2) {
       
   223     var result = createMatrixIdentity();
       
   224 
       
   225     for (var x = 0; x < 3; x++) {
       
   226       for (var y = 0; y < 3; y++) {
       
   227         var sum = 0;
       
   228 
       
   229         for (var z = 0; z < 3; z++) {
       
   230           sum += m1[x][z] * m2[z][y];
       
   231         }
       
   232 
       
   233         result[x][y] = sum;
       
   234       }
       
   235     }
       
   236     return result;
       
   237   }
       
   238 
       
   239   function copyState(o1, o2) {
       
   240     o2.fillStyle     = o1.fillStyle;
       
   241     o2.lineCap       = o1.lineCap;
       
   242     o2.lineJoin      = o1.lineJoin;
       
   243     o2.lineWidth     = o1.lineWidth;
       
   244     o2.miterLimit    = o1.miterLimit;
       
   245     o2.shadowBlur    = o1.shadowBlur;
       
   246     o2.shadowColor   = o1.shadowColor;
       
   247     o2.shadowOffsetX = o1.shadowOffsetX;
       
   248     o2.shadowOffsetY = o1.shadowOffsetY;
       
   249     o2.strokeStyle   = o1.strokeStyle;
       
   250     o2.globalAlpha   = o1.globalAlpha;
       
   251     o2.font          = o1.font;
       
   252     o2.textAlign     = o1.textAlign;
       
   253     o2.textBaseline  = o1.textBaseline;
       
   254     o2.arcScaleX_    = o1.arcScaleX_;
       
   255     o2.arcScaleY_    = o1.arcScaleY_;
       
   256     o2.lineScale_    = o1.lineScale_;
       
   257   }
       
   258 
       
   259   var colorData = {
       
   260     aliceblue: '#F0F8FF',
       
   261     antiquewhite: '#FAEBD7',
       
   262     aquamarine: '#7FFFD4',
       
   263     azure: '#F0FFFF',
       
   264     beige: '#F5F5DC',
       
   265     bisque: '#FFE4C4',
       
   266     black: '#000000',
       
   267     blanchedalmond: '#FFEBCD',
       
   268     blueviolet: '#8A2BE2',
       
   269     brown: '#A52A2A',
       
   270     burlywood: '#DEB887',
       
   271     cadetblue: '#5F9EA0',
       
   272     chartreuse: '#7FFF00',
       
   273     chocolate: '#D2691E',
       
   274     coral: '#FF7F50',
       
   275     cornflowerblue: '#6495ED',
       
   276     cornsilk: '#FFF8DC',
       
   277     crimson: '#DC143C',
       
   278     cyan: '#00FFFF',
       
   279     darkblue: '#00008B',
       
   280     darkcyan: '#008B8B',
       
   281     darkgoldenrod: '#B8860B',
       
   282     darkgray: '#A9A9A9',
       
   283     darkgreen: '#006400',
       
   284     darkgrey: '#A9A9A9',
       
   285     darkkhaki: '#BDB76B',
       
   286     darkmagenta: '#8B008B',
       
   287     darkolivegreen: '#556B2F',
       
   288     darkorange: '#FF8C00',
       
   289     darkorchid: '#9932CC',
       
   290     darkred: '#8B0000',
       
   291     darksalmon: '#E9967A',
       
   292     darkseagreen: '#8FBC8F',
       
   293     darkslateblue: '#483D8B',
       
   294     darkslategray: '#2F4F4F',
       
   295     darkslategrey: '#2F4F4F',
       
   296     darkturquoise: '#00CED1',
       
   297     darkviolet: '#9400D3',
       
   298     deeppink: '#FF1493',
       
   299     deepskyblue: '#00BFFF',
       
   300     dimgray: '#696969',
       
   301     dimgrey: '#696969',
       
   302     dodgerblue: '#1E90FF',
       
   303     firebrick: '#B22222',
       
   304     floralwhite: '#FFFAF0',
       
   305     forestgreen: '#228B22',
       
   306     gainsboro: '#DCDCDC',
       
   307     ghostwhite: '#F8F8FF',
       
   308     gold: '#FFD700',
       
   309     goldenrod: '#DAA520',
       
   310     grey: '#808080',
       
   311     greenyellow: '#ADFF2F',
       
   312     honeydew: '#F0FFF0',
       
   313     hotpink: '#FF69B4',
       
   314     indianred: '#CD5C5C',
       
   315     indigo: '#4B0082',
       
   316     ivory: '#FFFFF0',
       
   317     khaki: '#F0E68C',
       
   318     lavender: '#E6E6FA',
       
   319     lavenderblush: '#FFF0F5',
       
   320     lawngreen: '#7CFC00',
       
   321     lemonchiffon: '#FFFACD',
       
   322     lightblue: '#ADD8E6',
       
   323     lightcoral: '#F08080',
       
   324     lightcyan: '#E0FFFF',
       
   325     lightgoldenrodyellow: '#FAFAD2',
       
   326     lightgreen: '#90EE90',
       
   327     lightgrey: '#D3D3D3',
       
   328     lightpink: '#FFB6C1',
       
   329     lightsalmon: '#FFA07A',
       
   330     lightseagreen: '#20B2AA',
       
   331     lightskyblue: '#87CEFA',
       
   332     lightslategray: '#778899',
       
   333     lightslategrey: '#778899',
       
   334     lightsteelblue: '#B0C4DE',
       
   335     lightyellow: '#FFFFE0',
       
   336     limegreen: '#32CD32',
       
   337     linen: '#FAF0E6',
       
   338     magenta: '#FF00FF',
       
   339     mediumaquamarine: '#66CDAA',
       
   340     mediumblue: '#0000CD',
       
   341     mediumorchid: '#BA55D3',
       
   342     mediumpurple: '#9370DB',
       
   343     mediumseagreen: '#3CB371',
       
   344     mediumslateblue: '#7B68EE',
       
   345     mediumspringgreen: '#00FA9A',
       
   346     mediumturquoise: '#48D1CC',
       
   347     mediumvioletred: '#C71585',
       
   348     midnightblue: '#191970',
       
   349     mintcream: '#F5FFFA',
       
   350     mistyrose: '#FFE4E1',
       
   351     moccasin: '#FFE4B5',
       
   352     navajowhite: '#FFDEAD',
       
   353     oldlace: '#FDF5E6',
       
   354     olivedrab: '#6B8E23',
       
   355     orange: '#FFA500',
       
   356     orangered: '#FF4500',
       
   357     orchid: '#DA70D6',
       
   358     palegoldenrod: '#EEE8AA',
       
   359     palegreen: '#98FB98',
       
   360     paleturquoise: '#AFEEEE',
       
   361     palevioletred: '#DB7093',
       
   362     papayawhip: '#FFEFD5',
       
   363     peachpuff: '#FFDAB9',
       
   364     peru: '#CD853F',
       
   365     pink: '#FFC0CB',
       
   366     plum: '#DDA0DD',
       
   367     powderblue: '#B0E0E6',
       
   368     rosybrown: '#BC8F8F',
       
   369     royalblue: '#4169E1',
       
   370     saddlebrown: '#8B4513',
       
   371     salmon: '#FA8072',
       
   372     sandybrown: '#F4A460',
       
   373     seagreen: '#2E8B57',
       
   374     seashell: '#FFF5EE',
       
   375     sienna: '#A0522D',
       
   376     skyblue: '#87CEEB',
       
   377     slateblue: '#6A5ACD',
       
   378     slategray: '#708090',
       
   379     slategrey: '#708090',
       
   380     snow: '#FFFAFA',
       
   381     springgreen: '#00FF7F',
       
   382     steelblue: '#4682B4',
       
   383     tan: '#D2B48C',
       
   384     thistle: '#D8BFD8',
       
   385     tomato: '#FF6347',
       
   386     turquoise: '#40E0D0',
       
   387     violet: '#EE82EE',
       
   388     wheat: '#F5DEB3',
       
   389     whitesmoke: '#F5F5F5',
       
   390     yellowgreen: '#9ACD32'
       
   391   };
       
   392 
       
   393 
       
   394   function getRgbHslContent(styleString) {
       
   395     var start = styleString.indexOf('(', 3);
       
   396     var end = styleString.indexOf(')', start + 1);
       
   397     var parts = styleString.substring(start + 1, end).split(',');
       
   398     // add alpha if needed
       
   399     if (parts.length != 4 || styleString.charAt(3) != 'a') {
       
   400       parts[3] = 1;
       
   401     }
       
   402     return parts;
       
   403   }
       
   404 
       
   405   function percent(s) {
       
   406     return parseFloat(s) / 100;
       
   407   }
       
   408 
       
   409   function clamp(v, min, max) {
       
   410     return Math.min(max, Math.max(min, v));
       
   411   }
       
   412 
       
   413   function hslToRgb(parts){
       
   414     var r, g, b, h, s, l;
       
   415     h = parseFloat(parts[0]) / 360 % 360;
       
   416     if (h < 0)
       
   417       h++;
       
   418     s = clamp(percent(parts[1]), 0, 1);
       
   419     l = clamp(percent(parts[2]), 0, 1);
       
   420     if (s == 0) {
       
   421       r = g = b = l; // achromatic
       
   422     } else {
       
   423       var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
       
   424       var p = 2 * l - q;
       
   425       r = hueToRgb(p, q, h + 1 / 3);
       
   426       g = hueToRgb(p, q, h);
       
   427       b = hueToRgb(p, q, h - 1 / 3);
       
   428     }
       
   429 
       
   430     return '#' + decToHex[Math.floor(r * 255)] +
       
   431         decToHex[Math.floor(g * 255)] +
       
   432         decToHex[Math.floor(b * 255)];
       
   433   }
       
   434 
       
   435   function hueToRgb(m1, m2, h) {
       
   436     if (h < 0)
       
   437       h++;
       
   438     if (h > 1)
       
   439       h--;
       
   440 
       
   441     if (6 * h < 1)
       
   442       return m1 + (m2 - m1) * 6 * h;
       
   443     else if (2 * h < 1)
       
   444       return m2;
       
   445     else if (3 * h < 2)
       
   446       return m1 + (m2 - m1) * (2 / 3 - h) * 6;
       
   447     else
       
   448       return m1;
       
   449   }
       
   450 
       
   451   var processStyleCache = {};
       
   452 
       
   453   function processStyle(styleString) {
       
   454     if (styleString in processStyleCache) {
       
   455       return processStyleCache[styleString];
       
   456     }
       
   457 
       
   458     var str, alpha = 1;
       
   459 
       
   460     styleString = String(styleString);
       
   461     if (styleString.charAt(0) == '#') {
       
   462       str = styleString;
       
   463     } else if (/^rgb/.test(styleString)) {
       
   464       var parts = getRgbHslContent(styleString);
       
   465       var str = '#', n;
       
   466       for (var i = 0; i < 3; i++) {
       
   467         if (parts[i].indexOf('%') != -1) {
       
   468           n = Math.floor(percent(parts[i]) * 255);
       
   469         } else {
       
   470           n = +parts[i];
       
   471         }
       
   472         str += decToHex[clamp(n, 0, 255)];
       
   473       }
       
   474       alpha = +parts[3];
       
   475     } else if (/^hsl/.test(styleString)) {
       
   476       var parts = getRgbHslContent(styleString);
       
   477       str = hslToRgb(parts);
       
   478       alpha = parts[3];
       
   479     } else {
       
   480       str = colorData[styleString] || styleString;
       
   481     }
       
   482     return processStyleCache[styleString] = {color: str, alpha: alpha};
       
   483   }
       
   484 
       
   485   var DEFAULT_STYLE = {
       
   486     style: 'normal',
       
   487     variant: 'normal',
       
   488     weight: 'normal',
       
   489     size: 10,
       
   490     family: 'sans-serif'
       
   491   };
       
   492 
       
   493   // Internal text style cache
       
   494   var fontStyleCache = {};
       
   495 
       
   496   function processFontStyle(styleString) {
       
   497     if (fontStyleCache[styleString]) {
       
   498       return fontStyleCache[styleString];
       
   499     }
       
   500 
       
   501     var el = document.createElement('div');
       
   502     var style = el.style;
       
   503     try {
       
   504       style.font = styleString;
       
   505     } catch (ex) {
       
   506       // Ignore failures to set to invalid font.
       
   507     }
       
   508 
       
   509     return fontStyleCache[styleString] = {
       
   510       style: style.fontStyle || DEFAULT_STYLE.style,
       
   511       variant: style.fontVariant || DEFAULT_STYLE.variant,
       
   512       weight: style.fontWeight || DEFAULT_STYLE.weight,
       
   513       size: style.fontSize || DEFAULT_STYLE.size,
       
   514       family: style.fontFamily || DEFAULT_STYLE.family
       
   515     };
       
   516   }
       
   517 
       
   518   function getComputedStyle(style, element) {
       
   519     var computedStyle = {};
       
   520 
       
   521     for (var p in style) {
       
   522       computedStyle[p] = style[p];
       
   523     }
       
   524 
       
   525     // Compute the size
       
   526     var canvasFontSize = parseFloat(element.currentStyle.fontSize),
       
   527         fontSize = parseFloat(style.size);
       
   528 
       
   529     if (typeof style.size == 'number') {
       
   530       computedStyle.size = style.size;
       
   531     } else if (style.size.indexOf('px') != -1) {
       
   532       computedStyle.size = fontSize;
       
   533     } else if (style.size.indexOf('em') != -1) {
       
   534       computedStyle.size = canvasFontSize * fontSize;
       
   535     } else if(style.size.indexOf('%') != -1) {
       
   536       computedStyle.size = (canvasFontSize / 100) * fontSize;
       
   537     } else if (style.size.indexOf('pt') != -1) {
       
   538       computedStyle.size = fontSize / .75;
       
   539     } else {
       
   540       computedStyle.size = canvasFontSize;
       
   541     }
       
   542 
       
   543     // Different scaling between normal text and VML text. This was found using
       
   544     // trial and error to get the same size as non VML text.
       
   545     computedStyle.size *= 0.981;
       
   546 
       
   547     return computedStyle;
       
   548   }
       
   549 
       
   550   function buildStyle(style) {
       
   551     return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
       
   552         style.size + 'px ' + style.family;
       
   553   }
       
   554 
       
   555   var lineCapMap = {
       
   556     'butt': 'flat',
       
   557     'round': 'round'
       
   558   };
       
   559 
       
   560   function processLineCap(lineCap) {
       
   561     return lineCapMap[lineCap] || 'square';
       
   562   }
       
   563 
       
   564   /**
       
   565    * This class implements CanvasRenderingContext2D interface as described by
       
   566    * the WHATWG.
       
   567    * @param {HTMLElement} canvasElement The element that the 2D context should
       
   568    * be associated with
       
   569    */
       
   570   function CanvasRenderingContext2D_(canvasElement) {
       
   571     this.m_ = createMatrixIdentity();
       
   572 
       
   573     this.mStack_ = [];
       
   574     this.aStack_ = [];
       
   575     this.currentPath_ = [];
       
   576 
       
   577     // Canvas context properties
       
   578     this.strokeStyle = '#000';
       
   579     this.fillStyle = '#000';
       
   580 
       
   581     this.lineWidth = 1;
       
   582     this.lineJoin = 'miter';
       
   583     this.lineCap = 'butt';
       
   584     this.miterLimit = Z * 1;
       
   585     this.globalAlpha = 1;
       
   586     this.font = '10px sans-serif';
       
   587     this.textAlign = 'left';
       
   588     this.textBaseline = 'alphabetic';
       
   589     this.canvas = canvasElement;
       
   590 
       
   591     var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
       
   592         canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
       
   593     var el = canvasElement.ownerDocument.createElement('div');
       
   594     el.style.cssText = cssText;
       
   595     canvasElement.appendChild(el);
       
   596 
       
   597     var overlayEl = el.cloneNode(false);
       
   598     // Use a non transparent background.
       
   599     overlayEl.style.backgroundColor = 'red';
       
   600     overlayEl.style.filter = 'alpha(opacity=0)';
       
   601     canvasElement.appendChild(overlayEl);
       
   602 
       
   603     this.element_ = el;
       
   604     this.arcScaleX_ = 1;
       
   605     this.arcScaleY_ = 1;
       
   606     this.lineScale_ = 1;
       
   607   }
       
   608 
       
   609   var contextPrototype = CanvasRenderingContext2D_.prototype;
       
   610   contextPrototype.clearRect = function() {
       
   611     if (this.textMeasureEl_) {
       
   612       this.textMeasureEl_.removeNode(true);
       
   613       this.textMeasureEl_ = null;
       
   614     }
       
   615     this.element_.innerHTML = '';
       
   616   };
       
   617 
       
   618   contextPrototype.beginPath = function() {
       
   619     // TODO: Branch current matrix so that save/restore has no effect
       
   620     //       as per safari docs.
       
   621     this.currentPath_ = [];
       
   622   };
       
   623 
       
   624   contextPrototype.moveTo = function(aX, aY) {
       
   625     var p = getCoords(this, aX, aY);
       
   626     this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
       
   627     this.currentX_ = p.x;
       
   628     this.currentY_ = p.y;
       
   629   };
       
   630 
       
   631   contextPrototype.lineTo = function(aX, aY) {
       
   632     var p = getCoords(this, aX, aY);
       
   633     this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
       
   634 
       
   635     this.currentX_ = p.x;
       
   636     this.currentY_ = p.y;
       
   637   };
       
   638 
       
   639   contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
       
   640                                             aCP2x, aCP2y,
       
   641                                             aX, aY) {
       
   642     var p = getCoords(this, aX, aY);
       
   643     var cp1 = getCoords(this, aCP1x, aCP1y);
       
   644     var cp2 = getCoords(this, aCP2x, aCP2y);
       
   645     bezierCurveTo(this, cp1, cp2, p);
       
   646   };
       
   647 
       
   648   // Helper function that takes the already fixed cordinates.
       
   649   function bezierCurveTo(self, cp1, cp2, p) {
       
   650     self.currentPath_.push({
       
   651       type: 'bezierCurveTo',
       
   652       cp1x: cp1.x,
       
   653       cp1y: cp1.y,
       
   654       cp2x: cp2.x,
       
   655       cp2y: cp2.y,
       
   656       x: p.x,
       
   657       y: p.y
       
   658     });
       
   659     self.currentX_ = p.x;
       
   660     self.currentY_ = p.y;
       
   661   }
       
   662 
       
   663   contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
       
   664     // the following is lifted almost directly from
       
   665     // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
       
   666 
       
   667     var cp = getCoords(this, aCPx, aCPy);
       
   668     var p = getCoords(this, aX, aY);
       
   669 
       
   670     var cp1 = {
       
   671       x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
       
   672       y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
       
   673     };
       
   674     var cp2 = {
       
   675       x: cp1.x + (p.x - this.currentX_) / 3.0,
       
   676       y: cp1.y + (p.y - this.currentY_) / 3.0
       
   677     };
       
   678 
       
   679     bezierCurveTo(this, cp1, cp2, p);
       
   680   };
       
   681 
       
   682   contextPrototype.arc = function(aX, aY, aRadius,
       
   683                                   aStartAngle, aEndAngle, aClockwise) {
       
   684     aRadius *= Z;
       
   685     var arcType = aClockwise ? 'at' : 'wa';
       
   686 
       
   687     var xStart = aX + mc(aStartAngle) * aRadius - Z2;
       
   688     var yStart = aY + ms(aStartAngle) * aRadius - Z2;
       
   689 
       
   690     var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
       
   691     var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
       
   692 
       
   693     // IE won't render arches drawn counter clockwise if xStart == xEnd.
       
   694     if (xStart == xEnd && !aClockwise) {
       
   695       xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
       
   696                        // that can be represented in binary
       
   697     }
       
   698 
       
   699     var p = getCoords(this, aX, aY);
       
   700     var pStart = getCoords(this, xStart, yStart);
       
   701     var pEnd = getCoords(this, xEnd, yEnd);
       
   702 
       
   703     this.currentPath_.push({type: arcType,
       
   704                            x: p.x,
       
   705                            y: p.y,
       
   706                            radius: aRadius,
       
   707                            xStart: pStart.x,
       
   708                            yStart: pStart.y,
       
   709                            xEnd: pEnd.x,
       
   710                            yEnd: pEnd.y});
       
   711 
       
   712   };
       
   713 
       
   714   contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
       
   715     this.moveTo(aX, aY);
       
   716     this.lineTo(aX + aWidth, aY);
       
   717     this.lineTo(aX + aWidth, aY + aHeight);
       
   718     this.lineTo(aX, aY + aHeight);
       
   719     this.closePath();
       
   720   };
       
   721 
       
   722   contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
       
   723     var oldPath = this.currentPath_;
       
   724     this.beginPath();
       
   725 
       
   726     this.moveTo(aX, aY);
       
   727     this.lineTo(aX + aWidth, aY);
       
   728     this.lineTo(aX + aWidth, aY + aHeight);
       
   729     this.lineTo(aX, aY + aHeight);
       
   730     this.closePath();
       
   731     this.stroke();
       
   732 
       
   733     this.currentPath_ = oldPath;
       
   734   };
       
   735 
       
   736   contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
       
   737     var oldPath = this.currentPath_;
       
   738     this.beginPath();
       
   739 
       
   740     this.moveTo(aX, aY);
       
   741     this.lineTo(aX + aWidth, aY);
       
   742     this.lineTo(aX + aWidth, aY + aHeight);
       
   743     this.lineTo(aX, aY + aHeight);
       
   744     this.closePath();
       
   745     this.fill();
       
   746 
       
   747     this.currentPath_ = oldPath;
       
   748   };
       
   749 
       
   750   contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
       
   751     var gradient = new CanvasGradient_('gradient');
       
   752     gradient.x0_ = aX0;
       
   753     gradient.y0_ = aY0;
       
   754     gradient.x1_ = aX1;
       
   755     gradient.y1_ = aY1;
       
   756     return gradient;
       
   757   };
       
   758 
       
   759   contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
       
   760                                                    aX1, aY1, aR1) {
       
   761     var gradient = new CanvasGradient_('gradientradial');
       
   762     gradient.x0_ = aX0;
       
   763     gradient.y0_ = aY0;
       
   764     gradient.r0_ = aR0;
       
   765     gradient.x1_ = aX1;
       
   766     gradient.y1_ = aY1;
       
   767     gradient.r1_ = aR1;
       
   768     return gradient;
       
   769   };
       
   770 
       
   771   contextPrototype.drawImage = function(image, var_args) {
       
   772     var dx, dy, dw, dh, sx, sy, sw, sh;
       
   773 
       
   774     // to find the original width we overide the width and height
       
   775     var oldRuntimeWidth = image.runtimeStyle.width;
       
   776     var oldRuntimeHeight = image.runtimeStyle.height;
       
   777     image.runtimeStyle.width = 'auto';
       
   778     image.runtimeStyle.height = 'auto';
       
   779 
       
   780     // get the original size
       
   781     var w = image.width;
       
   782     var h = image.height;
       
   783 
       
   784     // and remove overides
       
   785     image.runtimeStyle.width = oldRuntimeWidth;
       
   786     image.runtimeStyle.height = oldRuntimeHeight;
       
   787 
       
   788     if (arguments.length == 3) {
       
   789       dx = arguments[1];
       
   790       dy = arguments[2];
       
   791       sx = sy = 0;
       
   792       sw = dw = w;
       
   793       sh = dh = h;
       
   794     } else if (arguments.length == 5) {
       
   795       dx = arguments[1];
       
   796       dy = arguments[2];
       
   797       dw = arguments[3];
       
   798       dh = arguments[4];
       
   799       sx = sy = 0;
       
   800       sw = w;
       
   801       sh = h;
       
   802     } else if (arguments.length == 9) {
       
   803       sx = arguments[1];
       
   804       sy = arguments[2];
       
   805       sw = arguments[3];
       
   806       sh = arguments[4];
       
   807       dx = arguments[5];
       
   808       dy = arguments[6];
       
   809       dw = arguments[7];
       
   810       dh = arguments[8];
       
   811     } else {
       
   812       throw Error('Invalid number of arguments');
       
   813     }
       
   814 
       
   815     var d = getCoords(this, dx, dy);
       
   816 
       
   817     var w2 = sw / 2;
       
   818     var h2 = sh / 2;
       
   819 
       
   820     var vmlStr = [];
       
   821 
       
   822     var W = 10;
       
   823     var H = 10;
       
   824 
       
   825     // For some reason that I've now forgotten, using divs didn't work
       
   826     vmlStr.push(' <g_vml_:group',
       
   827                 ' coordsize="', Z * W, ',', Z * H, '"',
       
   828                 ' coordorigin="0,0"' ,
       
   829                 ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
       
   830 
       
   831     // If filters are necessary (rotation exists), create them
       
   832     // filters are bog-slow, so only create them if abbsolutely necessary
       
   833     // The following check doesn't account for skews (which don't exist
       
   834     // in the canvas spec (yet) anyway.
       
   835 
       
   836     if (this.m_[0][0] != 1 || this.m_[0][1] ||
       
   837         this.m_[1][1] != 1 || this.m_[1][0]) {
       
   838       var filter = [];
       
   839 
       
   840       // Note the 12/21 reversal
       
   841       filter.push('M11=', this.m_[0][0], ',',
       
   842                   'M12=', this.m_[1][0], ',',
       
   843                   'M21=', this.m_[0][1], ',',
       
   844                   'M22=', this.m_[1][1], ',',
       
   845                   'Dx=', mr(d.x / Z), ',',
       
   846                   'Dy=', mr(d.y / Z), '');
       
   847 
       
   848       // Bounding box calculation (need to minimize displayed area so that
       
   849       // filters don't waste time on unused pixels.
       
   850       var max = d;
       
   851       var c2 = getCoords(this, dx + dw, dy);
       
   852       var c3 = getCoords(this, dx, dy + dh);
       
   853       var c4 = getCoords(this, dx + dw, dy + dh);
       
   854 
       
   855       max.x = m.max(max.x, c2.x, c3.x, c4.x);
       
   856       max.y = m.max(max.y, c2.y, c3.y, c4.y);
       
   857 
       
   858       vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
       
   859                   'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
       
   860                   filter.join(''), ", sizingmethod='clip');");
       
   861 
       
   862     } else {
       
   863       vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
       
   864     }
       
   865 
       
   866     vmlStr.push(' ">' ,
       
   867                 '<g_vml_:image src="', image.src, '"',
       
   868                 ' style="width:', Z * dw, 'px;',
       
   869                 ' height:', Z * dh, 'px"',
       
   870                 ' cropleft="', sx / w, '"',
       
   871                 ' croptop="', sy / h, '"',
       
   872                 ' cropright="', (w - sx - sw) / w, '"',
       
   873                 ' cropbottom="', (h - sy - sh) / h, '"',
       
   874                 ' />',
       
   875                 '</g_vml_:group>');
       
   876 
       
   877     this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
       
   878   };
       
   879 
       
   880   contextPrototype.stroke = function(aFill) {
       
   881     var W = 10;
       
   882     var H = 10;
       
   883     // Divide the shape into chunks if it's too long because IE has a limit
       
   884     // somewhere for how long a VML shape can be. This simple division does
       
   885     // not work with fills, only strokes, unfortunately.
       
   886     var chunkSize = 5000;
       
   887 
       
   888     var min = {x: null, y: null};
       
   889     var max = {x: null, y: null};
       
   890 
       
   891     for (var j = 0; j < this.currentPath_.length; j += chunkSize) {
       
   892       var lineStr = [];
       
   893       var lineOpen = false;
       
   894 
       
   895       lineStr.push('<g_vml_:shape',
       
   896                    ' filled="', !!aFill, '"',
       
   897                    ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
       
   898                    ' coordorigin="0,0"',
       
   899                    ' coordsize="', Z * W, ',', Z * H, '"',
       
   900                    ' stroked="', !aFill, '"',
       
   901                    ' path="');
       
   902 
       
   903       var newSeq = false;
       
   904 
       
   905       for (var i = j; i < Math.min(j + chunkSize, this.currentPath_.length); i++) {
       
   906         if (i % chunkSize == 0 && i > 0) { // move into position for next chunk
       
   907           lineStr.push(' m ', mr(this.currentPath_[i-1].x), ',', mr(this.currentPath_[i-1].y));
       
   908         }
       
   909 
       
   910         var p = this.currentPath_[i];
       
   911         var c;
       
   912 
       
   913         switch (p.type) {
       
   914           case 'moveTo':
       
   915             c = p;
       
   916             lineStr.push(' m ', mr(p.x), ',', mr(p.y));
       
   917             break;
       
   918           case 'lineTo':
       
   919             lineStr.push(' l ', mr(p.x), ',', mr(p.y));
       
   920             break;
       
   921           case 'close':
       
   922             lineStr.push(' x ');
       
   923             p = null;
       
   924             break;
       
   925           case 'bezierCurveTo':
       
   926             lineStr.push(' c ',
       
   927                          mr(p.cp1x), ',', mr(p.cp1y), ',',
       
   928                          mr(p.cp2x), ',', mr(p.cp2y), ',',
       
   929                          mr(p.x), ',', mr(p.y));
       
   930             break;
       
   931           case 'at':
       
   932           case 'wa':
       
   933             lineStr.push(' ', p.type, ' ',
       
   934                          mr(p.x - this.arcScaleX_ * p.radius), ',',
       
   935                          mr(p.y - this.arcScaleY_ * p.radius), ' ',
       
   936                          mr(p.x + this.arcScaleX_ * p.radius), ',',
       
   937                          mr(p.y + this.arcScaleY_ * p.radius), ' ',
       
   938                          mr(p.xStart), ',', mr(p.yStart), ' ',
       
   939                          mr(p.xEnd), ',', mr(p.yEnd));
       
   940             break;
       
   941         }
       
   942   
       
   943   
       
   944         // TODO: Following is broken for curves due to
       
   945         //       move to proper paths.
       
   946   
       
   947         // Figure out dimensions so we can do gradient fills
       
   948         // properly
       
   949         if (p) {
       
   950           if (min.x == null || p.x < min.x) {
       
   951             min.x = p.x;
       
   952           }
       
   953           if (max.x == null || p.x > max.x) {
       
   954             max.x = p.x;
       
   955           }
       
   956           if (min.y == null || p.y < min.y) {
       
   957             min.y = p.y;
       
   958           }
       
   959           if (max.y == null || p.y > max.y) {
       
   960             max.y = p.y;
       
   961           }
       
   962         }
       
   963       }
       
   964       lineStr.push(' ">');
       
   965   
       
   966       if (!aFill) {
       
   967         appendStroke(this, lineStr);
       
   968       } else {
       
   969         appendFill(this, lineStr, min, max);
       
   970       }
       
   971   
       
   972       lineStr.push('</g_vml_:shape>');
       
   973   
       
   974       this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
       
   975     }
       
   976   };
       
   977 
       
   978   function appendStroke(ctx, lineStr) {
       
   979     var a = processStyle(ctx.strokeStyle);
       
   980     var color = a.color;
       
   981     var opacity = a.alpha * ctx.globalAlpha;
       
   982     var lineWidth = ctx.lineScale_ * ctx.lineWidth;
       
   983 
       
   984     // VML cannot correctly render a line if the width is less than 1px.
       
   985     // In that case, we dilute the color to make the line look thinner.
       
   986     if (lineWidth < 1) {
       
   987       opacity *= lineWidth;
       
   988     }
       
   989 
       
   990     lineStr.push(
       
   991       '<g_vml_:stroke',
       
   992       ' opacity="', opacity, '"',
       
   993       ' joinstyle="', ctx.lineJoin, '"',
       
   994       ' miterlimit="', ctx.miterLimit, '"',
       
   995       ' endcap="', processLineCap(ctx.lineCap), '"',
       
   996       ' weight="', lineWidth, 'px"',
       
   997       ' color="', color, '" />'
       
   998     );
       
   999   }
       
  1000 
       
  1001   function appendFill(ctx, lineStr, min, max) {
       
  1002     var fillStyle = ctx.fillStyle;
       
  1003     var arcScaleX = ctx.arcScaleX_;
       
  1004     var arcScaleY = ctx.arcScaleY_;
       
  1005     var width = max.x - min.x;
       
  1006     var height = max.y - min.y;
       
  1007     if (fillStyle instanceof CanvasGradient_) {
       
  1008       // TODO: Gradients transformed with the transformation matrix.
       
  1009       var angle = 0;
       
  1010       var focus = {x: 0, y: 0};
       
  1011 
       
  1012       // additional offset
       
  1013       var shift = 0;
       
  1014       // scale factor for offset
       
  1015       var expansion = 1;
       
  1016 
       
  1017       if (fillStyle.type_ == 'gradient') {
       
  1018         var x0 = fillStyle.x0_ / arcScaleX;
       
  1019         var y0 = fillStyle.y0_ / arcScaleY;
       
  1020         var x1 = fillStyle.x1_ / arcScaleX;
       
  1021         var y1 = fillStyle.y1_ / arcScaleY;
       
  1022         var p0 = getCoords(ctx, x0, y0);
       
  1023         var p1 = getCoords(ctx, x1, y1);
       
  1024         var dx = p1.x - p0.x;
       
  1025         var dy = p1.y - p0.y;
       
  1026         angle = Math.atan2(dx, dy) * 180 / Math.PI;
       
  1027 
       
  1028         // The angle should be a non-negative number.
       
  1029         if (angle < 0) {
       
  1030           angle += 360;
       
  1031         }
       
  1032 
       
  1033         // Very small angles produce an unexpected result because they are
       
  1034         // converted to a scientific notation string.
       
  1035         if (angle < 1e-6) {
       
  1036           angle = 0;
       
  1037         }
       
  1038       } else {
       
  1039         var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
       
  1040         focus = {
       
  1041           x: (p0.x - min.x) / width,
       
  1042           y: (p0.y - min.y) / height
       
  1043         };
       
  1044 
       
  1045         width  /= arcScaleX * Z;
       
  1046         height /= arcScaleY * Z;
       
  1047         var dimension = m.max(width, height);
       
  1048         shift = 2 * fillStyle.r0_ / dimension;
       
  1049         expansion = 2 * fillStyle.r1_ / dimension - shift;
       
  1050       }
       
  1051 
       
  1052       // We need to sort the color stops in ascending order by offset,
       
  1053       // otherwise IE won't interpret it correctly.
       
  1054       var stops = fillStyle.colors_;
       
  1055       stops.sort(function(cs1, cs2) {
       
  1056         return cs1.offset - cs2.offset;
       
  1057       });
       
  1058 
       
  1059       var length = stops.length;
       
  1060       var color1 = stops[0].color;
       
  1061       var color2 = stops[length - 1].color;
       
  1062       var opacity1 = stops[0].alpha * ctx.globalAlpha;
       
  1063       var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
       
  1064 
       
  1065       var colors = [];
       
  1066       for (var i = 0; i < length; i++) {
       
  1067         var stop = stops[i];
       
  1068         colors.push(stop.offset * expansion + shift + ' ' + stop.color);
       
  1069       }
       
  1070 
       
  1071       // When colors attribute is used, the meanings of opacity and o:opacity2
       
  1072       // are reversed.
       
  1073       lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
       
  1074                    ' method="none" focus="100%"',
       
  1075                    ' color="', color1, '"',
       
  1076                    ' color2="', color2, '"',
       
  1077                    ' colors="', colors.join(','), '"',
       
  1078                    ' opacity="', opacity2, '"',
       
  1079                    ' g_o_:opacity2="', opacity1, '"',
       
  1080                    ' angle="', angle, '"',
       
  1081                    ' focusposition="', focus.x, ',', focus.y, '" />');
       
  1082     } else if (fillStyle instanceof CanvasPattern_) {
       
  1083       if (width && height) {
       
  1084         var deltaLeft = -min.x;
       
  1085         var deltaTop = -min.y;
       
  1086         lineStr.push('<g_vml_:fill',
       
  1087                      ' position="',
       
  1088                      deltaLeft / width * arcScaleX * arcScaleX, ',',
       
  1089                      deltaTop / height * arcScaleY * arcScaleY, '"',
       
  1090                      ' type="tile"',
       
  1091                      // TODO: Figure out the correct size to fit the scale.
       
  1092                      //' size="', w, 'px ', h, 'px"',
       
  1093                      ' src="', fillStyle.src_, '" />');
       
  1094        }
       
  1095     } else {
       
  1096       var a = processStyle(ctx.fillStyle);
       
  1097       var color = a.color;
       
  1098       var opacity = a.alpha * ctx.globalAlpha;
       
  1099       lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
       
  1100                    '" />');
       
  1101     }
       
  1102   }
       
  1103 
       
  1104   contextPrototype.fill = function() {
       
  1105     this.stroke(true);
       
  1106   };
       
  1107 
       
  1108   contextPrototype.closePath = function() {
       
  1109     this.currentPath_.push({type: 'close'});
       
  1110   };
       
  1111 
       
  1112   function getCoords(ctx, aX, aY) {
       
  1113     var m = ctx.m_;
       
  1114     return {
       
  1115       x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
       
  1116       y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
       
  1117     };
       
  1118   };
       
  1119 
       
  1120   contextPrototype.save = function() {
       
  1121     var o = {};
       
  1122     copyState(this, o);
       
  1123     this.aStack_.push(o);
       
  1124     this.mStack_.push(this.m_);
       
  1125     this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
       
  1126   };
       
  1127 
       
  1128   contextPrototype.restore = function() {
       
  1129     if (this.aStack_.length) {
       
  1130       copyState(this.aStack_.pop(), this);
       
  1131       this.m_ = this.mStack_.pop();
       
  1132     }
       
  1133   };
       
  1134 
       
  1135   function matrixIsFinite(m) {
       
  1136     return isFinite(m[0][0]) && isFinite(m[0][1]) &&
       
  1137         isFinite(m[1][0]) && isFinite(m[1][1]) &&
       
  1138         isFinite(m[2][0]) && isFinite(m[2][1]);
       
  1139   }
       
  1140 
       
  1141   function setM(ctx, m, updateLineScale) {
       
  1142     if (!matrixIsFinite(m)) {
       
  1143       return;
       
  1144     }
       
  1145     ctx.m_ = m;
       
  1146 
       
  1147     if (updateLineScale) {
       
  1148       // Get the line scale.
       
  1149       // Determinant of this.m_ means how much the area is enlarged by the
       
  1150       // transformation. So its square root can be used as a scale factor
       
  1151       // for width.
       
  1152       var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
       
  1153       ctx.lineScale_ = sqrt(abs(det));
       
  1154     }
       
  1155   }
       
  1156 
       
  1157   contextPrototype.translate = function(aX, aY) {
       
  1158     var m1 = [
       
  1159       [1,  0,  0],
       
  1160       [0,  1,  0],
       
  1161       [aX, aY, 1]
       
  1162     ];
       
  1163 
       
  1164     setM(this, matrixMultiply(m1, this.m_), false);
       
  1165   };
       
  1166 
       
  1167   contextPrototype.rotate = function(aRot) {
       
  1168     var c = mc(aRot);
       
  1169     var s = ms(aRot);
       
  1170 
       
  1171     var m1 = [
       
  1172       [c,  s, 0],
       
  1173       [-s, c, 0],
       
  1174       [0,  0, 1]
       
  1175     ];
       
  1176 
       
  1177     setM(this, matrixMultiply(m1, this.m_), false);
       
  1178   };
       
  1179 
       
  1180   contextPrototype.scale = function(aX, aY) {
       
  1181     this.arcScaleX_ *= aX;
       
  1182     this.arcScaleY_ *= aY;
       
  1183     var m1 = [
       
  1184       [aX, 0,  0],
       
  1185       [0,  aY, 0],
       
  1186       [0,  0,  1]
       
  1187     ];
       
  1188 
       
  1189     setM(this, matrixMultiply(m1, this.m_), true);
       
  1190   };
       
  1191 
       
  1192   contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
       
  1193     var m1 = [
       
  1194       [m11, m12, 0],
       
  1195       [m21, m22, 0],
       
  1196       [dx,  dy,  1]
       
  1197     ];
       
  1198 
       
  1199     setM(this, matrixMultiply(m1, this.m_), true);
       
  1200   };
       
  1201 
       
  1202   contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
       
  1203     var m = [
       
  1204       [m11, m12, 0],
       
  1205       [m21, m22, 0],
       
  1206       [dx,  dy,  1]
       
  1207     ];
       
  1208 
       
  1209     setM(this, m, true);
       
  1210   };
       
  1211 
       
  1212   /**
       
  1213    * The text drawing function.
       
  1214    * The maxWidth argument isn't taken in account, since no browser supports
       
  1215    * it yet.
       
  1216    */
       
  1217   contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
       
  1218     var m = this.m_,
       
  1219         delta = 1000,
       
  1220         left = 0,
       
  1221         right = delta,
       
  1222         offset = {x: 0, y: 0},
       
  1223         lineStr = [];
       
  1224 
       
  1225     var fontStyle = getComputedStyle(processFontStyle(this.font),
       
  1226                                      this.element_);
       
  1227 
       
  1228     var fontStyleString = buildStyle(fontStyle);
       
  1229 
       
  1230     var elementStyle = this.element_.currentStyle;
       
  1231     var textAlign = this.textAlign.toLowerCase();
       
  1232     switch (textAlign) {
       
  1233       case 'left':
       
  1234       case 'center':
       
  1235       case 'right':
       
  1236         break;
       
  1237       case 'end':
       
  1238         textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
       
  1239         break;
       
  1240       case 'start':
       
  1241         textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
       
  1242         break;
       
  1243       default:
       
  1244         textAlign = 'left';
       
  1245     }
       
  1246 
       
  1247     // 1.75 is an arbitrary number, as there is no info about the text baseline
       
  1248     switch (this.textBaseline) {
       
  1249       case 'hanging':
       
  1250       case 'top':
       
  1251         offset.y = fontStyle.size / 1.75;
       
  1252         break;
       
  1253       case 'middle':
       
  1254         break;
       
  1255       default:
       
  1256       case null:
       
  1257       case 'alphabetic':
       
  1258       case 'ideographic':
       
  1259       case 'bottom':
       
  1260         offset.y = -fontStyle.size / 2.25;
       
  1261         break;
       
  1262     }
       
  1263 
       
  1264     switch(textAlign) {
       
  1265       case 'right':
       
  1266         left = delta;
       
  1267         right = 0.05;
       
  1268         break;
       
  1269       case 'center':
       
  1270         left = right = delta / 2;
       
  1271         break;
       
  1272     }
       
  1273 
       
  1274     var d = getCoords(this, x + offset.x, y + offset.y);
       
  1275 
       
  1276     lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
       
  1277                  ' coordsize="100 100" coordorigin="0 0"',
       
  1278                  ' filled="', !stroke, '" stroked="', !!stroke,
       
  1279                  '" style="position:absolute;width:1px;height:1px;">');
       
  1280 
       
  1281     if (stroke) {
       
  1282       appendStroke(this, lineStr);
       
  1283     } else {
       
  1284       // TODO: Fix the min and max params.
       
  1285       appendFill(this, lineStr, {x: -left, y: 0},
       
  1286                  {x: right, y: fontStyle.size});
       
  1287     }
       
  1288 
       
  1289     var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
       
  1290                 m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
       
  1291 
       
  1292     var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
       
  1293 
       
  1294     lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
       
  1295                  ' offset="', skewOffset, '" origin="', left ,' 0" />',
       
  1296                  '<g_vml_:path textpathok="true" />',
       
  1297                  '<g_vml_:textpath on="true" string="',
       
  1298                  encodeHtmlAttribute(text),
       
  1299                  '" style="v-text-align:', textAlign,
       
  1300                  ';font:', encodeHtmlAttribute(fontStyleString),
       
  1301                  '" /></g_vml_:line>');
       
  1302 
       
  1303     this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
       
  1304   };
       
  1305 
       
  1306   contextPrototype.fillText = function(text, x, y, maxWidth) {
       
  1307     this.drawText_(text, x, y, maxWidth, false);
       
  1308   };
       
  1309 
       
  1310   contextPrototype.strokeText = function(text, x, y, maxWidth) {
       
  1311     this.drawText_(text, x, y, maxWidth, true);
       
  1312   };
       
  1313 
       
  1314   contextPrototype.measureText = function(text) {
       
  1315     if (!this.textMeasureEl_) {
       
  1316       var s = '<span style="position:absolute;' +
       
  1317           'top:-20000px;left:0;padding:0;margin:0;border:none;' +
       
  1318           'white-space:pre;"></span>';
       
  1319       this.element_.insertAdjacentHTML('beforeEnd', s);
       
  1320       this.textMeasureEl_ = this.element_.lastChild;
       
  1321     }
       
  1322     var doc = this.element_.ownerDocument;
       
  1323     this.textMeasureEl_.innerHTML = '';
       
  1324     this.textMeasureEl_.style.font = this.font;
       
  1325     // Don't use innerHTML or innerText because they allow markup/whitespace.
       
  1326     this.textMeasureEl_.appendChild(doc.createTextNode(text));
       
  1327     return {width: this.textMeasureEl_.offsetWidth};
       
  1328   };
       
  1329 
       
  1330   /******** STUBS ********/
       
  1331   contextPrototype.clip = function() {
       
  1332     // TODO: Implement
       
  1333   };
       
  1334 
       
  1335   contextPrototype.arcTo = function() {
       
  1336     // TODO: Implement
       
  1337   };
       
  1338 
       
  1339   contextPrototype.createPattern = function(image, repetition) {
       
  1340     return new CanvasPattern_(image, repetition);
       
  1341   };
       
  1342 
       
  1343   // Gradient / Pattern Stubs
       
  1344   function CanvasGradient_(aType) {
       
  1345     this.type_ = aType;
       
  1346     this.x0_ = 0;
       
  1347     this.y0_ = 0;
       
  1348     this.r0_ = 0;
       
  1349     this.x1_ = 0;
       
  1350     this.y1_ = 0;
       
  1351     this.r1_ = 0;
       
  1352     this.colors_ = [];
       
  1353   }
       
  1354 
       
  1355   CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
       
  1356     aColor = processStyle(aColor);
       
  1357     this.colors_.push({offset: aOffset,
       
  1358                        color: aColor.color,
       
  1359                        alpha: aColor.alpha});
       
  1360   };
       
  1361 
       
  1362   function CanvasPattern_(image, repetition) {
       
  1363     assertImageIsValid(image);
       
  1364     switch (repetition) {
       
  1365       case 'repeat':
       
  1366       case null:
       
  1367       case '':
       
  1368         this.repetition_ = 'repeat';
       
  1369         break
       
  1370       case 'repeat-x':
       
  1371       case 'repeat-y':
       
  1372       case 'no-repeat':
       
  1373         this.repetition_ = repetition;
       
  1374         break;
       
  1375       default:
       
  1376         throwException('SYNTAX_ERR');
       
  1377     }
       
  1378 
       
  1379     this.src_ = image.src;
       
  1380     this.width_ = image.width;
       
  1381     this.height_ = image.height;
       
  1382   }
       
  1383 
       
  1384   function throwException(s) {
       
  1385     throw new DOMException_(s);
       
  1386   }
       
  1387 
       
  1388   function assertImageIsValid(img) {
       
  1389     if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
       
  1390       throwException('TYPE_MISMATCH_ERR');
       
  1391     }
       
  1392     if (img.readyState != 'complete') {
       
  1393       throwException('INVALID_STATE_ERR');
       
  1394     }
       
  1395   }
       
  1396 
       
  1397   function DOMException_(s) {
       
  1398     this.code = this[s];
       
  1399     this.message = s +': DOM Exception ' + this.code;
       
  1400   }
       
  1401   var p = DOMException_.prototype = new Error;
       
  1402   p.INDEX_SIZE_ERR = 1;
       
  1403   p.DOMSTRING_SIZE_ERR = 2;
       
  1404   p.HIERARCHY_REQUEST_ERR = 3;
       
  1405   p.WRONG_DOCUMENT_ERR = 4;
       
  1406   p.INVALID_CHARACTER_ERR = 5;
       
  1407   p.NO_DATA_ALLOWED_ERR = 6;
       
  1408   p.NO_MODIFICATION_ALLOWED_ERR = 7;
       
  1409   p.NOT_FOUND_ERR = 8;
       
  1410   p.NOT_SUPPORTED_ERR = 9;
       
  1411   p.INUSE_ATTRIBUTE_ERR = 10;
       
  1412   p.INVALID_STATE_ERR = 11;
       
  1413   p.SYNTAX_ERR = 12;
       
  1414   p.INVALID_MODIFICATION_ERR = 13;
       
  1415   p.NAMESPACE_ERR = 14;
       
  1416   p.INVALID_ACCESS_ERR = 15;
       
  1417   p.VALIDATION_ERR = 16;
       
  1418   p.TYPE_MISMATCH_ERR = 17;
       
  1419 
       
  1420   // set up externs
       
  1421   G_vmlCanvasManager = G_vmlCanvasManager_;
       
  1422   CanvasRenderingContext2D = CanvasRenderingContext2D_;
       
  1423   CanvasGradient = CanvasGradient_;
       
  1424   CanvasPattern = CanvasPattern_;
       
  1425   DOMException = DOMException_;
       
  1426 })();
       
  1427 
       
  1428 } // if