diff --git a/external/flashcanvas.js b/external/flashcanvas.js new file mode 100644 index 000000000..710829cbb --- /dev/null +++ b/external/flashcanvas.js @@ -0,0 +1,1194 @@ +/* + * FlashCanvas + * + * Copyright (c) 2009 Tim Cameron Ryan + * Copyright (c) 2009-2011 FlashCanvas Project + * Released under the MIT/X License + */ + +// Reference: +// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html +// http://dev.w3.org/html5/spec/the-canvas-element.html + +// If the browser is IE and does not support HTML5 Canvas +if (window["ActiveXObject"] && !window["CanvasRenderingContext2D"]) { + + (function(window, document, undefined) { + + /* + * Constant + */ + + var NULL = null; + var CANVAS = "canvas"; + var CANVAS_RENDERING_CONTEXT_2D = "CanvasRenderingContext2D"; + var CANVAS_GRADIENT = "CanvasGradient"; + var CANVAS_PATTERN = "CanvasPattern"; + var FLASH_CANVAS = "FlashCanvas"; + var G_VML_CANVAS_MANAGER = "G_vmlCanvasManager"; + var OBJECT_ID_PREFIX = "external"; + var ON_FOCUS = "onfocus"; + var ON_PROPERTY_CHANGE = "onpropertychange"; + var ON_READY_STATE_CHANGE = "onreadystatechange"; + var ON_UNLOAD = "onunload"; + + var config = window[FLASH_CANVAS + "Options"] || {}; + var BASE_URL = config["swfPath"] || getScriptUrl().replace(/[^\/]+$/, ""); + var SWF_URL = BASE_URL + "flashcanvas.swf"; + + // DOMException code + var INDEX_SIZE_ERR = 1; + var NOT_SUPPORTED_ERR = 9; + var INVALID_STATE_ERR = 11; + var SYNTAX_ERR = 12; + var TYPE_MISMATCH_ERR = 17; + var SECURITY_ERR = 18; + + /** + * @constructor + */ + function Lookup(array) { + for (var i = 0, n = array.length; i < n; i++) + this[array[i]] = i; + } + + var properties = new Lookup([ + // Canvas element + "toDataURL", + + // CanvasRenderingContext2D + "save", + "restore", + "scale", + "rotate", + "translate", + "transform", + "setTransform", + "globalAlpha", + "globalCompositeOperation", + "strokeStyle", + "fillStyle", + "createLinearGradient", + "createRadialGradient", + "createPattern", + "lineWidth", + "lineCap", + "lineJoin", + "miterLimit", + "shadowOffsetX", + "shadowOffsetY", + "shadowBlur", + "shadowColor", + "clearRect", + "fillRect", + "strokeRect", + "beginPath", + "closePath", + "moveTo", + "lineTo", + "quadraticCurveTo", + "bezierCurveTo", + "arcTo", + "rect", + "arc", + "fill", + "stroke", + "clip", + "isPointInPath", + // "drawFocusRing", + "font", + "textAlign", + "textBaseline", + "fillText", + "strokeText", + "measureText", + "drawImage", + "createImageData", + "getImageData", + "putImageData", + + // CanvasGradient + "addColorStop", + + // Internal use + "direction", + "resize" + ]); + + // Whether swf is ready for use + var isReady = {}; + + // Cache of images loaded by createPattern() or drawImage() + var images = {}; + + // Monitor the number of loading files + var lock = {}; + + // Callback functions passed to loadImage() + var callbacks = {}; + + // Canvas elements + var canvases = {}; + + // SPAN element embedded in the canvas + var spans = {}; + + /** + * 2D context + * @constructor + */ + var CanvasRenderingContext2D = function(canvas, swf) { + // back-reference to the canvas + this.canvas = canvas; + + // back-reference to the swf + this._swf = swf; + + // unique ID of canvas + this._canvasId = swf.id.slice(8); + + // initialize drawing states + this._initialize(); + + // Count CanvasGradient and CanvasPattern objects + this._gradientPatternId = 0; + + // Directionality of the canvas element + this._direction = ""; + + // This ensures that font properties of the canvas element is + // transmitted to Flash. + this._font = ""; + + // frame update interval + var self = this; + setInterval(function() { + if (lock[self._canvasId] === 0) { + self._executeCommand(); + } + }, 30); + }; + + CanvasRenderingContext2D.prototype = { + /* + * state + */ + + save: function() { + // write all properties + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setFillStyle(); + this._setLineStyles(); + this._setFontStyles(); + + // push state + this._stateStack.push([ + this._globalAlpha, + this._globalCompositeOperation, + this._strokeStyle, + this._fillStyle, + this._lineWidth, + this._lineCap, + this._lineJoin, + this._miterLimit, + this._shadowOffsetX, + this._shadowOffsetY, + this._shadowBlur, + this._shadowColor, + this._font, + this._textAlign, + this._textBaseline + ]); + + this._queue.push(properties.save); + }, + + restore: function() { + // pop state + var stateStack = this._stateStack; + if (stateStack.length) { + var state = stateStack.pop(); + this.globalAlpha = state[0]; + this.globalCompositeOperation = state[1]; + this.strokeStyle = state[2]; + this.fillStyle = state[3]; + this.lineWidth = state[4]; + this.lineCap = state[5]; + this.lineJoin = state[6]; + this.miterLimit = state[7]; + this.shadowOffsetX = state[8]; + this.shadowOffsetY = state[9]; + this.shadowBlur = state[10]; + this.shadowColor = state[11]; + this.font = state[12]; + this.textAlign = state[13]; + this.textBaseline = state[14]; + } + + this._queue.push(properties.restore); + }, + + /* + * transformations + */ + + scale: function(x, y) { + this._queue.push(properties.scale, x, y); + }, + + rotate: function(angle) { + this._queue.push(properties.rotate, angle); + }, + + translate: function(x, y) { + this._queue.push(properties.translate, x, y); + }, + + transform: function(m11, m12, m21, m22, dx, dy) { + this._queue.push(properties.transform, m11, m12, m21, m22, dx, dy); + }, + + setTransform: function(m11, m12, m21, m22, dx, dy) { + this._queue.push(properties.setTransform, m11, m12, m21, m22, dx, dy); + }, + + /* + * compositing + */ + + _setCompositing: function() { + var queue = this._queue; + if (this._globalAlpha !== this.globalAlpha) { + this._globalAlpha = this.globalAlpha; + queue.push(properties.globalAlpha, this._globalAlpha); + } + if (this._globalCompositeOperation !== this.globalCompositeOperation) { + this._globalCompositeOperation = this.globalCompositeOperation; + queue.push(properties.globalCompositeOperation, this._globalCompositeOperation); + } + }, + + /* + * colors and styles + */ + + _setStrokeStyle: function() { + if (this._strokeStyle !== this.strokeStyle) { + var style = this._strokeStyle = this.strokeStyle; + if (typeof style === "string") { + // OK + } else if (style instanceof CanvasGradient || + style instanceof CanvasPattern) { + style = style.id; + } else { + return; + } + this._queue.push(properties.strokeStyle, style); + } + }, + + _setFillStyle: function() { + if (this._fillStyle !== this.fillStyle) { + var style = this._fillStyle = this.fillStyle; + if (typeof style === "string") { + // OK + } else if (style instanceof CanvasGradient || + style instanceof CanvasPattern) { + style = style.id; + } else { + return; + } + this._queue.push(properties.fillStyle, style); + } + }, + + createLinearGradient: function(x0, y0, x1, y1) { + // If any of the arguments are not finite numbers, throws a + // NOT_SUPPORTED_ERR exception. + if (!(isFinite(x0) && isFinite(y0) && isFinite(x1) && isFinite(y1))) { + throwException(NOT_SUPPORTED_ERR); + } + + this._queue.push(properties.createLinearGradient, x0, y0, x1, y1); + return new CanvasGradient(this); + }, + + createRadialGradient: function(x0, y0, r0, x1, y1, r1) { + // If any of the arguments are not finite numbers, throws a + // NOT_SUPPORTED_ERR exception. + if (!(isFinite(x0) && isFinite(y0) && isFinite(r0) && + isFinite(x1) && isFinite(y1) && isFinite(r1))) { + throwException(NOT_SUPPORTED_ERR); + } + + // If either of the radii are negative, throws an INDEX_SIZE_ERR + // exception. + if (r0 < 0 || r1 < 0) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.createRadialGradient, x0, y0, r0, x1, y1, r1); + return new CanvasGradient(this); + }, + + createPattern: function(image, repetition) { + // If the image is null, the implementation must raise a + // TYPE_MISMATCH_ERR exception. + if (!image) { + throwException(TYPE_MISMATCH_ERR); + } + + var tagName = image.tagName, src; + var canvasId = this._canvasId; + + // If the first argument isn't an img, canvas, or video element, + // throws a TYPE_MISMATCH_ERR exception. + if (tagName) { + tagName = tagName.toLowerCase(); + if (tagName === "img") { + src = image.getAttribute("src", 2); + } else if (tagName === CANVAS || tagName === "video") { + // For now, only HTMLImageElement is supported. + return; + } else { + throwException(TYPE_MISMATCH_ERR); + } + } + + // Additionally, we accept any object that has a src property. + // This is useful when you'd like to specify a long data URI. + else if (image.src) { + src = image.src; + } else { + throwException(TYPE_MISMATCH_ERR); + } + + // If the second argument isn't one of the allowed values, throws a + // SYNTAX_ERR exception. + if (!(repetition === "repeat" || repetition === "no-repeat" || + repetition === "repeat-x" || repetition === "repeat-y" || + repetition === "" || repetition === NULL)) { + throwException(SYNTAX_ERR); + } + + // Special characters in the filename need escaping. + this._queue.push(properties.createPattern, encodeXML(src), repetition); + + // If this is the first time to access the URL, the canvas should be + // locked while the image is being loaded asynchronously. + if (!images[canvasId][src] && isReady[canvasId]) { + this._executeCommand(); + ++lock[canvasId]; + images[canvasId][src] = true; + } + + return new CanvasPattern(this); + }, + + /* + * line caps/joins + */ + + _setLineStyles: function() { + var queue = this._queue; + if (this._lineWidth !== this.lineWidth) { + this._lineWidth = this.lineWidth; + queue.push(properties.lineWidth, this._lineWidth); + } + if (this._lineCap !== this.lineCap) { + this._lineCap = this.lineCap; + queue.push(properties.lineCap, this._lineCap); + } + if (this._lineJoin !== this.lineJoin) { + this._lineJoin = this.lineJoin; + queue.push(properties.lineJoin, this._lineJoin); + } + if (this._miterLimit !== this.miterLimit) { + this._miterLimit = this.miterLimit; + queue.push(properties.miterLimit, this._miterLimit); + } + }, + + /* + * shadows + */ + + _setShadows: function() { + var queue = this._queue; + if (this._shadowOffsetX !== this.shadowOffsetX) { + this._shadowOffsetX = this.shadowOffsetX; + queue.push(properties.shadowOffsetX, this._shadowOffsetX); + } + if (this._shadowOffsetY !== this.shadowOffsetY) { + this._shadowOffsetY = this.shadowOffsetY; + queue.push(properties.shadowOffsetY, this._shadowOffsetY); + } + if (this._shadowBlur !== this.shadowBlur) { + this._shadowBlur = this.shadowBlur; + queue.push(properties.shadowBlur, this._shadowBlur); + } + if (this._shadowColor !== this.shadowColor) { + this._shadowColor = this.shadowColor; + queue.push(properties.shadowColor, this._shadowColor); + } + }, + + /* + * rects + */ + + clearRect: function(x, y, w, h) { + this._queue.push(properties.clearRect, x, y, w, h); + }, + + fillRect: function(x, y, w, h) { + this._setCompositing(); + this._setShadows(); + this._setFillStyle(); + this._queue.push(properties.fillRect, x, y, w, h); + }, + + strokeRect: function(x, y, w, h) { + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setLineStyles(); + this._queue.push(properties.strokeRect, x, y, w, h); + }, + + /* + * path API + */ + + beginPath: function() { + this._queue.push(properties.beginPath); + }, + + closePath: function() { + this._queue.push(properties.closePath); + }, + + moveTo: function(x, y) { + this._queue.push(properties.moveTo, x, y); + }, + + lineTo: function(x, y) { + this._queue.push(properties.lineTo, x, y); + }, + + quadraticCurveTo: function(cpx, cpy, x, y) { + this._queue.push(properties.quadraticCurveTo, cpx, cpy, x, y); + }, + + bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) { + this._queue.push(properties.bezierCurveTo, cp1x, cp1y, cp2x, cp2y, x, y); + }, + + arcTo: function(x1, y1, x2, y2, radius) { + // Throws an INDEX_SIZE_ERR exception if the given radius is negative. + if (radius < 0 && isFinite(radius)) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.arcTo, x1, y1, x2, y2, radius); + }, + + rect: function(x, y, w, h) { + this._queue.push(properties.rect, x, y, w, h); + }, + + arc: function(x, y, radius, startAngle, endAngle, anticlockwise) { + // Throws an INDEX_SIZE_ERR exception if the given radius is negative. + if (radius < 0 && isFinite(radius)) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.arc, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0); + }, + + fill: function() { + this._setCompositing(); + this._setShadows(); + this._setFillStyle(); + this._queue.push(properties.fill); + }, + + stroke: function() { + this._setCompositing(); + this._setShadows(); + this._setStrokeStyle(); + this._setLineStyles(); + this._queue.push(properties.stroke); + }, + + clip: function() { + this._queue.push(properties.clip); + }, + + isPointInPath: function(x, y) { + // TODO: Implement + }, + + /* + * text + */ + + _setFontStyles: function() { + var queue = this._queue; + if (this._font !== this.font) { + try { + var span = spans[this._canvasId]; + span.style.font = this._font = this.font; + + var style = span.currentStyle; + var fontSize = span.offsetHeight; + var font = [style.fontStyle, style.fontWeight, fontSize, style.fontFamily].join(" "); + queue.push(properties.font, font); + } catch(e) { + // If this.font cannot be parsed as a CSS font value, then it + // must be ignored. + } + } + if (this._textAlign !== this.textAlign) { + this._textAlign = this.textAlign; + queue.push(properties.textAlign, this._textAlign); + } + if (this._textBaseline !== this.textBaseline) { + this._textBaseline = this.textBaseline; + queue.push(properties.textBaseline, this._textBaseline); + } + if (this._direction !== this.canvas.currentStyle.direction) { + this._direction = this.canvas.currentStyle.direction; + queue.push(properties.direction, this._direction); + } + }, + + fillText: function(text, x, y, maxWidth) { + this._setCompositing(); + this._setFillStyle(); + this._setShadows(); + this._setFontStyles(); + this._queue.push(properties.fillText, encodeXML(text), x, y, + maxWidth === undefined ? Infinity : maxWidth); + }, + + strokeText: function(text, x, y, maxWidth) { + this._setCompositing(); + this._setStrokeStyle(); + this._setShadows(); + this._setFontStyles(); + this._queue.push(properties.strokeText, encodeXML(text), x, y, + maxWidth === undefined ? Infinity : maxWidth); + }, + + measureText: function(text) { + var span = spans[this._canvasId]; + try { + span.style.font = this.font; + } catch(e) { + // If this.font cannot be parsed as a CSS font value, then it must + // be ignored. + } + + // Replace space characters with tab characters because innerText + // removes trailing white spaces. + span.innerText = text.replace(/[ \n\f\r]/g, "\t"); + + return new TextMetrics(span.offsetWidth); + }, + + /* + * drawing images + */ + + drawImage: function(image, x1, y1, w1, h1, x2, y2, w2, h2) { + // If the image is null, the implementation must raise a + // TYPE_MISMATCH_ERR exception. + + if (!image) { + throwException(TYPE_MISMATCH_ERR); + } + + var tagName = image.tagName, src, argc = arguments.length; + var canvasId = this._canvasId; + + // If the first argument isn't an img, canvas, or video element, + // throws a TYPE_MISMATCH_ERR exception. + if (tagName) { + tagName = tagName.toLowerCase(); + if (tagName === "img") { + src = image.getAttribute("src", 2); + } else if (tagName === CANVAS || tagName === "video") { + // For now, only HTMLImageElement is supported. + return; + } else { + throwException(TYPE_MISMATCH_ERR); + } + } + + // Additionally, we accept any object that has a src property. + // This is useful when you'd like to specify a long data URI. + else if (image.src) { + src = image.src; + } else { + throwException(TYPE_MISMATCH_ERR); + } + + this._setCompositing(); + this._setShadows(); + + // Special characters in the filename need escaping. + src = encodeXML(src); + + if (argc === 3) { + this._queue.push(properties.drawImage, argc, src, x1, y1); + } else if (argc === 5) { + this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1); + } else if (argc === 9) { + // If one of the sw or sh arguments is zero, the implementation + // must raise an INDEX_SIZE_ERR exception. + if (w1 === 0 || h1 === 0) { + throwException(INDEX_SIZE_ERR); + } + + this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1, x2, y2, w2, h2); + } else { + return; + } + + // If this is the first time to access the URL, the canvas should be + // locked while the image is being loaded asynchronously. + if (!images[canvasId][src] && isReady[canvasId]) { + this._executeCommand(); + ++lock[canvasId]; + images[canvasId][src] = true; + } + }, + + /* + * pixel manipulation + */ + + // ImageData createImageData(in float sw, in float sh); + // ImageData createImageData(in ImageData imagedata); + createImageData: function() { + // TODO: Implement + }, + + // ImageData getImageData(in float sx, in float sy, in float sw, in float sh); + getImageData: function(sx, sy, sw, sh) { + // TODO: Implement + }, + + // void putImageData(in ImageData imagedata, in float dx, in float dy, [Optional] in float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight); + putImageData: function(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) { + // TODO: Implement + }, + + /* + * extended functions + */ + + loadImage: function(image, onload, onerror) { + var tagName = image.tagName, src; + var canvasId = this._canvasId; + + // Get the URL of the image. + if (tagName) { + if (tagName.toLowerCase() === "img") { + src = image.getAttribute("src", 2); + } + } else if (image.src) { + src = image.src; + } + + // Do nothing in the following cases: + // - The first argument is neither an img element nor an object + // with a src property, + // - The image has been already cached. + if (!src || images[canvasId][src]) { + return; + } + + // Store the objects. + if (onload || onerror) { + callbacks[canvasId][src] = [image, onload, onerror]; + } + + // Load the image without drawing. + this._queue.push(properties.drawImage, 1, encodeXML(src)); + + // Execute the command immediately if possible. + if (isReady[canvasId]) { + this._executeCommand(); + ++lock[canvasId]; + images[canvasId][src] = true; + } + }, + + /* + * private methods + */ + + _initialize: function() { + // compositing + this.globalAlpha = this._globalAlpha = 1.0; + this.globalCompositeOperation = this._globalCompositeOperation = "source-over"; + + // colors and styles + this.strokeStyle = this._strokeStyle = "#000000"; + this.fillStyle = this._fillStyle = "#000000"; + + // line caps/joins + this.lineWidth = this._lineWidth = 1.0; + this.lineCap = this._lineCap = "butt"; + this.lineJoin = this._lineJoin = "miter"; + this.miterLimit = this._miterLimit = 10.0; + + // shadows + this.shadowOffsetX = this._shadowOffsetX = 0; + this.shadowOffsetY = this._shadowOffsetY = 0; + this.shadowBlur = this._shadowBlur = 0; + this.shadowColor = this._shadowColor = "rgba(0, 0, 0, 0.0)"; + + // text + this.font = this._font = "10px sans-serif"; + this.textAlign = this._textAlign = "start"; + this.textBaseline = this._textBaseline = "alphabetic"; + + // command queue + this._queue = []; + + // stack of drawing states + this._stateStack = []; + }, + + _flush: function() { + var queue = this._queue; + this._queue = []; + return queue; + }, + + _executeCommand: function() { + // execute commands + var commands = this._flush(); + if (commands.length > 0) { + return eval(this._swf.CallFunction( + '' + + commands.join("�") + "" + )); + } + }, + + _resize: function(width, height) { + // Flush commands in the queue + this._executeCommand(); + + // Clear back to the initial state + this._initialize(); + + // Adjust the size of Flash to that of the canvas + if (width > 0) { + this._swf.width = width; + } + if (height > 0) { + this._swf.height = height; + } + + // Execute a resize command at the start of the next frame + this._queue.push(properties.resize, width, height); + } + }; + + /** + * CanvasGradient stub + * @constructor + */ + var CanvasGradient = function(ctx) { + this._ctx = ctx; + this.id = ctx._gradientPatternId++; + }; + + CanvasGradient.prototype = { + addColorStop: function(offset, color) { + // Throws an INDEX_SIZE_ERR exception if the offset is out of range. + if (isNaN(offset) || offset < 0 || offset > 1) { + throwException(INDEX_SIZE_ERR); + } + + this._ctx._queue.push(properties.addColorStop, this.id, offset, color); + } + }; + + /** + * CanvasPattern stub + * @constructor + */ + var CanvasPattern = function(ctx) { + this.id = ctx._gradientPatternId++; + }; + + /** + * TextMetrics stub + * @constructor + */ + var TextMetrics = function(width) { + this.width = width; + }; + + /** + * DOMException + * @constructor + */ + var DOMException = function(code) { + this.code = code; + this.message = DOMExceptionNames[code]; + }; + + DOMException.prototype = new Error; + + var DOMExceptionNames = { + 1: "INDEX_SIZE_ERR", + 9: "NOT_SUPPORTED_ERR", + 11: "INVALID_STATE_ERR", + 12: "SYNTAX_ERR", + 17: "TYPE_MISMATCH_ERR", + 18: "SECURITY_ERR" + }; + + /* + * Event handlers + */ + + function onReadyStateChange() { + if (document.readyState === "complete") { + document.detachEvent(ON_READY_STATE_CHANGE, onReadyStateChange); + + var canvases = document.getElementsByTagName(CANVAS); + for (var i = 0, n = canvases.length; i < n; ++i) { + FlashCanvas.initElement(canvases[i]); + } + } + } + + function onFocus() { + // forward the event to the parent + var swf = event.srcElement, canvas = swf.parentNode; + swf.blur(); + canvas.focus(); + } + + function onPropertyChange() { + var prop = event.propertyName; + if (prop === "width" || prop === "height") { + var canvas = event.srcElement; + var value = canvas[prop]; + var number = parseInt(value, 10); + + if (isNaN(number) || number < 0) { + number = (prop === "width") ? 300 : 150; + } + + if (value === number) { + canvas.style[prop] = number + "px"; + canvas.getContext("2d")._resize(canvas.width, canvas.height); + } else { + canvas[prop] = number; + } + } + } + + function onUnload() { + window.detachEvent(ON_UNLOAD, onUnload); + + for (var canvasId in canvases) { + var canvas = canvases[canvasId], swf = canvas.firstChild, prop; + + // clean up the references of swf.executeCommand and swf.resize + for (prop in swf) { + if (typeof swf[prop] === "function") { + swf[prop] = NULL; + } + } + + // clean up the references of canvas.getContext and canvas.toDataURL + for (prop in canvas) { + if (typeof canvas[prop] === "function") { + canvas[prop] = NULL; + } + } + + // remove event listeners + swf.detachEvent(ON_FOCUS, onFocus); + canvas.detachEvent(ON_PROPERTY_CHANGE, onPropertyChange); + } + + // delete exported symbols + window[CANVAS_RENDERING_CONTEXT_2D] = NULL; + window[CANVAS_GRADIENT] = NULL; + window[CANVAS_PATTERN] = NULL; + window[FLASH_CANVAS] = NULL; + window[G_VML_CANVAS_MANAGER] = NULL; + } + + /* + * FlashCanvas API + */ + + var FlashCanvas = { + initElement: function(canvas) { + // Check whether the initialization is required or not. + if (canvas.getContext) { + return canvas; + } + + // initialize lock + var canvasId = getUniqueId(); + var objectId = OBJECT_ID_PREFIX + canvasId; + isReady[canvasId] = false; + images[canvasId] = {}; + lock[canvasId] = 1; + callbacks[canvasId] = {}; + + // Set the width and height attributes. + setCanvasSize(canvas); + + // embed swf and SPAN element + canvas.innerHTML = + '' + + '' + + '' + + '' + + '' + + '' + + ''; + + canvases[canvasId] = canvas; + var swf = canvas.firstChild; + spans[canvasId] = canvas.lastChild; + + // Check whether the canvas element is in the DOM tree + var documentContains = document.body.contains; + if (documentContains(canvas)) { + // Load swf file immediately + swf["movie"] = SWF_URL; + } else { + // Wait until the element is added to the DOM tree + var intervalId = setInterval(function() { + if (documentContains(canvas)) { + clearInterval(intervalId); + swf["movie"] = SWF_URL; + } + }, 0); + } + + // If the browser is IE6 or in quirks mode + if (document.compatMode === "BackCompat" || !window.XMLHttpRequest) { + spans[canvasId].style.overflow = "hidden"; + } + + // initialize context + var ctx = new CanvasRenderingContext2D(canvas, swf); + + // canvas API + canvas.getContext = function(contextId) { + return contextId === "2d" ? ctx : NULL; + }; + + canvas.toDataURL = function(type, quality) { + if (("" + type).replace(/[A-Z]+/g, toLowerCase) === "image/jpeg") { + ctx._queue.push(properties.toDataURL, type, + typeof quality === "number" ? quality : ""); + } else { + ctx._queue.push(properties.toDataURL, type); + } + return ctx._executeCommand(); + }; + + // add event listener + swf.attachEvent(ON_FOCUS, onFocus); + + return canvas; + }, + + saveImage: function(canvas) { + var swf = canvas.firstChild; + swf.saveImage(); + }, + + setOptions: function(options) { + // TODO: Implement + }, + + trigger: function(canvasId, type) { + var canvas = canvases[canvasId]; + canvas.fireEvent("on" + type); + }, + + unlock: function(canvasId, url, error) { + var canvas, swf, width, height; + var _callback, image, callback; + + if (lock[canvasId]) { + --lock[canvasId]; + } + + // If Flash becomes ready + if (url === undefined) { + canvas = canvases[canvasId]; + swf = canvas.firstChild; + + // Set the width and height attributes of the canvas element. + setCanvasSize(canvas); + width = canvas.width; + height = canvas.height; + + canvas.style.width = width + "px"; + canvas.style.height = height + "px"; + + // Adjust the size of Flash to that of the canvas + if (width > 0) { + swf.width = width; + } + if (height > 0) { + swf.height = height; + } + swf.resize(width, height); + + // Add event listener + canvas.attachEvent(ON_PROPERTY_CHANGE, onPropertyChange); + + // ExternalInterface is now ready for use + isReady[canvasId] = true; + + // Call the onload event handler + if (typeof canvas.onload === "function") { + setTimeout(function() { + canvas.onload(); + }, 0); + } + } + + // If callback functions were defined + else if (_callback = callbacks[canvasId][url]) { + image = _callback[0]; + callback = _callback[1 + error]; + delete callbacks[canvasId][url]; + + // Call the onload or onerror callback function. + if (typeof callback === "function") { + callback.call(image); + } + } + } + }; + + /* + * Utility methods + */ + + // Get the absolute URL of flashcanvas.js + function getScriptUrl() { + var scripts = document.getElementsByTagName("script"); + var script = scripts[scripts.length - 1]; + + // @see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx + if (document.documentMode >= 8) { + return script.src; + } else { + return script.getAttribute("src", 4); + } + } + + // Get a unique ID composed of alphanumeric characters. + function getUniqueId() { + return Math.random().toString(36).slice(2) || "0"; + } + + // Escape characters not permitted in XML. + function encodeXML(str) { + return ("" + str).replace(/&/g, "&").replace(/=8?a.src:a.getAttribute("src",4)}function v(a){return(""+a).replace(/&/g,"&").replace(/0)return eval(this.B.CallFunction(''+a.join("�")+""))},I:function(a,b){this.e();this.D();if(a>0)this.B.width=a;if(b>0)this.B.height=b;this.a.push(e.resize,a,b)}};t.prototype={addColorStop:function(a,b){if(isNaN(a)||a<0||a>1)i(1);this.G.a.push(e.addColorStop,this.id,a,b)}};D.prototype=Error();var T={1:"INDEX_SIZE_ERR",9:"NOT_SUPPORTED_ERR",11:"INVALID_STATE_ERR", +12:"SYNTAX_ERR",17:"TYPE_MISMATCH_ERR",18:"SECURITY_ERR"},B={initElement:function(a){if(a.getContext)return a;var b=a.uniqueID,c="external"+b;x[b]=false;n[b]=1;Q(a);a.innerHTML=''; +s[b]=a;var d=a.firstChild;y[b]=a.lastChild;var f=j.body.contains;if(f(a))d.movie=w;else var g=setInterval(function(){if(f(a)){clearInterval(g);d.movie=w}},0);if(j.compatMode==="BackCompat"||!h.XMLHttpRequest)y[b].style.overflow="hidden";var o=new u(a,d);a.getContext=function(l){return l==="2d"?o:k};a.toDataURL=function(l,z){(""+l).replace(/[A-Z]+/g,W)==="image/jpeg"?o.a.push(e.toDataURL,l,typeof z==="number"?z:""):o.a.push(e.toDataURL,l);return o.e()};d.attachEvent(K,G);return a},saveImage:function(a){a.firstChild.saveImage()}, +setOptions:function(){},trigger:function(a,b){s[a].fireEvent("on"+b)},unlock:function(a,b){n[a]&&--n[a];if(b){var c=s[a],d=c.firstChild,f,g;Q(c);f=c.width;g=c.height;c.style.width=f+"px";c.style.height=g+"px";if(f>0)d.width=f;if(g>0)d.height=g;d.resize(f,g);c.attachEvent(L,H);x[a]=true}}};j.createElement(r);j.createStyleSheet().cssText=r+"{display:inline-block;overflow:hidden;width:300px;height:150px}";j.readyState==="complete"?A():j.attachEvent(F,A);h.attachEvent(J,I);if(w.indexOf(location.protocol+ +"//"+location.host+"/")===0){var S=new ActiveXObject("Microsoft.XMLHTTP");S.open("GET",w,false);S.send(k)}h[M]=u;h[N]=t;h[O]=E;h[C]=B;h[P]={init:function(){},init_:function(){},initElement:B.initElement};keep=u.measureText}(window,document); diff --git a/external/flashcanvas.swf b/external/flashcanvas.swf new file mode 100644 index 000000000..66ff213fb Binary files /dev/null and b/external/flashcanvas.swf differ