shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

excanvas.js (43371B)


      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