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