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 // do not use inline function because that will leak memory
142 el
.attachEvent('onpropertychange', onPropertyChange
);
143 el
.attachEvent('onresize', onResize
);
145 var attrs
= el
.attributes
;
146 if (attrs
.width
&& attrs
.width
.specified
) {
147 // TODO: use runtimeStyle and coordsize
148 // el.getContext().setWidth_(attrs.width.nodeValue);
149 el
.style
.width
= attrs
.width
.nodeValue
+ 'px';
151 el
.width
= el
.clientWidth
;
153 if (attrs
.height
&& attrs
.height
.specified
) {
154 // TODO: use runtimeStyle and coordsize
155 // el.getContext().setHeight_(attrs.height.nodeValue);
156 el
.style
.height
= attrs
.height
.nodeValue
+ 'px';
158 el
.height
= el
.clientHeight
;
160 //el.getContext().setCoordsize_()
166 function onPropertyChange(e
) {
167 var el
= e
.srcElement
;
169 switch (e
.propertyName
) {
171 el
.style
.width
= el
.attributes
.width
.nodeValue
+ 'px';
172 el
.getContext().clearRect();
175 el
.style
.height
= el
.attributes
.height
.nodeValue
+ 'px';
176 el
.getContext().clearRect();
181 function onResize(e
) {
182 var el
= e
.srcElement
;
184 el
.firstChild
.style
.width
= el
.clientWidth
+ 'px';
185 el
.firstChild
.style
.height
= el
.clientHeight
+ 'px';
189 G_vmlCanvasManager_
.init();
191 // precompute "00" to "FF"
193 for (var i
= 0; i
< 16; i
++) {
194 for (var j
= 0; j
< 16; j
++) {
195 dec2hex
[i
* 16 + j
] = i
.toString(16) + j
.toString(16);
199 function createMatrixIdentity() {
207 function matrixMultiply(m1
, m2
) {
208 var result
= createMatrixIdentity();
210 for (var x
= 0; x
< 3; x
++) {
211 for (var y
= 0; y
< 3; y
++) {
214 for (var z
= 0; z
< 3; z
++) {
215 sum
+= m1
[x
][z
] * m2
[z
][y
];
224 function copyState(o1
, o2
) {
225 o2
.fillStyle
= o1
.fillStyle
;
226 o2
.lineCap
= o1
.lineCap
;
227 o2
.lineJoin
= o1
.lineJoin
;
228 o2
.lineWidth
= o1
.lineWidth
;
229 o2
.miterLimit
= o1
.miterLimit
;
230 o2
.shadowBlur
= o1
.shadowBlur
;
231 o2
.shadowColor
= o1
.shadowColor
;
232 o2
.shadowOffsetX
= o1
.shadowOffsetX
;
233 o2
.shadowOffsetY
= o1
.shadowOffsetY
;
234 o2
.strokeStyle
= o1
.strokeStyle
;
235 o2
.globalAlpha
= o1
.globalAlpha
;
236 o2
.arcScaleX_
= o1
.arcScaleX_
;
237 o2
.arcScaleY_
= o1
.arcScaleY_
;
238 o2
.lineScale_
= o1
.lineScale_
;
241 function processStyle(styleString
) {
244 styleString
= String(styleString
);
245 if (styleString
.substring(0, 3) == 'rgb') {
246 var start
= styleString
.indexOf('(', 3);
247 var end
= styleString
.indexOf(')', start
+ 1);
248 var guts
= styleString
.substring(start
+ 1, end
).split(',');
251 for (var i
= 0; i
< 3; i
++) {
252 str
+= dec2hex
[Number(guts
[i
])];
255 if (guts
.length
== 4 && styleString
.substr(3, 1) == 'a') {
262 return {color
: str
, alpha
: alpha
};
265 function processLineCap(lineCap
) {
278 * This class implements CanvasRenderingContext2D interface as described by
280 * @param {HTMLElement} surfaceElement The element that the 2D context should
283 function CanvasRenderingContext2D_(surfaceElement
) {
284 this.m_
= createMatrixIdentity();
288 this.currentPath_
= [];
290 // Canvas context properties
291 this.strokeStyle
= '#000';
292 this.fillStyle
= '#000';
295 this.lineJoin
= 'miter';
296 this.lineCap
= 'butt';
297 this.miterLimit
= Z
* 1;
298 this.globalAlpha
= 1;
299 this.canvas
= surfaceElement
;
301 var el
= surfaceElement
.ownerDocument
.createElement('div');
302 el
.style
.width
= surfaceElement
.clientWidth
+ 'px';
303 el
.style
.height
= surfaceElement
.clientHeight
+ 'px';
304 el
.style
.overflow
= 'hidden';
305 el
.style
.position
= 'absolute';
306 surfaceElement
.appendChild(el
);
314 var contextPrototype
= CanvasRenderingContext2D_
.prototype;
315 contextPrototype
.clearRect
= function() {
316 this.element_
.innerHTML
= '';
317 this.currentPath_
= [];
320 contextPrototype
.beginPath
= function() {
321 // TODO: Branch current matrix so that save/restore has no effect
322 // as per safari docs.
323 this.currentPath_
= [];
326 contextPrototype
.moveTo
= function(aX
, aY
) {
327 var p
= this.getCoords_(aX
, aY
);
328 this.currentPath_
.push({type
: 'moveTo', x
: p
.x
, y
: p
.y
});
329 this.currentX_
= p
.x
;
330 this.currentY_
= p
.y
;
333 contextPrototype
.lineTo
= function(aX
, aY
) {
334 var p
= this.getCoords_(aX
, aY
);
335 this.currentPath_
.push({type
: 'lineTo', x
: p
.x
, y
: p
.y
});
337 this.currentX_
= p
.x
;
338 this.currentY_
= p
.y
;
341 contextPrototype
.bezierCurveTo
= function(aCP1x
, aCP1y
,
344 var p
= this.getCoords_(aX
, aY
);
345 var cp1
= this.getCoords_(aCP1x
, aCP1y
);
346 var cp2
= this.getCoords_(aCP2x
, aCP2y
);
347 bezierCurveTo(this, cp1
, cp2
, p
);
350 // Helper function that takes the already fixed cordinates.
351 function bezierCurveTo(self
, cp1
, cp2
, p
) {
352 self
.currentPath_
.push({
353 type
: 'bezierCurveTo',
361 self
.currentX_
= p
.x
;
362 self
.currentY_
= p
.y
;
365 contextPrototype
.quadraticCurveTo
= function(aCPx
, aCPy
, aX
, aY
) {
366 // the following is lifted almost directly from
367 // http://developer.mozilla.org/en
/docs/Canvas_tutorial
:Drawing_shapes
369 var cp
= this.getCoords_(aCPx
, aCPy
);
370 var p
= this.getCoords_(aX
, aY
);
373 x
: this.currentX_
+ 2.0 / 3.0 * (cp
.x
- this.currentX_
),
374 y
: this.currentY_
+ 2.0 / 3.0 * (cp
.y
- this.currentY_
)
377 x
: cp1
.x
+ (p
.x
- this.currentX_
) / 3.0,
378 y
: cp1
.y
+ (p
.y
- this.currentY_
) / 3.0
381 bezierCurveTo(this, cp1
, cp2
, p
);
384 contextPrototype
.arc
= function(aX
, aY
, aRadius
,
385 aStartAngle
, aEndAngle
, aClockwise
) {
387 var arcType
= aClockwise
? 'at' : 'wa';
389 var xStart
= aX
+ mc(aStartAngle
) * aRadius
- Z2
;
390 var yStart
= aY
+ ms(aStartAngle
) * aRadius
- Z2
;
392 var xEnd
= aX
+ mc(aEndAngle
) * aRadius
- Z2
;
393 var yEnd
= aY
+ ms(aEndAngle
) * aRadius
- Z2
;
395 // IE won't render arches drawn counter clockwise if xStart == xEnd.
396 if (xStart
== xEnd
&& !aClockwise
) {
397 xStart
+= 0.125; // Offset xStart by 1/80 of a pixel
. Use something
398 // that can be represented in binary
401 var p
= this.getCoords_(aX
, aY
);
402 var pStart
= this.getCoords_(xStart
, yStart
);
403 var pEnd
= this.getCoords_(xEnd
, yEnd
);
405 this.currentPath_
.push({type
: arcType
,
416 contextPrototype
.rect
= function(aX
, aY
, aWidth
, aHeight
) {
418 this.lineTo(aX
+ aWidth
, aY
);
419 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
420 this.lineTo(aX
, aY
+ aHeight
);
424 contextPrototype
.strokeRect
= function(aX
, aY
, aWidth
, aHeight
) {
425 // Will destroy any existing path (same as FF behaviour)
428 this.lineTo(aX
+ aWidth
, aY
);
429 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
430 this.lineTo(aX
, aY
+ aHeight
);
433 this.currentPath_
= [];
436 contextPrototype
.fillRect
= function(aX
, aY
, aWidth
, aHeight
) {
437 // Will destroy any existing path (same as FF behaviour)
440 this.lineTo(aX
+ aWidth
, aY
);
441 this.lineTo(aX
+ aWidth
, aY
+ aHeight
);
442 this.lineTo(aX
, aY
+ aHeight
);
445 this.currentPath_
= [];
448 contextPrototype
.createLinearGradient
= function(aX0
, aY0
, aX1
, aY1
) {
449 var gradient
= new CanvasGradient_('gradient');
457 contextPrototype
.createRadialGradient
= function(aX0
, aY0
, aR0
,
459 var gradient
= new CanvasGradient_('gradientradial');
469 contextPrototype
.drawImage
= function(image
, var_args
) {
470 var dx
, dy
, dw
, dh
, sx
, sy
, sw
, sh
;
472 // to find the original width we overide the width and height
473 var oldRuntimeWidth
= image
.runtimeStyle
.width
;
474 var oldRuntimeHeight
= image
.runtimeStyle
.height
;
475 image
.runtimeStyle
.width
= 'auto';
476 image
.runtimeStyle
.height
= 'auto';
478 // get the original size
480 var h
= image
.height
;
482 // and remove overides
483 image
.runtimeStyle
.width
= oldRuntimeWidth
;
484 image
.runtimeStyle
.height
= oldRuntimeHeight
;
486 if (arguments
.length
== 3) {
492 } else if (arguments
.length
== 5) {
500 } else if (arguments
.length
== 9) {
510 throw Error('Invalid number of arguments');
513 var d
= this.getCoords_(dx
, dy
);
523 // For some reason that I've now forgotten, using divs didn't work
524 vmlStr
.push(' <g_vml_:group',
525 ' coordsize="', Z
* W
, ',', Z
* H
, '"',
526 ' coordorigin="0,0"' ,
527 ' style="width:', W
, 'px;height:', H
, 'px;position:absolute;');
529 // If filters are necessary (rotation exists), create them
530 // filters are bog-slow, so only create them if abbsolutely necessary
531 // The following check doesn't account for skews (which don't exist
532 // in the canvas spec (yet) anyway.
534 if (this.m_
[0][0] != 1 || this.m_
[0][1]) {
537 // Note the 12/21 reversal
538 filter
.push('M11=', this.m_
[0][0], ',',
539 'M12=', this.m_
[1][0], ',',
540 'M21=', this.m_
[0][1], ',',
541 'M22=', this.m_
[1][1], ',',
542 'Dx=', mr(d
.x
/ Z
), ',',
543 'Dy=', mr(d
.y
/ Z
), '');
545 // Bounding box calculation (need to minimize displayed area so that
546 // filters don't waste time on unused pixels.
548 var c2
= this.getCoords_(dx
+ dw
, dy
);
549 var c3
= this.getCoords_(dx
, dy
+ dh
);
550 var c4
= this.getCoords_(dx
+ dw
, dy
+ dh
);
552 max
.x
= m
.max(max
.x
, c2
.x
, c3
.x
, c4
.x
);
553 max
.y
= m
.max(max
.y
, c2
.y
, c3
.y
, c4
.y
);
555 vmlStr
.push('padding:0 ', mr(max
.x
/ Z), 'px ', mr(max.y / Z
),
556 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
557 filter
.join(''), ", sizingmethod='clip');")
559 vmlStr
.push('top:', mr(d
.y
/ Z), 'px;left:', mr(d.x / Z
), 'px;');
563 '<g_vml_:image src="', image
.src
, '"',
564 ' style="width:', Z
* dw
, 'px;',
565 ' height:', Z
* dh
, 'px;"',
566 ' cropleft="', sx
/ w
, '"',
567 ' croptop="', sy
/ h
, '"',
568 ' cropright="', (w
- sx
- sw
) / w
, '"',
569 ' cropbottom="', (h
- sy
- sh
) / h
, '"',
573 this.element_
.insertAdjacentHTML('BeforeEnd',
577 contextPrototype
.stroke
= function(aFill
) {
579 var lineOpen
= false;
580 var a
= processStyle(aFill
? this.fillStyle
: this.strokeStyle
);
582 var opacity
= a
.alpha
* this.globalAlpha
;
587 lineStr
.push('<g_vml_:shape',
588 ' filled="', !!aFill
, '"',
589 ' style="position:absolute;width:', W
, 'px;height:', H
, 'px;"',
590 ' coordorigin="0 0" coordsize="', Z
* W
, ' ', Z
* H
, '"',
591 ' stroked="', !aFill
, '"',
595 var min
= {x
: null, y
: null};
596 var max
= {x
: null, y
: null};
598 for (var i
= 0; i
< this.currentPath_
.length
; i
++) {
599 var p
= this.currentPath_
[i
];
605 lineStr
.push(' m ', mr(p
.x
), ',', mr(p
.y
));
608 lineStr
.push(' l ', mr(p
.x
), ',', mr(p
.y
));
614 case 'bezierCurveTo':
616 mr(p
.cp1x
), ',', mr(p
.cp1y
), ',',
617 mr(p
.cp2x
), ',', mr(p
.cp2y
), ',',
618 mr(p
.x
), ',', mr(p
.y
));
622 lineStr
.push(' ', p
.type
, ' ',
623 mr(p
.x
- this.arcScaleX_
* p
.radius
), ',',
624 mr(p
.y
- this.arcScaleY_
* p
.radius
), ' ',
625 mr(p
.x
+ this.arcScaleX_
* p
.radius
), ',',
626 mr(p
.y
+ this.arcScaleY_
* p
.radius
), ' ',
627 mr(p
.xStart
), ',', mr(p
.yStart
), ' ',
628 mr(p
.xEnd
), ',', mr(p
.yEnd
));
633 // TODO: Following is broken for curves due to
634 // move to proper paths.
636 // Figure out dimensions so we can do gradient fills
639 if (min
.x
== null || p
.x
< min
.x
) {
642 if (max
.x
== null || p
.x
> max
.x
) {
645 if (min
.y
== null || p
.y
< min
.y
) {
648 if (max
.y
== null || p
.y
> max
.y
) {
656 var lineWidth
= this.lineScale_
* this.lineWidth
;
658 // VML cannot correctly render a line if the width is less than 1px.
659 // In that case, we dilute the color to make the line look thinner.
661 opacity
*= lineWidth
;
666 ' opacity="', opacity
, '"',
667 ' joinstyle="', this.lineJoin
, '"',
668 ' miterlimit="', this.miterLimit
, '"',
669 ' endcap="', processLineCap(this.lineCap
), '"',
670 ' weight="', lineWidth
, 'px"',
671 ' color="', color
, '" />'
673 } else if (typeof this.fillStyle
== 'object') {
674 var fillStyle
= this.fillStyle
;
676 var focus
= {x
: 0, y
: 0};
680 // scale factor for offset
683 if (fillStyle
.type_
== 'gradient') {
684 var x0
= fillStyle
.x0_
/ this.arcScaleX_
;
685 var y0
= fillStyle
.y0_
/ this.arcScaleY_
;
686 var x1
= fillStyle
.x1_
/ this.arcScaleX_
;
687 var y1
= fillStyle
.y1_
/ this.arcScaleY_
;
688 var p0
= this.getCoords_(x0
, y0
);
689 var p1
= this.getCoords_(x1
, y1
);
690 var dx
= p1
.x
- p0
.x
;
691 var dy
= p1
.y
- p0
.y
;
692 angle
= Math
.atan2(dx
, dy
) * 180 / Math
.PI
;
694 // The angle should be a non-negative number.
699 // Very small angles produce an unexpected result because they are
700 // converted to a scientific notation string.
705 var p0
= this.getCoords_(fillStyle
.x0_
, fillStyle
.y0_
);
706 var width
= max
.x
- min
.x
;
707 var height
= max
.y
- min
.y
;
709 x
: (p0
.x
- min
.x
) / width
,
710 y
: (p0
.y
- min
.y
) / height
713 width
/= this.arcScaleX_
* Z
;
714 height
/= this.arcScaleY_
* Z
;
715 var dimension
= m
.max(width
, height
);
716 shift
= 2 * fillStyle
.r0_
/ dimension
;
717 expansion
= 2 * fillStyle
.r1_
/ dimension
- shift
;
720 // We need to sort the color stops in ascending order by offset,
721 // otherwise IE won't interpret it correctly.
722 var stops
= fillStyle
.colors_
;
723 stops
.sort(function(cs1
, cs2
) {
724 return cs1
.offset
- cs2
.offset
;
727 var length
= stops
.length
;
728 var color1
= stops
[0].color
;
729 var color2
= stops
[length
- 1].color
;
730 var opacity1
= stops
[0].alpha
* this.globalAlpha
;
731 var opacity2
= stops
[length
- 1].alpha
* this.globalAlpha
;
734 for (var i
= 0; i
< length
; i
++) {
736 colors
.push(stop
.offset
* expansion
+ shift
+ ' ' + stop
.color
);
739 // When colors attribute is used, the meanings of opacity and o:opacity2
741 lineStr
.push('<g_vml_:fill type="', fillStyle
.type_
, '"',
742 ' method="none" focus="100%"',
743 ' color="', color1
, '"',
744 ' color2="', color2
, '"',
745 ' colors="', colors
.join(','), '"',
746 ' opacity="', opacity2
, '"',
747 ' g_o_:opacity2="', opacity1
, '"',
748 ' angle="', angle
, '"',
749 ' focusposition="', focus
.x
, ',', focus
.y
, '" />');
751 lineStr
.push('<g_vml_:fill color="', color
, '" opacity="', opacity
,
755 lineStr
.push('</g_vml_:shape>');
757 this.element_
.insertAdjacentHTML('beforeEnd', lineStr
.join(''));
760 contextPrototype
.fill
= function() {
764 contextPrototype
.closePath
= function() {
765 this.currentPath_
.push({type
: 'close'});
771 contextPrototype
.getCoords_
= function(aX
, aY
) {
774 x
: Z
* (aX
* m
[0][0] + aY
* m
[1][0] + m
[2][0]) - Z2
,
775 y
: Z
* (aX
* m
[0][1] + aY
* m
[1][1] + m
[2][1]) - Z2
779 contextPrototype
.save
= function() {
782 this.aStack_
.push(o
);
783 this.mStack_
.push(this.m_
);
784 this.m_
= matrixMultiply(createMatrixIdentity(), this.m_
);
787 contextPrototype
.restore
= function() {
788 copyState(this.aStack_
.pop(), this);
789 this.m_
= this.mStack_
.pop();
792 contextPrototype
.translate
= function(aX
, aY
) {
799 this.m_
= matrixMultiply(m1
, this.m_
);
802 contextPrototype
.rotate
= function(aRot
) {
812 this.m_
= matrixMultiply(m1
, this.m_
);
815 contextPrototype
.scale
= function(aX
, aY
) {
816 this.arcScaleX_
*= aX
;
817 this.arcScaleY_
*= aY
;
824 var m
= this.m_
= matrixMultiply(m1
, this.m_
);
826 // Get the line scale.
827 // Determinant of this.m_ means how much the area is enlarged by the
828 // transformation. So its square root can be used as a scale factor
830 var det
= m
[0][0] * m
[1][1] - m
[0][1] * m
[1][0];
831 this.lineScale_
= sqrt(abs(det
));
834 /******** STUBS ********/
835 contextPrototype
.clip
= function() {
839 contextPrototype
.arcTo
= function() {
843 contextPrototype
.createPattern
= function() {
844 return new CanvasPattern_
;
847 // Gradient / Pattern Stubs
848 function CanvasGradient_(aType
) {
859 CanvasGradient_
.prototype.addColorStop
= function(aOffset
, aColor
) {
860 aColor
= processStyle(aColor
);
861 this.colors_
.push({offset
: aOffset
,
863 alpha
: aColor
.alpha
});
866 function CanvasPattern_() {}
869 G_vmlCanvasManager
= G_vmlCanvasManager_
;
870 CanvasRenderingContext2D
= CanvasRenderingContext2D_
;
871 CanvasGradient
= CanvasGradient_
;
872 CanvasPattern
= CanvasPattern_
;