1 // Copyright 2006 Google Inc.
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
7 // http://www.apache.org/licenses/LICENSE
-2.0
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.
18 // * Patterns are not implemented.
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
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 // * Optimize. There is always room for speed improvements.
34 // Only add this code if we do not already have a canvas implementation
35 if (!document
.createElement('canvas').getContext
) {
39 // alias some functions to make (compiled) code shorter
47 // this is used for sub pixel precision
52 * This funtion is assigned to the <canvas> elements as element.getContext().
54 * @return {CanvasRenderingContext2D_}
56 function getContext() {
57 return this.context_
||
58 (this.context_
= new CanvasRenderingContext2D_(this));
61 var slice
= Array
.prototype.slice
;
64 * Binds a function to an object. The returned function will always use the
65 * passed in {@code obj} as {@code this}.
69 * g = bind(f, obj, a, b)
70 * g(c, d) // will do f.call(obj, a, b, c, d)
72 * @param {Function} f The function to bind the object to
73 * @param {Object} obj The object that should act as this when the function
75 * @param {*} var_args Rest arguments that will be used as the initial
76 * arguments when the function is called
77 * @return {Function} A new function that has bound this
79 function bind(f
, obj
, var_args
) {
80 var a
= slice
.call(arguments
, 2);
82 return f
.apply(obj
, a
.concat(slice
.call(arguments
)));
86 var G_vmlCanvasManager_
= {
87 init
: function(opt_doc
) {
88 if (/MSIE/.test(navigator
.userAgent
) && !window
.opera
) {
89 var doc
= opt_doc
|| document
;
90 // Create a dummy element so that IE will allow canvas elements to be
92 doc
.createElement('canvas');
93 doc
.attachEvent('onreadystatechange', bind(this.init_
, this, doc
));
97 init_
: function(doc
) {
99 if (!doc
.namespaces
['g_vml_']) {
100 doc
.namespaces
.add('g_vml_', 'urn:schemas-microsoft-com:vml',
104 if (!doc
.namespaces
['g_o_']) {
105 doc
.namespaces
.add('g_o_', 'urn:schemas-microsoft-com:office:office',
109 // Setup default CSS. Only add one style sheet per document
110 if (!doc
.styleSheets
['ex_canvas_']) {
111 var ss
= doc
.createStyleSheet();
112 ss
.owningElement
.id
= 'ex_canvas_';
113 ss
.cssText
= 'canvas{display:inline-block;overflow:hidden;' +
114 // default size is 300x150 in Gecko and Opera
115 'text-align:left;width:300px;height:150px}' +
116 'g_vml_\\:*{behavior:url(#default#VML)}' +
117 'g_o_\\:*{behavior:url(#default#VML)}';
121 // find all canvas elements
122 var els
= doc
.getElementsByTagName('canvas');
123 for (var i
= 0; i
< els
.length
; i
++) {
124 this.initElement(els
[i
]);
129 * Public initializes a canvas element so that it can be used as canvas
130 * element from now on. This is called automatically before the page is
131 * loaded but if you are creating elements using createElement you need to
132 * make sure this is called on the element.
133 * @param {HTMLElement} el The canvas element to initialize.
134 * @return {HTMLElement} the element that was created.
136 initElement
: function(el
) {
137 if (!el
.getContext
) {
139 el
.getContext
= getContext
;
141 // Remove fallback content. There is no way to hide text nodes so we
142 // just remove all childNodes. We could hide all elements and remove
143 // text nodes but who really cares about the fallback content.
146 // do not use inline function because that will leak memory
147 el
.attachEvent('onpropertychange', onPropertyChange
);
148 el
.attachEvent('onresize', onResize
);
150 var attrs
= el
.attributes
;
151 if (attrs
.width
&& attrs
.width
.specified
) {
152 // TODO: use runtimeStyle and coordsize
153 // el.getContext().setWidth_(attrs.width.nodeValue);
154 el
.style
.width
= attrs
.width
.nodeValue
+ 'px';
156 el
.width
= el
.clientWidth
;
158 if (attrs
.height
&& attrs
.height
.specified
) {
159 // TODO: use runtimeStyle and coordsize
160 // el.getContext().setHeight_(attrs.height.nodeValue);
161 el
.style
.height
= attrs
.height
.nodeValue
+ 'px';
163 el
.height
= el
.clientHeight
;
165 //el.getContext().setCoordsize_()
171 function onPropertyChange(e
) {
172 var el
= e
.srcElement
;
174 switch (e
.propertyName
) {
176 el
.style
.width
= el
.attributes
.width
.nodeValue
+ 'px';
177 el
.getContext().clearRect();
180 el
.style
.height
= el
.attributes
.height
.nodeValue
+ 'px';
181 el
.getContext().clearRect();
186 function onResize(e
) {
187 var el
= e
.srcElement
;
189 el
.firstChild
.style
.width
= el
.clientWidth
+ 'px';
190 el
.firstChild
.style
.height
= el
.clientHeight
+ 'px';
194 G_vmlCanvasManager_
.init();
196 // precompute "00" to "FF"
198 for (var i
= 0; i
< 16; i
++) {
199 for (var j
= 0; j
< 16; j
++) {
200 dec2hex
[i
* 16 + j
] = i
.toString(16) + j
.toString(16);
204 function createMatrixIdentity() {
212 function matrixMultiply(m1
, m2
) {
213 var result
= createMatrixIdentity();
215 for (var x
= 0; x
< 3; x
++) {
216 for (var y
= 0; y
< 3; y
++) {
219 for (var z
= 0; z
< 3; z
++) {
220 sum
+= m1
[x
][z
] * m2
[z
][y
];
229 function copyState(o1
, o2
) {
230 o2
.fillStyle
= o1
.fillStyle
;
231 o2
.lineCap
= o1
.lineCap
;
232 o2
.lineJoin
= o1
.lineJoin
;
233 o2
.lineWidth
= o1
.lineWidth
;
234 o2
.miterLimit
= o1
.miterLimit
;
235 o2
.shadowBlur
= o1
.shadowBlur
;
236 o2
.shadowColor
= o1
.shadowColor
;
237 o2
.shadowOffsetX
= o1
.shadowOffsetX
;
238 o2
.shadowOffsetY
= o1
.shadowOffsetY
;
239 o2
.strokeStyle
= o1
.strokeStyle
;
240 o2
.globalAlpha
= o1
.globalAlpha
;
241 o2
.arcScaleX_
= o1
.arcScaleX_
;
242 o2
.arcScaleY_
= o1
.arcScaleY_
;
243 o2
.lineScale_
= o1
.lineScale_
;
246 function processStyle(styleString
) {
249 styleString
= String(styleString
);
250 if (styleString
.substring(0, 3) == 'rgb') {
251 var start
= styleString
.indexOf('(', 3);
252 var end
= styleString
.indexOf(')', start
+ 1);
253 var guts
= styleString
.substring(start
+ 1, end
).split(',');
256 for (var i
= 0; i
< 3; i
++) {
257 str
+= dec2hex
[Number(guts
[i
])];
260 if (guts
.length
== 4 && styleString
.substr(3, 1) == 'a') {
267 return {color
: str
, alpha
: alpha
};
270 function processLineCap(lineCap
) {
283 * This class implements CanvasRenderingContext2D interface as described by
285 * @param {HTMLElement} surfaceElement The element that the 2D context should
288 function CanvasRenderingContext2D_(surfaceElement
) {
289 this.m_
= createMatrixIdentity();
293 this.currentPath_
= [];
295 // Canvas context properties
296 this.strokeStyle
= '#000';
297 this.fillStyle
= '#000';
300 this.lineJoin
= 'miter';
301 this.lineCap
= 'butt';
302 this.miterLimit
= Z
* 1;
303 this.globalAlpha
= 1;
304 this.canvas
= surfaceElement
;
306 var el
= surfaceElement
.ownerDocument
.createElement('div');
307 el
.style
.width
= surfaceElement
.clientWidth
+ 'px';
308 el
.style
.height
= surfaceElement
.clientHeight
+ 'px';
309 el
.style
.overflow
= 'hidden';
310 el
.style
.position
= 'absolute';
311 surfaceElement
.appendChild(el
);
319 var contextPrototype
= CanvasRenderingContext2D_
.prototype;
320 contextPrototype
.clearRect
= function() {
321 this.element_
.innerHTML
= '';
324 contextPrototype
.beginPath
= function() {
325 // TODO: Branch current matrix so that save/restore has no effect
326 // as per safari docs.
327 this.currentPath_
= [];
330 contextPrototype
.moveTo
= function(aX
, aY
) {
331 var p
= this.getCoords_(aX
, aY
);
332 this.currentPath_
.push({type
: 'moveTo', x
: p
.x
, y
: p
.y
});
333 this.currentX_
= p
.x
;
334 this.currentY_
= p
.y
;
337 contextPrototype
.lineTo
= function(aX
, aY
) {
338 var p
= this.getCoords_(aX
, aY
);
339 this.currentPath_
.push({type
: 'lineTo', x
: p
.x
, y
: p
.y
});
341 this.currentX_
= p
.x
;
342 this.currentY_
= p
.y
;
345 contextPrototype
.bezierCurveTo
= function(aCP1x
, aCP1y
,
348 var p
= this.getCoords_(aX
, aY
);
349 var cp1
= this.getCoords_(aCP1x
, aCP1y
);
350 var cp2
= this.getCoords_(aCP2x
, aCP2y
);
351 bezierCurveTo(this, cp1
, cp2
, p
);
354 // Helper function that takes the already fixed cordinates.
355 function bezierCurveTo(self
, cp1
, cp2
, p
) {
356 self
.currentPath_
.push({
357 type
: 'bezierCurveTo',
365 self
.currentX_
= p
.x
;
366 self
.currentY_
= p
.y
;
369 contextPrototype
.quadraticCurveTo
= function(aCPx
, aCPy
, aX
, aY
) {
370 // the following is lifted almost directly from
371 // http://developer.mozilla.org/en
/docs/Canvas_tutorial
:Drawing_shapes
373 var cp
= this.getCoords_(aCPx
, aCPy
);
374 var p
= this.getCoords_(aX
, aY
);
377 x
: this.currentX_
+ 2.0 / 3.0 * (cp
.x
- this.currentX_
),
378 y
: this.currentY_
+ 2.0 / 3.0 * (cp
.y
- this.currentY_
)
381 x
: cp1
.x
+ (p
.x
- this.currentX_
) / 3.0,
382 y
: cp1
.y
+ (p
.y
- this.currentY_
) / 3.0
385 bezierCurveTo(this, cp1
, cp2
, p
);
388 contextPrototype
.arc
= function(aX
, aY
, aRadius
,
389 aStartAngle
, aEndAngle
, aClockwise
) {
391 var arcType
= aClockwise
? 'at' : 'wa';
393 var xStart
= aX
+ mc(aStartAngle
) * aRadius
- Z2
;
394 var yStart
= aY
+ ms(aStartAngle
) * aRadius
- Z2
;
396 var xEnd
= aX
+ mc(aEndAngle
) * aRadius
- Z2
;
397 var yEnd
= aY
+ ms(aEndAngle
) * aRadius
- Z2
;
399 // IE won't render arches drawn counter clockwise if xStart == xEnd.
400 if (xStart
== xEnd
&& !aClockwise
) {
401 xStart
+= 0.125; // Offset xStart by 1/80 of a pixel
. Use something
402 // that can be represented in binary
405 var p
= this.getCoords_(aX
, aY
);
406 var pStart
= this.getCoords_(xStart
, yStart
);
407 var pEnd
= this.getCoords_(xEnd
, yEnd
);
409 this.currentPath_
.push({type
: arcType
,
420 contextPrototype
.rect
= function(aX
, aY
, aWidth
, aHeight
) {
422 this.lineTo(aX
+ aWidth
, aY
);
423 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
424 this.lineTo(aX
, aY
+ aHeight
);
428 contextPrototype
.strokeRect
= function(aX
, aY
, aWidth
, aHeight
) {
429 var oldPath
= this.currentPath_
;
433 this.lineTo(aX
+ aWidth
, aY
);
434 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
435 this.lineTo(aX
, aY
+ aHeight
);
439 this.currentPath_
= oldPath
;
442 contextPrototype
.fillRect
= function(aX
, aY
, aWidth
, aHeight
) {
443 var oldPath
= this.currentPath_
;
447 this.lineTo(aX
+ aWidth
, aY
);
448 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
449 this.lineTo(aX
, aY
+ aHeight
);
453 this.currentPath_
= oldPath
;
456 contextPrototype
.createLinearGradient
= function(aX0
, aY0
, aX1
, aY1
) {
457 var gradient
= new CanvasGradient_('gradient');
465 contextPrototype
.createRadialGradient
= function(aX0
, aY0
, aR0
,
467 var gradient
= new CanvasGradient_('gradientradial');
477 contextPrototype
.drawImage
= function(image
, var_args
) {
478 var dx
, dy
, dw
, dh
, sx
, sy
, sw
, sh
;
480 // to find the original width we overide the width and height
481 var oldRuntimeWidth
= image
.runtimeStyle
.width
;
482 var oldRuntimeHeight
= image
.runtimeStyle
.height
;
483 image
.runtimeStyle
.width
= 'auto';
484 image
.runtimeStyle
.height
= 'auto';
486 // get the original size
488 var h
= image
.height
;
490 // and remove overides
491 image
.runtimeStyle
.width
= oldRuntimeWidth
;
492 image
.runtimeStyle
.height
= oldRuntimeHeight
;
494 if (arguments
.length
== 3) {
500 } else if (arguments
.length
== 5) {
508 } else if (arguments
.length
== 9) {
518 throw Error('Invalid number of arguments');
521 var d
= this.getCoords_(dx
, dy
);
531 // For some reason that I've now forgotten, using divs didn't work
532 vmlStr
.push(' <g_vml_:group',
533 ' coordsize="', Z
* W
, ',', Z
* H
, '"',
534 ' coordorigin="0,0"' ,
535 ' style="width:', W
, 'px;height:', H
, 'px;position:absolute;');
537 // If filters are necessary (rotation exists), create them
538 // filters are bog-slow, so only create them if abbsolutely necessary
539 // The following check doesn't account for skews (which don't exist
540 // in the canvas spec (yet) anyway.
542 if (this.m_
[0][0] != 1 || this.m_
[0][1]) {
545 // Note the 12/21 reversal
546 filter
.push('M11=', this.m_
[0][0], ',',
547 'M12=', this.m_
[1][0], ',',
548 'M21=', this.m_
[0][1], ',',
549 'M22=', this.m_
[1][1], ',',
550 'Dx=', mr(d
.x
/ Z
), ',',
551 'Dy=', mr(d
.y
/ Z
), '');
553 // Bounding box calculation (need to minimize displayed area so that
554 // filters don't waste time on unused pixels.
556 var c2
= this.getCoords_(dx
+ dw
, dy
);
557 var c3
= this.getCoords_(dx
, dy
+ dh
);
558 var c4
= this.getCoords_(dx
+ dw
, dy
+ dh
);
560 max
.x
= m
.max(max
.x
, c2
.x
, c3
.x
, c4
.x
);
561 max
.y
= m
.max(max
.y
, c2
.y
, c3
.y
, c4
.y
);
563 vmlStr
.push('padding:0 ', mr(max
.x
/ Z), 'px ', mr(max.y / Z
),
564 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
565 filter
.join(''), ", sizingmethod='clip');")
567 vmlStr
.push('top:', mr(d
.y
/ Z), 'px;left:', mr(d.x / Z
), 'px;');
571 '<g_vml_:image src="', image
.src
, '"',
572 ' style="width:', Z
* dw
, 'px;',
573 ' height:', Z
* dh
, 'px;"',
574 ' cropleft="', sx
/ w
, '"',
575 ' croptop="', sy
/ h
, '"',
576 ' cropright="', (w
- sx
- sw
) / w
, '"',
577 ' cropbottom="', (h
- sy
- sh
) / h
, '"',
581 this.element_
.insertAdjacentHTML('BeforeEnd',
585 contextPrototype
.stroke
= function(aFill
) {
587 var lineOpen
= false;
588 var a
= processStyle(aFill
? this.fillStyle
: this.strokeStyle
);
590 var opacity
= a
.alpha
* this.globalAlpha
;
595 lineStr
.push('<g_vml_:shape',
596 ' filled="', !!aFill
, '"',
597 ' style="position:absolute;width:', W
, 'px;height:', H
, 'px;"',
598 ' coordorigin="0 0" coordsize="', Z
* W
, ' ', Z
* H
, '"',
599 ' stroked="', !aFill
, '"',
603 var min
= {x
: null, y
: null};
604 var max
= {x
: null, y
: null};
606 for (var i
= 0; i
< this.currentPath_
.length
; i
++) {
607 var p
= this.currentPath_
[i
];
613 lineStr
.push(' m ', mr(p
.x
), ',', mr(p
.y
));
616 lineStr
.push(' l ', mr(p
.x
), ',', mr(p
.y
));
622 case 'bezierCurveTo':
624 mr(p
.cp1x
), ',', mr(p
.cp1y
), ',',
625 mr(p
.cp2x
), ',', mr(p
.cp2y
), ',',
626 mr(p
.x
), ',', mr(p
.y
));
630 lineStr
.push(' ', p
.type
, ' ',
631 mr(p
.x
- this.arcScaleX_
* p
.radius
), ',',
632 mr(p
.y
- this.arcScaleY_
* p
.radius
), ' ',
633 mr(p
.x
+ this.arcScaleX_
* p
.radius
), ',',
634 mr(p
.y
+ this.arcScaleY_
* p
.radius
), ' ',
635 mr(p
.xStart
), ',', mr(p
.yStart
), ' ',
636 mr(p
.xEnd
), ',', mr(p
.yEnd
));
641 // TODO: Following is broken for curves due to
642 // move to proper paths.
644 // Figure out dimensions so we can do gradient fills
647 if (min
.x
== null || p
.x
< min
.x
) {
650 if (max
.x
== null || p
.x
> max
.x
) {
653 if (min
.y
== null || p
.y
< min
.y
) {
656 if (max
.y
== null || p
.y
> max
.y
) {
664 var lineWidth
= this.lineScale_
* this.lineWidth
;
666 // VML cannot correctly render a line if the width is less than 1px.
667 // In that case, we dilute the color to make the line look thinner.
669 opacity
*= lineWidth
;
674 ' opacity="', opacity
, '"',
675 ' joinstyle="', this.lineJoin
, '"',
676 ' miterlimit="', this.miterLimit
, '"',
677 ' endcap="', processLineCap(this.lineCap
), '"',
678 ' weight="', lineWidth
, 'px"',
679 ' color="', color
, '" />'
681 } else if (typeof this.fillStyle
== 'object') {
682 var fillStyle
= this.fillStyle
;
684 var focus
= {x
: 0, y
: 0};
688 // scale factor for offset
691 if (fillStyle
.type_
== 'gradient') {
692 var x0
= fillStyle
.x0_
/ this.arcScaleX_
;
693 var y0
= fillStyle
.y0_
/ this.arcScaleY_
;
694 var x1
= fillStyle
.x1_
/ this.arcScaleX_
;
695 var y1
= fillStyle
.y1_
/ this.arcScaleY_
;
696 var p0
= this.getCoords_(x0
, y0
);
697 var p1
= this.getCoords_(x1
, y1
);
698 var dx
= p1
.x
- p0
.x
;
699 var dy
= p1
.y
- p0
.y
;
700 angle
= Math
.atan2(dx
, dy
) * 180 / Math
.PI
;
702 // The angle should be a non-negative number.
707 // Very small angles produce an unexpected result because they are
708 // converted to a scientific notation string.
713 var p0
= this.getCoords_(fillStyle
.x0_
, fillStyle
.y0_
);
714 var width
= max
.x
- min
.x
;
715 var height
= max
.y
- min
.y
;
717 x
: (p0
.x
- min
.x
) / width
,
718 y
: (p0
.y
- min
.y
) / height
721 width
/= this.arcScaleX_
* Z
;
722 height
/= this.arcScaleY_
* Z
;
723 var dimension
= m
.max(width
, height
);
724 shift
= 2 * fillStyle
.r0_
/ dimension
;
725 expansion
= 2 * fillStyle
.r1_
/ dimension
- shift
;
728 // We need to sort the color stops in ascending order by offset,
729 // otherwise IE won't interpret it correctly.
730 var stops
= fillStyle
.colors_
;
731 stops
.sort(function(cs1
, cs2
) {
732 return cs1
.offset
- cs2
.offset
;
735 var length
= stops
.length
;
736 var color1
= stops
[0].color
;
737 var color2
= stops
[length
- 1].color
;
738 var opacity1
= stops
[0].alpha
* this.globalAlpha
;
739 var opacity2
= stops
[length
- 1].alpha
* this.globalAlpha
;
742 for (var i
= 0; i
< length
; i
++) {
744 colors
.push(stop
.offset
* expansion
+ shift
+ ' ' + stop
.color
);
747 // When colors attribute is used, the meanings of opacity and o:opacity2
749 lineStr
.push('<g_vml_:fill type="', fillStyle
.type_
, '"',
750 ' method="none" focus="100%"',
751 ' color="', color1
, '"',
752 ' color2="', color2
, '"',
753 ' colors="', colors
.join(','), '"',
754 ' opacity="', opacity2
, '"',
755 ' g_o_:opacity2="', opacity1
, '"',
756 ' angle="', angle
, '"',
757 ' focusposition="', focus
.x
, ',', focus
.y
, '" />');
759 lineStr
.push('<g_vml_:fill color="', color
, '" opacity="', opacity
,
763 lineStr
.push('</g_vml_:shape>');
765 this.element_
.insertAdjacentHTML('beforeEnd', lineStr
.join(''));
768 contextPrototype
.fill
= function() {
772 contextPrototype
.closePath
= function() {
773 this.currentPath_
.push({type
: 'close'});
779 contextPrototype
.getCoords_
= function(aX
, aY
) {
782 x
: Z
* (aX
* m
[0][0] + aY
* m
[1][0] + m
[2][0]) - Z2
,
783 y
: Z
* (aX
* m
[0][1] + aY
* m
[1][1] + m
[2][1]) - Z2
787 contextPrototype
.save
= function() {
790 this.aStack_
.push(o
);
791 this.mStack_
.push(this.m_
);
792 this.m_
= matrixMultiply(createMatrixIdentity(), this.m_
);
795 contextPrototype
.restore
= function() {
796 copyState(this.aStack_
.pop(), this);
797 this.m_
= this.mStack_
.pop();
800 function matrixIsFinite(m
) {
801 for (var j
= 0; j
< 3; j
++) {
802 for (var k
= 0; k
< 2; k
++) {
803 if (!isFinite(m
[j
][k
]) || isNaN(m
[j
][k
])) {
811 function setM(ctx
, m
, updateLineScale
) {
812 if (!matrixIsFinite(m
)) {
817 if (updateLineScale
) {
818 // Get the line scale.
819 // Determinant of this.m_ means how much the area is enlarged by the
820 // transformation. So its square root can be used as a scale factor
822 var det
= m
[0][0] * m
[1][1] - m
[0][1] * m
[1][0];
823 ctx
.lineScale_
= sqrt(abs(det
));
827 contextPrototype
.translate
= function(aX
, aY
) {
834 setM(this, matrixMultiply(m1
, this.m_
), false);
837 contextPrototype
.rotate
= function(aRot
) {
847 setM(this, matrixMultiply(m1
, this.m_
), false);
850 contextPrototype
.scale
= function(aX
, aY
) {
851 this.arcScaleX_
*= aX
;
852 this.arcScaleY_
*= aY
;
859 setM(this, matrixMultiply(m1
, this.m_
), true);
862 contextPrototype
.transform
= function(m11
, m12
, m21
, m22
, dx
, dy
) {
869 setM(this, matrixMultiply(m1
, this.m_
), true);
872 contextPrototype
.setTransform
= function(m11
, m12
, m21
, m22
, dx
, dy
) {
882 /******** STUBS ********/
883 contextPrototype
.clip
= function() {
887 contextPrototype
.arcTo
= function() {
891 contextPrototype
.createPattern
= function() {
892 return new CanvasPattern_
;
895 // Gradient / Pattern Stubs
896 function CanvasGradient_(aType
) {
907 CanvasGradient_
.prototype.addColorStop
= function(aOffset
, aColor
) {
908 aColor
= processStyle(aColor
);
909 this.colors_
.push({offset
: aOffset
,
911 alpha
: aColor
.alpha
});
914 function CanvasPattern_() {}
917 G_vmlCanvasManager
= G_vmlCanvasManager_
;
918 CanvasRenderingContext2D
= CanvasRenderingContext2D_
;
919 CanvasGradient
= CanvasGradient_
;
920 CanvasPattern
= CanvasPattern_
;