4 * Copyright (c) 2009 Tim Cameron Ryan
5 * Copyright (c) 2009-2011 FlashCanvas Project
6 * Released under the MIT/X License
10 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
11 // http://dev.w3.org/html5/spec/the-canvas-element.html
13 // If the browser is IE and does not support HTML5 Canvas
14 if (window
["ActiveXObject"] && !window
["CanvasRenderingContext2D"]) {
16 (function(window
, document
, undefined) {
23 var CANVAS
= "canvas";
24 var CANVAS_RENDERING_CONTEXT_2D
= "CanvasRenderingContext2D";
25 var CANVAS_GRADIENT
= "CanvasGradient";
26 var CANVAS_PATTERN
= "CanvasPattern";
27 var FLASH_CANVAS
= "FlashCanvas";
28 var G_VML_CANVAS_MANAGER
= "G_vmlCanvasManager";
29 var OBJECT_ID_PREFIX
= "external";
30 var ON_FOCUS
= "onfocus";
31 var ON_PROPERTY_CHANGE
= "onpropertychange";
32 var ON_READY_STATE_CHANGE
= "onreadystatechange";
33 var ON_UNLOAD
= "onunload";
35 var config
= window
[FLASH_CANVAS
+ "Options"] || {};
36 var BASE_URL
= config
["swfPath"] || getScriptUrl().replace(/[^\/]+$/, "");
37 var SWF_URL
= BASE_URL
+ "flashcanvas.swf";
40 var INDEX_SIZE_ERR
= 1;
41 var NOT_SUPPORTED_ERR
= 9;
42 var INVALID_STATE_ERR
= 11;
44 var TYPE_MISMATCH_ERR
= 17;
45 var SECURITY_ERR
= 18;
50 function Lookup(array
) {
51 for (var i
= 0, n
= array
.length
; i
< n
; i
++)
55 var properties
= new Lookup([
59 // CanvasRenderingContext2D
68 "globalCompositeOperation",
71 "createLinearGradient",
72 "createRadialGradient",
118 // Whether swf is ready for use
121 // Monitor the number of loading files
127 // SPAN element embedded in the canvas
134 var CanvasRenderingContext2D = function(canvas
, swf
) {
135 // back-reference to the canvas
136 this.canvas
= canvas
;
138 // back-reference to the swf
141 // unique ID of canvas
142 this._canvasId
= swf
.id
.slice(8);
144 // initialize drawing states
147 // Count CanvasGradient and CanvasPattern objects
148 this._gradientPatternId
= 0;
150 // Directionality of the canvas element
151 this._direction
= "";
153 // frame update interval
155 setInterval(function() {
156 if (lock
[self
._canvasId
] === 0) {
157 self
._executeCommand();
162 CanvasRenderingContext2D
.prototype = {
168 // write all properties
169 this._setCompositing();
171 this._setStrokeStyle();
172 this._setFillStyle();
173 this._setLineStyles();
174 this._setFontStyles();
177 this._stateStack
.push([
179 this._globalCompositeOperation
,
195 this._queue
.push(properties
.save
);
198 restore: function() {
200 var stateStack
= this._stateStack
;
201 if (stateStack
.length
) {
202 var state
= stateStack
.pop();
203 this.globalAlpha
= state
[0];
204 this.globalCompositeOperation
= state
[1];
205 this.strokeStyle
= state
[2];
206 this.fillStyle
= state
[3];
207 this.lineWidth
= state
[4];
208 this.lineCap
= state
[5];
209 this.lineJoin
= state
[6];
210 this.miterLimit
= state
[7];
211 this.shadowOffsetX
= state
[8];
212 this.shadowOffsetY
= state
[9];
213 this.shadowBlur
= state
[10];
214 this.shadowColor
= state
[11];
215 this.font
= state
[12];
216 this.textAlign
= state
[13];
217 this.textBaseline
= state
[14];
220 this._queue
.push(properties
.restore
);
227 scale: function(x
, y
) {
228 this._queue
.push(properties
.scale
, x
, y
);
231 rotate: function(angle
) {
232 this._queue
.push(properties
.rotate
, angle
);
235 translate: function(x
, y
) {
236 this._queue
.push(properties
.translate
, x
, y
);
239 transform: function(m11
, m12
, m21
, m22
, dx
, dy
) {
240 this._queue
.push(properties
.transform
, m11
, m12
, m21
, m22
, dx
, dy
);
243 setTransform: function(m11
, m12
, m21
, m22
, dx
, dy
) {
244 this._queue
.push(properties
.setTransform
, m11
, m12
, m21
, m22
, dx
, dy
);
251 _setCompositing: function() {
252 var queue
= this._queue
;
253 if (this._globalAlpha
!== this.globalAlpha
) {
254 this._globalAlpha
= this.globalAlpha
;
255 queue
.push(properties
.globalAlpha
, this._globalAlpha
);
257 if (this._globalCompositeOperation
!== this.globalCompositeOperation
) {
258 this._globalCompositeOperation
= this.globalCompositeOperation
;
259 queue
.push(properties
.globalCompositeOperation
, this._globalCompositeOperation
);
267 _setStrokeStyle: function() {
268 if (this._strokeStyle
!== this.strokeStyle
) {
269 var style
= this._strokeStyle
= this.strokeStyle
;
270 this._queue
.push(properties
.strokeStyle
, (typeof style
=== "object") ? style
.id
: style
);
274 _setFillStyle: function() {
275 if (this._fillStyle
!== this.fillStyle
) {
276 var style
= this._fillStyle
= this.fillStyle
;
277 this._queue
.push(properties
.fillStyle
, (typeof style
=== "object") ? style
.id
: style
);
281 createLinearGradient: function(x0
, y0
, x1
, y1
) {
282 // If any of the arguments are not finite numbers, throws a
283 // NOT_SUPPORTED_ERR exception.
284 if (!(isFinite(x0
) && isFinite(y0
) && isFinite(x1
) && isFinite(y1
))) {
285 throwException(NOT_SUPPORTED_ERR
);
288 this._queue
.push(properties
.createLinearGradient
, x0
, y0
, x1
, y1
);
289 return new CanvasGradient(this);
292 createRadialGradient: function(x0
, y0
, r0
, x1
, y1
, r1
) {
293 // If any of the arguments are not finite numbers, throws a
294 // NOT_SUPPORTED_ERR exception.
295 if (!(isFinite(x0
) && isFinite(y0
) && isFinite(r0
) &&
296 isFinite(x1
) && isFinite(y1
) && isFinite(r1
))) {
297 throwException(NOT_SUPPORTED_ERR
);
300 // If either of the radii are negative, throws an INDEX_SIZE_ERR
302 if (r0
< 0 || r1
< 0) {
303 throwException(INDEX_SIZE_ERR
);
306 this._queue
.push(properties
.createRadialGradient
, x0
, y0
, r0
, x1
, y1
, r1
);
307 return new CanvasGradient(this);
310 createPattern: function(image
, repetition
) {
311 // If the image is null, the implementation must raise a
312 // TYPE_MISMATCH_ERR exception.
314 throwException(TYPE_MISMATCH_ERR
);
317 var tagName
= image
.tagName
, src
;
318 var canvasId
= this._canvasId
;
320 // If the first argument isn't an img, canvas, or video element,
321 // throws a TYPE_MISMATCH_ERR exception.
323 tagName
= tagName
.toLowerCase();
324 if (tagName
=== "img") {
325 src
= image
.getAttribute("src", 2);
326 } else if (tagName
=== CANVAS
|| tagName
=== "video") {
327 // For now, only HTMLImageElement is supported.
330 throwException(TYPE_MISMATCH_ERR
);
334 // Additionally, we accept any object that has a src property.
335 // This is useful when you'd like to specify a long data URI.
336 else if (image
.src
) {
339 throwException(TYPE_MISMATCH_ERR
);
342 // If the second argument isn't one of the allowed values, throws a
343 // SYNTAX_ERR exception.
344 if (!(repetition
=== "repeat" || repetition
=== "no-repeat" ||
345 repetition
=== "repeat-x" || repetition
=== "repeat-y" ||
346 repetition
=== "" || repetition
=== NULL
)) {
347 throwException(SYNTAX_ERR
);
350 // Special characters in the filename need escaping.
351 this._queue
.push(properties
.createPattern
, encodeXML(src
), repetition
);
353 if (isReady
[canvasId
]) {
354 this._executeCommand();
358 return new CanvasPattern(this);
365 _setLineStyles: function() {
366 var queue
= this._queue
;
367 if (this._lineWidth
!== this.lineWidth
) {
368 this._lineWidth
= this.lineWidth
;
369 queue
.push(properties
.lineWidth
, this._lineWidth
);
371 if (this._lineCap
!== this.lineCap
) {
372 this._lineCap
= this.lineCap
;
373 queue
.push(properties
.lineCap
, this._lineCap
);
375 if (this._lineJoin
!== this.lineJoin
) {
376 this._lineJoin
= this.lineJoin
;
377 queue
.push(properties
.lineJoin
, this._lineJoin
);
379 if (this._miterLimit
!== this.miterLimit
) {
380 this._miterLimit
= this.miterLimit
;
381 queue
.push(properties
.miterLimit
, this._miterLimit
);
389 _setShadows: function() {
390 var queue
= this._queue
;
391 if (this._shadowOffsetX
!== this.shadowOffsetX
) {
392 this._shadowOffsetX
= this.shadowOffsetX
;
393 queue
.push(properties
.shadowOffsetX
, this._shadowOffsetX
);
395 if (this._shadowOffsetY
!== this.shadowOffsetY
) {
396 this._shadowOffsetY
= this.shadowOffsetY
;
397 queue
.push(properties
.shadowOffsetY
, this._shadowOffsetY
);
399 if (this._shadowBlur
!== this.shadowBlur
) {
400 this._shadowBlur
= this.shadowBlur
;
401 queue
.push(properties
.shadowBlur
, this._shadowBlur
);
403 if (this._shadowColor
!== this.shadowColor
) {
404 this._shadowColor
= this.shadowColor
;
405 queue
.push(properties
.shadowColor
, this._shadowColor
);
413 clearRect: function(x
, y
, w
, h
) {
414 this._queue
.push(properties
.clearRect
, x
, y
, w
, h
);
417 fillRect: function(x
, y
, w
, h
) {
418 this._setCompositing();
420 this._setFillStyle();
421 this._queue
.push(properties
.fillRect
, x
, y
, w
, h
);
424 strokeRect: function(x
, y
, w
, h
) {
425 this._setCompositing();
427 this._setStrokeStyle();
428 this._setLineStyles();
429 this._queue
.push(properties
.strokeRect
, x
, y
, w
, h
);
436 beginPath: function() {
437 this._queue
.push(properties
.beginPath
);
440 closePath: function() {
441 this._queue
.push(properties
.closePath
);
444 moveTo: function(x
, y
) {
445 this._queue
.push(properties
.moveTo
, x
, y
);
448 lineTo: function(x
, y
) {
449 this._queue
.push(properties
.lineTo
, x
, y
);
452 quadraticCurveTo: function(cpx
, cpy
, x
, y
) {
453 this._queue
.push(properties
.quadraticCurveTo
, cpx
, cpy
, x
, y
);
456 bezierCurveTo: function(cp1x
, cp1y
, cp2x
, cp2y
, x
, y
) {
457 this._queue
.push(properties
.bezierCurveTo
, cp1x
, cp1y
, cp2x
, cp2y
, x
, y
);
460 arcTo: function(x1
, y1
, x2
, y2
, radius
) {
461 // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
462 if (radius
< 0 && isFinite(radius
)) {
463 throwException(INDEX_SIZE_ERR
);
466 this._queue
.push(properties
.arcTo
, x1
, y1
, x2
, y2
, radius
);
469 rect: function(x
, y
, w
, h
) {
470 this._queue
.push(properties
.rect
, x
, y
, w
, h
);
473 arc: function(x
, y
, radius
, startAngle
, endAngle
, anticlockwise
) {
474 // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
475 if (radius
< 0 && isFinite(radius
)) {
476 throwException(INDEX_SIZE_ERR
);
479 this._queue
.push(properties
.arc
, x
, y
, radius
, startAngle
, endAngle
, anticlockwise
? 1 : 0);
483 this._setCompositing();
485 this._setFillStyle();
486 this._queue
.push(properties
.fill
);
490 this._setCompositing();
492 this._setStrokeStyle();
493 this._setLineStyles();
494 this._queue
.push(properties
.stroke
);
498 this._queue
.push(properties
.clip
);
501 isPointInPath: function(x
, y
) {
509 _setFontStyles: function() {
510 var queue
= this._queue
;
511 if (this._font
!== this.font
) {
513 var span
= spans
[this._canvasId
];
514 span
.style
.font
= this._font
= this.font
;
516 var style
= span
.currentStyle
;
517 var fontSize
= span
.offsetHeight
;
518 var font
= [style
.fontStyle
, style
.fontWeight
, fontSize
, style
.fontFamily
].join(" ");
519 queue
.push(properties
.font
, font
);
521 // If this.font cannot be parsed as a CSS font value, then it
525 if (this._textAlign
!== this.textAlign
) {
526 this._textAlign
= this.textAlign
;
527 queue
.push(properties
.textAlign
, this._textAlign
);
529 if (this._textBaseline
!== this.textBaseline
) {
530 this._textBaseline
= this.textBaseline
;
531 queue
.push(properties
.textBaseline
, this._textBaseline
);
533 if (this._direction
!== this.canvas
.currentStyle
.direction
) {
534 this._direction
= this.canvas
.currentStyle
.direction
;
535 queue
.push(properties
.direction
, this._direction
);
539 fillText: function(text
, x
, y
, maxWidth
) {
540 this._setCompositing();
541 this._setFillStyle();
543 this._setFontStyles();
544 this._queue
.push(properties
.fillText
, encodeXML(text
), x
, y
,
545 maxWidth
=== undefined ? Infinity
: maxWidth
);
548 strokeText: function(text
, x
, y
, maxWidth
) {
549 this._setCompositing();
550 this._setStrokeStyle();
552 this._setFontStyles();
553 this._queue
.push(properties
.strokeText
, encodeXML(text
), x
, y
,
554 maxWidth
=== undefined ? Infinity
: maxWidth
);
557 measureText: function(text
) {
558 var span
= spans
[this._canvasId
];
560 span
.style
.font
= this.font
;
562 // If this.font cannot be parsed as a CSS font value, then it must
566 // Replace space characters with tab characters because innerText
567 // removes trailing white spaces.
568 span
.innerText
= text
.replace(/[ \n\f\r]/g, "\t");
570 return new TextMetrics(span
.offsetWidth
);
577 drawImage: function(image
, x1
, y1
, w1
, h1
, x2
, y2
, w2
, h2
) {
578 // If the image is null, the implementation must raise a
579 // TYPE_MISMATCH_ERR exception.
581 throwException(TYPE_MISMATCH_ERR
);
584 var tagName
= image
.tagName
, src
, argc
= arguments
.length
;
585 var canvasId
= this._canvasId
;
587 // If the first argument isn't an img, canvas, or video element,
588 // throws a TYPE_MISMATCH_ERR exception.
590 tagName
= tagName
.toLowerCase();
591 if (tagName
=== "img") {
592 src
= image
.getAttribute("src", 2);
593 } else if (tagName
=== CANVAS
|| tagName
=== "video") {
594 // For now, only HTMLImageElement is supported.
597 throwException(TYPE_MISMATCH_ERR
);
601 // Additionally, we accept any object that has a src property.
602 // This is useful when you'd like to specify a long data URI.
603 else if (image
.src
) {
606 throwException(TYPE_MISMATCH_ERR
);
609 this._setCompositing();
612 // Special characters in the filename need escaping.
613 src
= encodeXML(src
);
616 this._queue
.push(properties
.drawImage
, argc
, src
, x1
, y1
);
617 } else if (argc
=== 5) {
618 this._queue
.push(properties
.drawImage
, argc
, src
, x1
, y1
, w1
, h1
);
619 } else if (argc
=== 9) {
620 // If one of the sw or sh arguments is zero, the implementation
621 // must raise an INDEX_SIZE_ERR exception.
622 if (w1
=== 0 || h1
=== 0) {
623 throwException(INDEX_SIZE_ERR
);
626 this._queue
.push(properties
.drawImage
, argc
, src
, x1
, y1
, w1
, h1
, x2
, y2
, w2
, h2
);
631 if (isReady
[canvasId
]) {
632 this._executeCommand();
641 // ImageData createImageData(in float sw, in float sh);
642 // ImageData createImageData(in ImageData imagedata);
643 createImageData: function() {
647 // ImageData getImageData(in float sx, in float sy, in float sw, in float sh);
648 getImageData: function(sx
, sy
, sw
, sh
) {
652 // void putImageData(in ImageData imagedata, in float dx, in float dy, [Optional] in float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight);
653 putImageData: function(imagedata
, dx
, dy
, dirtyX
, dirtyY
, dirtyWidth
, dirtyHeight
) {
661 _initialize: function() {
663 this.globalAlpha
= this._globalAlpha
= 1.0;
664 this.globalCompositeOperation
= this._globalCompositeOperation
= "source-over";
667 this.strokeStyle
= this._strokeStyle
= "#000000";
668 this.fillStyle
= this._fillStyle
= "#000000";
671 this.lineWidth
= this._lineWidth
= 1.0;
672 this.lineCap
= this._lineCap
= "butt";
673 this.lineJoin
= this._lineJoin
= "miter";
674 this.miterLimit
= this._miterLimit
= 10.0;
677 this.shadowOffsetX
= this._shadowOffsetX
= 0;
678 this.shadowOffsetY
= this._shadowOffsetY
= 0;
679 this.shadowBlur
= this._shadowBlur
= 0;
680 this.shadowColor
= this._shadowColor
= "rgba(0, 0, 0, 0.0)";
683 this.font
= this._font
= "10px sans-serif";
684 this.textAlign
= this._textAlign
= "start";
685 this.textBaseline
= this._textBaseline
= "alphabetic";
690 // stack of drawing states
691 this._stateStack
= [];
695 var queue
= this._queue
;
700 _executeCommand: function() {
702 var commands
= this._flush();
703 if (commands
.length
> 0) {
704 return eval(this._swf
.CallFunction(
705 '<invoke name="executeCommand" returntype="javascript"><arguments><string>'
706 + commands
.join("�") + "</string></arguments></invoke>"
711 _resize: function(width
, height
) {
712 // Flush commands in the queue
713 this._executeCommand();
715 // Clear back to the initial state
718 // Adjust the size of Flash to that of the canvas
720 this._swf
.width
= width
;
723 this._swf
.height
= height
;
726 // Execute a resize command at the start of the next frame
727 this._queue
.push(properties
.resize
, width
, height
);
732 * CanvasGradient stub
735 var CanvasGradient = function(ctx
) {
737 this.id
= ctx
._gradientPatternId
++;
740 CanvasGradient
.prototype = {
741 addColorStop: function(offset
, color
) {
742 // Throws an INDEX_SIZE_ERR exception if the offset is out of range.
743 if (isNaN(offset
) || offset
< 0 || offset
> 1) {
744 throwException(INDEX_SIZE_ERR
);
747 this._ctx
._queue
.push(properties
.addColorStop
, this.id
, offset
, color
);
755 var CanvasPattern = function(ctx
) {
756 this.id
= ctx
._gradientPatternId
++;
763 var TextMetrics = function(width
) {
771 var DOMException = function(code
) {
773 this.message
= DOMExceptionNames
[code
];
776 DOMException
.prototype = new Error
;
778 var DOMExceptionNames
= {
780 9: "NOT_SUPPORTED_ERR",
781 11: "INVALID_STATE_ERR",
783 17: "TYPE_MISMATCH_ERR",
791 function onReadyStateChange() {
792 if (document
.readyState
=== "complete") {
793 document
.detachEvent(ON_READY_STATE_CHANGE
, onReadyStateChange
);
795 var canvases
= document
.getElementsByTagName(CANVAS
);
796 for (var i
= 0, n
= canvases
.length
; i
< n
; ++i
) {
797 FlashCanvas
.initElement(canvases
[i
]);
803 // forward the event to the parent
804 var swf
= event
.srcElement
, canvas
= swf
.parentNode
;
809 function onPropertyChange() {
810 var prop
= event
.propertyName
;
811 if (prop
=== "width" || prop
=== "height") {
812 var canvas
= event
.srcElement
;
813 var value
= canvas
[prop
];
814 var number
= parseInt(value
, 10);
816 if (isNaN(number
) || number
< 0) {
817 number
= (prop
=== "width") ? 300 : 150;
820 if (value
=== number
) {
821 canvas
.style
[prop
] = number
+ "px";
822 canvas
.getContext("2d")._resize(canvas
.width
, canvas
.height
);
824 canvas
[prop
] = number
;
829 function onUnload() {
830 window
.detachEvent(ON_UNLOAD
, onUnload
);
832 for (var canvasId
in canvases
) {
833 var canvas
= canvases
[canvasId
], swf
= canvas
.firstChild
, prop
;
835 // clean up the references of swf.executeCommand and swf.resize
837 if (typeof swf
[prop
] === "function") {
842 // clean up the references of canvas.getContext and canvas.toDataURL
843 for (prop
in canvas
) {
844 if (typeof canvas
[prop
] === "function") {
849 // remove event listeners
850 swf
.detachEvent(ON_FOCUS
, onFocus
);
851 canvas
.detachEvent(ON_PROPERTY_CHANGE
, onPropertyChange
);
854 // delete exported symbols
855 window
[CANVAS_RENDERING_CONTEXT_2D
] = NULL
;
856 window
[CANVAS_GRADIENT
] = NULL
;
857 window
[CANVAS_PATTERN
] = NULL
;
858 window
[FLASH_CANVAS
] = NULL
;
859 window
[G_VML_CANVAS_MANAGER
] = NULL
;
867 initElement: function(canvas
) {
868 // Check whether the initialization is required or not.
869 if (canvas
.getContext
) {
874 var canvasId
= getUniqueId();
875 var objectId
= OBJECT_ID_PREFIX
+ canvasId
;
876 isReady
[canvasId
] = false;
879 // Set the width and height attributes.
880 setCanvasSize(canvas
);
882 // embed swf and SPAN element
884 '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"' +
885 ' codebase="' + location
.protocol
+ '//fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0"' +
886 ' width="100%" height="100%" id="' + objectId
+ '">' +
887 '<param name="allowScriptAccess" value="always">' +
888 '<param name="flashvars" value="id=' + objectId
+ '">' +
889 '<param name="wmode" value="transparent">' +
891 '<span style="margin:0;padding:0;border:0;display:inline-block;position:static;height:1em;overflow:visible;white-space:nowrap">' +
894 canvases
[canvasId
] = canvas
;
895 var swf
= canvas
.firstChild
;
896 spans
[canvasId
] = canvas
.lastChild
;
898 // Check whether the canvas element is in the DOM tree
899 var documentContains
= document
.body
.contains
;
900 if (documentContains(canvas
)) {
901 // Load swf file immediately
902 swf
["movie"] = SWF_URL
;
904 // Wait until the element is added to the DOM tree
905 var intervalId
= setInterval(function() {
906 if (documentContains(canvas
)) {
907 clearInterval(intervalId
);
908 swf
["movie"] = SWF_URL
;
913 // If the browser is IE6 or in quirks mode
914 if (document
.compatMode
=== "BackCompat" || !window
.XMLHttpRequest
) {
915 spans
[canvasId
].style
.overflow
= "hidden";
918 // initialize context
919 var ctx
= new CanvasRenderingContext2D(canvas
, swf
);
922 canvas
.getContext = function(contextId
) {
923 return contextId
=== "2d" ? ctx
: NULL
;
926 canvas
.toDataURL = function(type
, quality
) {
927 if (("" + type
).replace(/[A-Z]+/g, toLowerCase
) === "image/jpeg") {
928 ctx
._queue
.push(properties
.toDataURL
, type
,
929 typeof quality
=== "number" ? quality
: "");
931 ctx
._queue
.push(properties
.toDataURL
, type
);
933 return ctx
._executeCommand();
936 // add event listener
937 swf
.attachEvent(ON_FOCUS
, onFocus
);
942 saveImage: function(canvas
) {
943 var swf
= canvas
.firstChild
;
947 setOptions: function(options
) {
951 trigger: function(canvasId
, type
) {
952 var canvas
= canvases
[canvasId
];
953 canvas
.fireEvent("on" + type
);
956 unlock: function(canvasId
, ready
) {
957 if (lock
[canvasId
]) {
961 var canvas
= canvases
[canvasId
];
962 var swf
= canvas
.firstChild
;
966 // Set the width and height attributes of the canvas element.
967 setCanvasSize(canvas
);
968 width
= canvas
.width
;
969 height
= canvas
.height
;
971 canvas
.style
.width
= width
+ "px";
972 canvas
.style
.height
= height
+ "px";
974 // Adjust the size of Flash to that of the canvas
981 swf
.resize(width
, height
);
983 // Add event listener
984 canvas
.attachEvent(ON_PROPERTY_CHANGE
, onPropertyChange
);
986 // ExternalInterface is now ready for use
987 isReady
[canvasId
] = true;
996 // Get the absolute URL of flashcanvas.js
997 function getScriptUrl() {
998 var scripts
= document
.getElementsByTagName("script");
999 var script
= scripts
[scripts
.length
- 1];
1001 // @see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
1002 if (document
.documentMode
>= 8) {
1005 return script
.getAttribute("src", 4);
1009 // Get a unique ID composed of alphanumeric characters.
1010 function getUniqueId() {
1011 return Math
.random().toString(36).slice(2) || "0";
1014 // Escape characters not permitted in XML.
1015 function encodeXML(str
) {
1016 return ("" + str
).replace(/&/g, "&").replace(/</g
, "<");
1019 function toLowerCase(str
) {
1020 return str
.toLowerCase();
1023 function throwException(code
) {
1024 throw new DOMException(code
);
1027 // The width and height attributes of a canvas element must have values that
1028 // are valid non-negative integers.
1029 function setCanvasSize(canvas
) {
1030 var width
= parseInt(canvas
.width
, 10);
1031 var height
= parseInt(canvas
.height
, 10);
1033 if (isNaN(width
) || width
< 0) {
1036 if (isNaN(height
) || height
< 0) {
1040 canvas
.width
= width
;
1041 canvas
.height
= height
;
1049 document
.createElement(CANVAS
);
1051 // setup default CSS
1052 document
.createStyleSheet().cssText
=
1053 CANVAS
+ "{display:inline-block;overflow:hidden;width:300px;height:150px}";
1055 // initialize canvas elements
1056 if (document
.readyState
=== "complete") {
1057 onReadyStateChange();
1059 document
.attachEvent(ON_READY_STATE_CHANGE
, onReadyStateChange
);
1062 // prevent IE6 memory leaks
1063 window
.attachEvent(ON_UNLOAD
, onUnload
);
1065 // preload SWF file if it's in the same domain
1066 if (SWF_URL
.indexOf(location
.protocol
+ "//" + location
.host
+ "/") === 0) {
1067 var req
= new ActiveXObject("Microsoft.XMLHTTP");
1068 req
.open("GET", SWF_URL
, false);
1076 window
[CANVAS_RENDERING_CONTEXT_2D
] = CanvasRenderingContext2D
;
1077 window
[CANVAS_GRADIENT
] = CanvasGradient
;
1078 window
[CANVAS_PATTERN
] = CanvasPattern
;
1079 window
[FLASH_CANVAS
] = FlashCanvas
;
1081 // ExplorerCanvas-compatible APIs for convenience
1082 window
[G_VML_CANVAS_MANAGER
] = {
1084 init_: function(){},
1085 initElement
: FlashCanvas
.initElement
1088 // Prevent Closure Compiler from removing the function.
1089 keep
= CanvasRenderingContext2D
.measureText
;
1091 })(window
, document
);