Translation update done using Pootle.
[phpmyadmin.git] / js / canvg / flashcanvas.js
blob6ecaa942aef03ea649b483d5894da92e2b898458
1 /*
2  * FlashCanvas
3  *
4  * Copyright (c) 2009      Tim Cameron Ryan
5  * Copyright (c) 2009-2011 FlashCanvas Project
6  * Released under the MIT/X License
7  */
9 // Reference:
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) {
19  * Constant
20  */
22 var NULL                        = null;
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";
39 // DOMException code
40 var INDEX_SIZE_ERR              =  1;
41 var NOT_SUPPORTED_ERR           =  9;
42 var INVALID_STATE_ERR           = 11;
43 var SYNTAX_ERR                  = 12;
44 var TYPE_MISMATCH_ERR           = 17;
45 var SECURITY_ERR                = 18;
47 /**
48  * @constructor
49  */
50 function Lookup(array) {
51     for (var i = 0, n = array.length; i < n; i++)
52         this[array[i]] = i;
55 var properties = new Lookup([
56     // Canvas element
57     "toDataURL",
59     // CanvasRenderingContext2D
60     "save",
61     "restore",
62     "scale",
63     "rotate",
64     "translate",
65     "transform",
66     "setTransform",
67     "globalAlpha",
68     "globalCompositeOperation",
69     "strokeStyle",
70     "fillStyle",
71     "createLinearGradient",
72     "createRadialGradient",
73     "createPattern",
74     "lineWidth",
75     "lineCap",
76     "lineJoin",
77     "miterLimit",
78     "shadowOffsetX",
79     "shadowOffsetY",
80     "shadowBlur",
81     "shadowColor",
82     "clearRect",
83     "fillRect",
84     "strokeRect",
85     "beginPath",
86     "closePath",
87     "moveTo",
88     "lineTo",
89     "quadraticCurveTo",
90     "bezierCurveTo",
91     "arcTo",
92     "rect",
93     "arc",
94     "fill",
95     "stroke",
96     "clip",
97     "isPointInPath",
98 //  "drawFocusRing",
99     "font",
100     "textAlign",
101     "textBaseline",
102     "fillText",
103     "strokeText",
104     "measureText",
105     "drawImage",
106     "createImageData",
107     "getImageData",
108     "putImageData",
110     // CanvasGradient
111     "addColorStop",
113     // Internal use
114     "direction",
115     "resize"
118 // Whether swf is ready for use
119 var isReady = {};
121 // Monitor the number of loading files
122 var lock = {};
124 // Canvas elements
125 var canvases = {};
127 // SPAN element embedded in the canvas
128 var spans = {};
131  * 2D context
132  * @constructor
133  */
134 var CanvasRenderingContext2D = function(canvas, swf) {
135     // back-reference to the canvas
136     this.canvas = canvas;
138     // back-reference to the swf
139     this._swf = swf;
141     // unique ID of canvas
142     this._canvasId = swf.id.slice(8);
144     // initialize drawing states
145     this._initialize();
147     // Count CanvasGradient and CanvasPattern objects
148     this._gradientPatternId = 0;
150     // Directionality of the canvas element
151     this._direction = "";
153     // frame update interval
154     var self = this;
155     setInterval(function() {
156         if (lock[self._canvasId] === 0) {
157             self._executeCommand();
158         }
159     }, 30);
162 CanvasRenderingContext2D.prototype = {
163     /*
164      * state
165      */
167     save: function() {
168         // write all properties
169         this._setCompositing();
170         this._setShadows();
171         this._setStrokeStyle();
172         this._setFillStyle();
173         this._setLineStyles();
174         this._setFontStyles();
176         // push state
177         this._stateStack.push([
178             this._globalAlpha,
179             this._globalCompositeOperation,
180             this._strokeStyle,
181             this._fillStyle,
182             this._lineWidth,
183             this._lineCap,
184             this._lineJoin,
185             this._miterLimit,
186             this._shadowOffsetX,
187             this._shadowOffsetY,
188             this._shadowBlur,
189             this._shadowColor,
190             this._font,
191             this._textAlign,
192             this._textBaseline
193         ]);
195         this._queue.push(properties.save);
196     },
198     restore: function() {
199         // pop state
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];
218         }
220         this._queue.push(properties.restore);
221     },
223     /*
224      * transformations
225      */
227     scale: function(x, y) {
228         this._queue.push(properties.scale, x, y);
229     },
231     rotate: function(angle) {
232         this._queue.push(properties.rotate, angle);
233     },
235     translate: function(x, y) {
236         this._queue.push(properties.translate, x, y);
237     },
239     transform: function(m11, m12, m21, m22, dx, dy) {
240         this._queue.push(properties.transform, m11, m12, m21, m22, dx, dy);
241     },
243     setTransform: function(m11, m12, m21, m22, dx, dy) {
244         this._queue.push(properties.setTransform, m11, m12, m21, m22, dx, dy);
245     },
247     /*
248      * compositing
249      */
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);
256         }
257         if (this._globalCompositeOperation !== this.globalCompositeOperation) {
258             this._globalCompositeOperation = this.globalCompositeOperation;
259             queue.push(properties.globalCompositeOperation, this._globalCompositeOperation);
260         }
261     },
263     /*
264      * colors and styles
265      */
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);
271         }
272     },
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);
278         }
279     },
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);
286         }
288         this._queue.push(properties.createLinearGradient, x0, y0, x1, y1);
289         return new CanvasGradient(this);
290     },
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);
298         }
300         // If either of the radii are negative, throws an INDEX_SIZE_ERR
301         // exception.
302         if (r0 < 0 || r1 < 0) {
303             throwException(INDEX_SIZE_ERR);
304         }
306         this._queue.push(properties.createRadialGradient, x0, y0, r0, x1, y1, r1);
307         return new CanvasGradient(this);
308     },
310     createPattern: function(image, repetition) {
311         // If the image is null, the implementation must raise a
312         // TYPE_MISMATCH_ERR exception.
313         if (!image) {
314             throwException(TYPE_MISMATCH_ERR);
315         }
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.
322         if (tagName) {
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.
328                 return;
329             } else {
330                 throwException(TYPE_MISMATCH_ERR);
331             }
332         }
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) {
337             src = image.src;
338         } else {
339             throwException(TYPE_MISMATCH_ERR);
340         }
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);
348         }
350         // Special characters in the filename need escaping.
351         this._queue.push(properties.createPattern, encodeXML(src), repetition);
353         if (isReady[canvasId]) {
354             this._executeCommand();
355             ++lock[canvasId];
356         }
358         return new CanvasPattern(this);
359     },
361     /*
362      * line caps/joins
363      */
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);
370         }
371         if (this._lineCap !== this.lineCap) {
372             this._lineCap = this.lineCap;
373             queue.push(properties.lineCap, this._lineCap);
374         }
375         if (this._lineJoin !== this.lineJoin) {
376             this._lineJoin = this.lineJoin;
377             queue.push(properties.lineJoin, this._lineJoin);
378         }
379         if (this._miterLimit !== this.miterLimit) {
380             this._miterLimit = this.miterLimit;
381             queue.push(properties.miterLimit, this._miterLimit);
382         }
383     },
385     /*
386      * shadows
387      */
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);
394         }
395         if (this._shadowOffsetY !== this.shadowOffsetY) {
396             this._shadowOffsetY = this.shadowOffsetY;
397             queue.push(properties.shadowOffsetY, this._shadowOffsetY);
398         }
399         if (this._shadowBlur !== this.shadowBlur) {
400             this._shadowBlur = this.shadowBlur;
401             queue.push(properties.shadowBlur, this._shadowBlur);
402         }
403         if (this._shadowColor !== this.shadowColor) {
404             this._shadowColor = this.shadowColor;
405             queue.push(properties.shadowColor, this._shadowColor);
406         }
407     },
409     /*
410      * rects
411      */
413     clearRect: function(x, y, w, h) {
414         this._queue.push(properties.clearRect, x, y, w, h);
415     },
417     fillRect: function(x, y, w, h) {
418         this._setCompositing();
419         this._setShadows();
420         this._setFillStyle();
421         this._queue.push(properties.fillRect, x, y, w, h);
422     },
424     strokeRect: function(x, y, w, h) {
425         this._setCompositing();
426         this._setShadows();
427         this._setStrokeStyle();
428         this._setLineStyles();
429         this._queue.push(properties.strokeRect, x, y, w, h);
430     },
432     /*
433      * path API
434      */
436     beginPath: function() {
437         this._queue.push(properties.beginPath);
438     },
440     closePath: function() {
441         this._queue.push(properties.closePath);
442     },
444     moveTo: function(x, y) {
445         this._queue.push(properties.moveTo, x, y);
446     },
448     lineTo: function(x, y) {
449         this._queue.push(properties.lineTo, x, y);
450     },
452     quadraticCurveTo: function(cpx, cpy, x, y) {
453         this._queue.push(properties.quadraticCurveTo, cpx, cpy, x, y);
454     },
456     bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {
457         this._queue.push(properties.bezierCurveTo, cp1x, cp1y, cp2x, cp2y, x, y);
458     },
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);
464         }
466         this._queue.push(properties.arcTo, x1, y1, x2, y2, radius);
467     },
469     rect: function(x, y, w, h) {
470         this._queue.push(properties.rect, x, y, w, h);
471     },
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);
477         }
479         this._queue.push(properties.arc, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0);
480     },
482     fill: function() {
483         this._setCompositing();
484         this._setShadows();
485         this._setFillStyle();
486         this._queue.push(properties.fill);
487     },
489     stroke: function() {
490         this._setCompositing();
491         this._setShadows();
492         this._setStrokeStyle();
493         this._setLineStyles();
494         this._queue.push(properties.stroke);
495     },
497     clip: function() {
498         this._queue.push(properties.clip);
499     },
501     isPointInPath: function(x, y) {
502         // TODO: Implement
503     },
505     /*
506      * text
507      */
509     _setFontStyles: function() {
510         var queue = this._queue;
511         if (this._font !== this.font) {
512             try {
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);
520             } catch(e) {
521                 // If this.font cannot be parsed as a CSS font value, then it
522                 // must be ignored.
523             }
524         }
525         if (this._textAlign !== this.textAlign) {
526             this._textAlign = this.textAlign;
527             queue.push(properties.textAlign, this._textAlign);
528         }
529         if (this._textBaseline !== this.textBaseline) {
530             this._textBaseline = this.textBaseline;
531             queue.push(properties.textBaseline, this._textBaseline);
532         }
533         if (this._direction !== this.canvas.currentStyle.direction) {
534             this._direction = this.canvas.currentStyle.direction;
535             queue.push(properties.direction, this._direction);
536         }
537     },
539     fillText: function(text, x, y, maxWidth) {
540         this._setCompositing();
541         this._setFillStyle();
542         this._setShadows();
543         this._setFontStyles();
544         this._queue.push(properties.fillText, encodeXML(text), x, y,
545                          maxWidth === undefined ? Infinity : maxWidth);
546     },
548     strokeText: function(text, x, y, maxWidth) {
549         this._setCompositing();
550         this._setStrokeStyle();
551         this._setShadows();
552         this._setFontStyles();
553         this._queue.push(properties.strokeText, encodeXML(text), x, y,
554                          maxWidth === undefined ? Infinity : maxWidth);
555     },
557     measureText: function(text) {
558         var span = spans[this._canvasId];
559         try {
560             span.style.font = this.font;
561         } catch(e) {
562             // If this.font cannot be parsed as a CSS font value, then it must
563             // be ignored.
564         }
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);
571     },
573     /*
574      * drawing images
575      */
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.
580         if (!image) {
581             throwException(TYPE_MISMATCH_ERR);
582         }
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.
589         if (tagName) {
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.
595                 return;
596             } else {
597                 throwException(TYPE_MISMATCH_ERR);
598             }
599         }
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) {
604             src = image.src;
605         } else {
606             throwException(TYPE_MISMATCH_ERR);
607         }
609         this._setCompositing();
610         this._setShadows();
612         // Special characters in the filename need escaping.
613         src = encodeXML(src);
615         if (argc === 3) {
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);
624             }
626             this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1, x2, y2, w2, h2);
627         } else {
628             return;
629         }
631         if (isReady[canvasId]) {
632             this._executeCommand();
633             ++lock[canvasId];
634         }
635     },
637     /*
638      * pixel manipulation
639      */
641     // ImageData createImageData(in float sw, in float sh);
642     // ImageData createImageData(in ImageData imagedata);
643     createImageData: function() {
644         // TODO: Implement
645     },
647     // ImageData getImageData(in float sx, in float sy, in float sw, in float sh);
648     getImageData: function(sx, sy, sw, sh) {
649         // TODO: Implement
650     },
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) {
654         // TODO: Implement
655     },
657     /*
658      * private methods
659      */
661     _initialize: function() {
662         // compositing
663         this.globalAlpha = this._globalAlpha = 1.0;
664         this.globalCompositeOperation = this._globalCompositeOperation = "source-over";
666         // colors and styles
667         this.strokeStyle = this._strokeStyle = "#000000";
668         this.fillStyle   = this._fillStyle   = "#000000";
670         // line caps/joins
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;
676         // shadows
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)";
682         // text
683         this.font         = this._font         = "10px sans-serif";
684         this.textAlign    = this._textAlign    = "start";
685         this.textBaseline = this._textBaseline = "alphabetic";
687         // command queue
688         this._queue = [];
690         // stack of drawing states
691         this._stateStack = [];
692     },
694     _flush: function() {
695         var queue = this._queue;
696         this._queue = [];
697         return queue;
698     },
700     _executeCommand: function() {
701         // execute commands
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("&#0;") + "</string></arguments></invoke>"
707             ));
708         }
709     },
711     _resize: function(width, height) {
712         // Flush commands in the queue
713         this._executeCommand();
715         // Clear back to the initial state
716         this._initialize();
718         // Adjust the size of Flash to that of the canvas
719         if (width > 0) {
720             this._swf.width = width;
721         }
722         if (height > 0) {
723             this._swf.height = height;
724         }
726         // Execute a resize command at the start of the next frame
727         this._queue.push(properties.resize, width, height);
728     }
732  * CanvasGradient stub
733  * @constructor
734  */
735 var CanvasGradient = function(ctx) {
736     this._ctx = 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);
745         }
747         this._ctx._queue.push(properties.addColorStop, this.id, offset, color);
748     }
752  * CanvasPattern stub
753  * @constructor
754  */
755 var CanvasPattern = function(ctx) {
756     this.id = ctx._gradientPatternId++;
760  * TextMetrics stub
761  * @constructor
762  */
763 var TextMetrics = function(width) {
764     this.width = width;
768  * DOMException
769  * @constructor
770  */
771 var DOMException = function(code) {
772     this.code    = code;
773     this.message = DOMExceptionNames[code];
776 DOMException.prototype = new Error;
778 var DOMExceptionNames = {
779     1:  "INDEX_SIZE_ERR",
780     9:  "NOT_SUPPORTED_ERR",
781     11: "INVALID_STATE_ERR",
782     12: "SYNTAX_ERR",
783     17: "TYPE_MISMATCH_ERR",
784     18: "SECURITY_ERR"
788  * Event handlers
789  */
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]);
798         }
799     }
802 function onFocus() {
803     // forward the event to the parent
804     var swf = event.srcElement, canvas = swf.parentNode;
805     swf.blur();
806     canvas.focus();
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;
818         }
820         if (value === number) {
821             canvas.style[prop] = number + "px";
822             canvas.getContext("2d")._resize(canvas.width, canvas.height);
823         } else {
824             canvas[prop] = number;
825         }
826     }
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
836         for (prop in swf) {
837             if (typeof swf[prop] === "function") {
838                 swf[prop] = NULL;
839             }
840         }
842         // clean up the references of canvas.getContext and canvas.toDataURL
843         for (prop in canvas) {
844             if (typeof canvas[prop] === "function") {
845                 canvas[prop] = NULL;
846             }
847         }
849         // remove event listeners
850         swf.detachEvent(ON_FOCUS, onFocus);
851         canvas.detachEvent(ON_PROPERTY_CHANGE, onPropertyChange);
852     }
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;
863  * FlashCanvas API
864  */
866 var FlashCanvas = {
867     initElement: function(canvas) {
868         // Check whether the initialization is required or not.
869         if (canvas.getContext) {
870             return canvas;
871         }
873         // initialize lock
874         var canvasId      = getUniqueId();
875         var objectId      = OBJECT_ID_PREFIX + canvasId;
876         isReady[canvasId] = false;
877         lock[canvasId]    = 1;
879         // Set the width and height attributes.
880         setCanvasSize(canvas);
882         // embed swf and SPAN element
883         canvas.innerHTML =
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">' +
890             '</object>' +
891             '<span style="margin:0;padding:0;border:0;display:inline-block;position:static;height:1em;overflow:visible;white-space:nowrap">' +
892             '</span>';
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;
903         } else {
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;
909                 }
910             }, 0);
911         }
913         // If the browser is IE6 or in quirks mode
914         if (document.compatMode === "BackCompat" || !window.XMLHttpRequest) {
915             spans[canvasId].style.overflow = "hidden";
916         }
918         // initialize context
919         var ctx = new CanvasRenderingContext2D(canvas, swf);
921         // canvas API
922         canvas.getContext = function(contextId) {
923             return contextId === "2d" ? ctx : NULL;
924         };
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 : "");
930             } else {
931                 ctx._queue.push(properties.toDataURL, type);
932             }
933             return ctx._executeCommand();
934         };
936         // add event listener
937         swf.attachEvent(ON_FOCUS, onFocus);
939         return canvas;
940     },
942     saveImage: function(canvas) {
943         var swf = canvas.firstChild;
944         swf.saveImage();
945     },
947     setOptions: function(options) {
948         // TODO: Implement
949     },
951     trigger: function(canvasId, type) {
952         var canvas = canvases[canvasId];
953         canvas.fireEvent("on" + type);
954     },
956     unlock: function(canvasId, ready) {
957         if (lock[canvasId]) {
958             --lock[canvasId];
959         }
960         if (ready) {
961             var canvas = canvases[canvasId];
962             var swf    = canvas.firstChild;
963             var width;
964             var height;
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
975             if (width > 0) {
976                 swf.width = width;
977             }
978             if (height > 0) {
979                 swf.height = height;
980             }
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;
988         }
989     }
993  * Utility methods
994  */
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) {
1003         return script.src;
1004     } else {
1005         return script.getAttribute("src", 4);
1006     }
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, "&amp;").replace(/</g, "&lt;");
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) {
1034         width = 300;
1035     }
1036     if (isNaN(height) || height < 0) {
1037         height = 150;
1038     }
1040     canvas.width  = width;
1041     canvas.height = height;
1045  * initialization
1046  */
1048 // IE HTML5 shiv
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();
1058 } else {
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);
1069     req.send(NULL);
1073  * public API
1074  */
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] = {
1083     init:  function(){},
1084     init_: function(){},
1085     initElement: FlashCanvas.initElement
1088 // Prevent Closure Compiler from removing the function.
1089 keep = CanvasRenderingContext2D.measureText;
1091 })(window, document);