From e9257191519f67d74fd5e364d8dee3c0963ba5fc Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sun, 26 Feb 2012 00:21:01 +0200 Subject: [PATCH] added flashcanvas integration and some legacy IE bug fixes --- external/flashcanvas.js | 1194 +++++++++++++++++++++++++++++++++++ external/flashcanvas.min.js | 28 + external/flashcanvas.swf | Bin 0 -> 21235 bytes 3 files changed, 1222 insertions(+) create mode 100644 external/flashcanvas.js create mode 100644 external/flashcanvas.min.js create mode 100644 external/flashcanvas.swf diff --git a/external/flashcanvas.js b/external/flashcanvas.js new file mode 100644 index 0000000..710829c --- /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 0000000000000000000000000000000000000000..66ff213fb9b86811d17f032be3c73df61547e597 GIT binary patch literal 21235 zcmV)3K+C^FS5pbJn*acK+RVKNe3M7gKmN>9^hmO7%f>biJ}yp>fH;q^A>~Me&>Zjq zPH3q%DweQ=Z8?%m$=$tYNJt?KLK4zrAS5BATzYS$cVq`Uz4zXe;}Z1$&OVPM+i>LW z-ur$2uaDhlW_EXWc6N4mW}ip7C6fBCB;{QrNu?xBF4#koq`wY&nk4DaSVQFCS(B^F zm$Wpu#t#N|*oekNqV3?y%EgNpk6OInsAz0K<>-SBI;gU0Oy!s{KGg8Vm$oJXOMI>I zVI#%~29v|_P^_sf(G+bhXFL#$wkHl7F~Z{75XvxZYmYSxmJOlGaC5jN+?t42jvh4{ zj2c1*N20NoKw?~=t*yB!6kv9hOMLOhXlUW$z@o4((j1649$J~L$ZZl$iRSRQ2@TO; zxV*YKyrg`;@(J1YqPAIuD>q~X9oJ`=0d74i6m6+&i$xpSLy#!~R-$p=Cd{I(J=ok7 zZw$xAwYM&8jV?C3$WpmZC>9PRqWx>6XEedDCb?vIs2rAcxsl{(I;V18{#igwm$N&F4^{GjK2nD6JD z)vUxxf!0NVxb#rb(n}->WGNC0w1h`XZfTTsDcvAnL}Mg9MKUQXZ@;dwqvPVkyUtkm zj0)!ZOUqY3qds=|g}}afRPXKmOi!hhmylAaNA=e~>(w87I;B6~o6_Iyy-vOUr3yBUbFE*dAy#7r}zyG?gZD*~O&pP{EZBzF(+Gk(>sGYU! z2KDBiZF0xOJM`y1cwg=4xIx{1!!`POe}7Ot>mQx+i&s8C$t#rZWn0xXn|?v(@+Vyz zmDIYE*FW}@_Vo`bef>o%DYfhu{q=jkv!%XyPw%)XrFTB_sjXw#axjx`-f@@qHAKC= z`&a$-yYH|qUv`81HO6)J^N;G=pTA81;hmJ;@$+}e^I-MiEq7@h%eJ7CQ}nJMKGVCe zebKh+jYri-zSyB}e|U#}{Rhiw`xRHIm#ltMdGpLiwYR~iV~292a_JAsa~n3uD>h$l`})>~?Wtb<9q8;1q#GYVr=R_V zmdn)*m*1pxJhMaZSoy2o^Uf!>>y!8C7ytC0`sEuBX=~p3!nW_~-A~q^w%DMcIx4r*i4(R zDt~$W!Dm;!d+yRH<51}-N5wl_ufcuS7kEOisjntP#GiWjSAnUw969>O@y|qOtiI`^ z@5f(X(EL?!>$l?%dFa}Qn>?S4zpwnsKb?HkJLBs%$KITKU2^=mi^GvCJ3kzMP-We< z@BZcG@n=r(f3S7$?(sqQF$dlL)Vt%KUi9EqLno_SJ{l7WcH?xApdiHffT&DB9HE68q4o@FL#4oO7C5!m%j~+h!+XO|1)*rq+Ze z2+kSN_IOx;L$JAwS`dvcXby*>4dGFOMM$i|QgboR3b!_dx&5SQYa+ZPF=nzhtp$66 zb4n~0wK|yD-VzMQ98FBGY-?R$Gn?j3Z)yz(Vn@UR4Na()Hw(`|a~jPxD;*g~B*L*) zePT4)91gTP%|UQa&N;}c#g6I_T}}=p0#^BgaI{4`GKwMPnuLa?cw2K|sU6(g8k<6K z=ftK&OQ4OLXcJ9O^L9U;F-|GeCS}?5@>DECz(%|D4PnX z8TyOkFcAYS5eL<@M7U)(4x>=Qs({vF3T&}(yy@g{K}%*DtqL}C=IrKUT0>rVNjTJ= z2wT0Wk*4P6oaSht!K&|!Ct}fsVb-&q%Vwc3yCq^gUz`l#jOL3>y#FNX5Cx}4*w&1x zo)fj|^q+V4N$r7#7>>Qrr1sb%)XHs&i>aH|$|`g5kR}G=VP@e9^AuR(<-{8?1&b#| zo1-x$lvq-_pg9^0G-EVv(Ks|)+ZK+A(=?|g9Ei8a#6WB7A9&^RRi8--A zYn)GK2lsMxQ$wOrYYW8UVP^{%#ilpmVKcR1DTQ_62sKCJVHVQP&1xXD&cLKVo7U9Y z)}C-#niU2xxT&=%0o`C~Cc_#7iBRL5K(INiMlfw^sJW?4i-!WuVI>d?JUClspEP^6u#;iC)2dZRZ)^{bi7g1)FgrlC z0NaTU!kEVJ61_3r%uh{}*%MJ~E1oZE!C0~UY|Y`;1(U^<(rq^pYR=yii&2ul9IaridxG{#7w&rjmoZi|q z1bZ6DnV)6ktU2oZ$sk2EBL&GYBZ%j2D9YI`Jty=P9ad5?IaVSXSqKhzkDwluW{ zT0`OaivrE<;kZ8Qh>2s4ojcF5m`@@sp17#nu&cVZ zG%^P;ci!a8KwvNVEcsFGf!2iJ8FyGLs;iWFljj!j)ZsMED!z0lc`d01TLKRpi0xRx zxOAuD^k`rc3^*c_C(RLp;4C*4Y2zdE!2Nf(^_ew1zh%L~Cz_AYEueBX@;A3UYj3yB z7Z&M^h1tDY>$B z-jCW%54)(oyV3Xem3CLNzvpF#=?DcCEU+lEd9$y^3ehm21>rq+!-{2dX5AH)&*~vN z?K~NmSAq2^*k1SjZBxm(!}@%4KXzn11uDqIu|Yj(*}r&xcJ8rxeJxj zPoFjulv;g8?L2JZl#v)JOyxFfza4yWEe8(uj^wv;Mz0?p0Q#gn5gfu`0xD}GzTi!IPx zFvS9Jn1y4JKqy?;0`H&^Ev769g~d%vJcnH|P=Yv}Fg9!BHZ^J0ZtVjofdCy3Wb&SShHv0G(8r9a=hVveMK!**{cD5okSU9DX4WvPvG=18nKf0kh(cAQ< zIG<`U*W}u{u#)+r)NaL%O*xj^HzVBIp5F)NV9h-nUTwM9W6LMRM~xa)FoEN^@;))4 zyH67{(3~-4=G^(y=1iF}fA*B=Qzp%EH#RkdC-MwTYh|YKoKQPLtQG;QZSC>KoKTiW zVISV3+FP5Wp@k~!vRRumZN`*Yn(+TL;hJc|Nwtd@lWov4kOoI4)*KWIbL`Yl38Fsh z&tWQ{HJorFY?u{338z`2wC{~*G?WM@d^Zg; z0q3>`7Bwxv0+|y@ZYJghX=R`7q|!D& z*c@nGnA^{?3U;!?l3kmxy}G?s#3i})VkbPI^2E0A0uME*2@Jw6e01>An&LA9GxcFr z;!ov;rcIeOt9F(RT8DR^D>lQ~tpRb=*d|Ordcv`@2P`V{z;tkAhdmo19iPYt8@5*A zHsP3STPnE3o#y2V3!-yl#HE_3Yd<6?j(tOIfhVuDMceEPtP@BRQKK1>arB_W{pY8> zDPGOHWY`Ws1Ho@x1P1EN z+L=?-V0$8=&8nR{bFvnTwzoFe;t2O+VcYDZ=1!P3#mO;q!VFs`PMJ4lx)#LBZq`K9 zt<8Yl&$9F3BcknzI58S{M}-0*fJ8hb0k5p#JcM^2bLXj*32Fhh$DU%r5w9FaNi z(KJt-oS8tfKumVJ22XZgs9%MAQNc7jF1D`E5a{!{;5-7SQMG`z`V-<69*4MtYYI0v z6l9iJmG7W{FH-lX{r=)%+JSv(2ZDxBbo2rB$WIsv%^bh4 zuWtMa4f_H#c8i{{k7zn(LjR`6pU^O>ZeMTT`nx58jRppR(NmdE;NO#B0KY!$#>^SO z?%$Pbm#h_p>xb7P8_4(H8Lo=tnJ_0OXN#Jmi0rjsEV{VW z5rj9gba6D=;0VD1ONdILMjXenaCEH80dd zv8I-Iv{el)4Yb-D0+jMKoqr|0u|AN1%}{_=nTff4T2OGgLVlp91yL95aRNZWy@kLNnb2%Vu|+H zN$t_5xY>oNGkZfg%%|kS90uYGmog`ub1lrne1?}g8=?&hO#L__O|ftg8^*#MC=L1( zGnj)74hOUlN{_UM8skj?#{wMb@nAF-buMU(#uMgLv&tM9y{X)b%3WxpZH~y7hS@c@ zHzF8Z+7Mo>H%1dIp`)o4VOT5lVPj}PRBKv<@LUtRwKv0E;ybq7GyrMijgIEP;#R@b z)eIjC#?usuL|btHU7T9S);F>04ngt{@DIjyYa!uHp zFPDj#O3pKp^nCS0!)Y`T4mWdUlcy<&+T7H_QF4oEIvSr$t-57b2-;)OXsf#=+|bnC zl3i0ylOWcf37RHmA#LrkHXLatk`_1LLemCTVBu{Xnf9%bF)Rx=O$!s{!hznJR<#3h zbAs=zTL8rqOJne)>@CqyC;+D-2kvO##HeW`fS2mfK8FpOSR@XS!(R#7%vItrA8ZWf zKyuq~#%HF?AuzE<7|IAJu#9+cS=s_i1L(BP(H0Jb8r#|<5w$HGYgc(9)wbsL7H3;@ zv1v_Vo2exQEgcYkskz<6qDuqj9K{3dy$Gt!oPK9K(Ar?uv!}JnK)ew%X^X>CXbs@e z3{OS3%)*gTw4;VcCbQAcL*Hfx() z+!zjU4plov9&d>*4BJfW$}u12a!f?0^Ts#{ey+-1iOQadjW^Q?3At*CE#4kP*g*z) zbu7v7lG=!dT>&oZFip7TO4X<;aoUZPjdI$DB&xDa7h!RM6{k$k1j+VEwKI;KK4s1n z`?T8m!kf<#Zno)uJJ`t=euS%@1CjXngHNbDp;8-<&HgZFmWyuj&26wkSMxoo8H?LE zP75>~K4Idd$y2J2m^!WIkJD$&tUdCmS+nQNopr@sgz{SB)OC-~I<2_=m9v?OREQxsR`(;5#nhI~jh;3Hu`A%@w1pGQ|J($%#kX zW1+AQL0v3=bXD7uau`C~7Z1mpBCfQL$RV1IuRtcuDllK=q)R47m)K@am^^L5bUkFg zA3C~(N|X{uNm0q*l08a(U$S@U2u&(AklITZBkv$&7g6cO{G5ZdiGkZVdy7in=I48y zeL|(*Qt8iB`gbbb&V(IQx=b!TRR(xEGEFMEQZ88!XfWAtkcW&Ia-%%tCV9xs@{qsD zLvE3WcuF3WOI||x5OhA+F=X+OuNmo}lE)Mf_8xK(xDH`4hCqxVEWwbsh#3$0gyHQb zyo21iPO7XL?Xa|JchMnLqjL@&OXSKY<*;$1^Tr-5qrK%Uu0jdjcc*SX_{&l|f|)k)T6#qHE*96@)QFl$!U6Xa!6x~&=yN=LZQ+3xg-BqKz{;1P*-8EWw&Cp#lbyuzK zI#PEXrMqV7uGu=x(Oq+O*F4>IwC*}acO9#{j?-Po>#h@YSDo&v*In~+Yg?h5O!i0)dTyBc*@lkPfEcP-Ri&AO{aceU!SsP1ahT_@?TnC^<}N&Vmgi0sa2{>j1Au+JJO5(nh3fkgg?Nz7Egpkv5ZFdIQppNH-zf4BFrDyaoN- zO8Su7k?ugc6X`CbyOHiex)My7lW+NVgLk*j^`ancLKf#&wG*XL%JX70a-ujA*6?~D?JMQ zF{H=J@dJ$e@GQ8TQHVLHJR2`v>fRaq*E2gRoGx@1>n;Zecb8beFoq&k0ZV0HE5q9{ebih(pN}daGm>5em^97 z0M7>%$EQ4!hZJ3V82BTK<9f!o0De@_Cq1s{>XS%M0e>3l8Kh^Co=18C=|!ZiNJ*qF z#o>e^Ujn#|yMU4O;Mt4xGSVwbnT}}#^qS(hknH;ClzW z+n~LR^ghyuilfZo_!!WqTzWeApA8OYX!_`@$g!5RGN)q>#$8tHxaSGC{WG{0$-XzUYqB#n+vRua*axT=F3w7o?J_h|K z9HzFb(R3ryHJZbX$F*8n9xCQJ3LwWar7R!ILvsw*9K-AmujV*S)>g{aCx9}M%%7nu z45}(XxD9r4xrl5glKGP*oj-0RtGZ+%|L2nQ8+2*cb;zz*1154vyZnmkbkLMfyul8f@l)Md7(oV0owO7|#}Gu_w(ZOHkrFO=;_h^JT_xp@XqT~@2ybSpVe zOwZ13Gr&&r?*gzY%PN^%2R{1`#LUxW*%4^u?BC2P%7>i+?K1_kB)Cdx8Qp!6G@z2g zT4_M`?Bj3sAYh>{6$KjA{zAInf|1=S>Yfn+c*fM`*Y{!Fn#L9pLP->;$4O|xk)(%;M4`>eyVM=kI)c3Ds z*Q-vIER|Y+HYqceS$=*0hNks0Nczn`m|R-OlHc#?a7dI>E=hYydrQ(V$wQLtB}p4j zl0E`PB#k6VMQm-W;B~w&kI%rQ4l8ryNY3#-lB!0LG_jJTu~j5_N3&#ONIGOcl7{S0 zQvLxXRUAmt9)BQd-?7~IAd>bwn4|*_A!*W~Bn>-^`xr-3$#{|uI-I0^CXlrEM3Rao zk@UOCB)O)Lba*vMdmcg3=&2+PpGMNi8j^nhN0Nq4m!uieOl&o^B<+7BNn?&8$vumt z%Go4Mm_t(GT#^o(N75gTCdqRQNrR6iso*%04mh6WJb|Q9btIM7leE`-l5+hd83B?8 z1xfOSNGfe0>CiAq<02%LEg)$`BS{B0v8kL$Qt?8P@|sB+-%{qtm8zwjR!5#REeFav zGP<`^(~o0h5*Z-qQ30E4>|h_#awC(#-fUX@*|ra7(l2EGFp!T0+t# zv{aE6o=nobKasSG{!G%J{zB5y{~_r@`YTDNQU|ub$TH#qEC+b>DaiikRAi^q3Sxsg z4e(!2M|LrtfqW&M334HxvUCoe1DJ0bGtCMV6XZqq_fU;O8S)QKa})$QEB?3Um%CoPpSdFskd2AF~PB zDF|W+qHRWg+zrUq(v8T^qnl8B?#*a^(%*m|dkdh33y`&6j)Jr3RuC_RM2ggOGYD-P zk;N`W7Pt*1OKwNjfw)SMqIUpXMt35=i0(pu{N2bF+=8m1dr)3~uTcLz!0R>uZ@G_n zaokU3jzVbzNe=)RBwa)H2hnU3J%rLyRe2cIH`5~^U5iMGr0eKWP_C!P0N+55qmom$ z%_wseONhsw5IsD}Ev2VW;Z}NDz|R2w8$FBs7J3f(ZDfC*9D~uf;{|ZLk6uK64{ar0 zYe`hN8x%#llez%kMctt1JDo3~(!I2ec*p92uzN@^P(`ok@@3R}gkBNwtAL-U*N{I- zuM5f>B7YN<$LTHPkI~!6pP_dI{aum2hg?;a_XY9+ieID;k#C`o1m$DEPtzv?{#4|j ziTrbse}U2`5tA|emB_ya`48Ngru>NFhv+BdPtnh!;O~GR zrtJd$hsb{s`L811A@UUJJb_4#;bn4}qfCNI3d$V6lU^g+a+&pgipWoudH-J_%TP&F zhK}O0^a|ybIerga*iVy5dYev19lLY};I|M2%1{G4oQlNL@-CeP`a5*C%o;xjU6o58 zlKotuPE|P%k5#hEwHkFkLU1TUFKdKQ7XbbkA)+jOf*_H(m6SR5f|3R;Wn`_)YxH8# z97}~sD@A^}$S)<{tS$l9FX<}44-hPi58{m?zd+=xMb%qG)k|3(tar}u5&2~z-yrH- z2C?^+zQLA!j_er5`p*U6k>HK5miLl=Mz|`5bA>GC|4PPe%K7r%n2Vuhj^UU9?J5Yf zgF40FR)~BZC@Eqgf2AR1juFywSzd2)*kC?>SLPUr%7}bXylDIxFAeNN#K2e@3xjrL02h`lJh)Pt-`(NQ{|-5FryRITX83M7a*rIimtoJLf%|0t{j$+1*FGSV|3P_p;2{|g;{d-=`>-s) z(f*NXaFpN6V0}ehPEC%~SW!R1UtwG$S1+Q*5p|Eq{)*a1WC}B%$QF4;3&3V7FWOU* zKzme-q{!{o-j{Vm+` zY1#jbY}_SJB;#(m`dL{HT+6M82c8o{vPGVk{rl9uAY0gr78a(ut#Zv)S?bs-w=kBJ zjYs5~q)Z)2xkWJvTQUR{QYii|Inpg-prU=VYT?5zT&{gdmiQUj#;C$oYI|gPE7KZ# zg_qSfprHQ=(@jGXM(qHA>Lgg!Z>fV*R zYTkve-j#d1jUGAhp4{z!Uyghr2R;OJKlv*nFHkj(jKB)H?jyO|cwMgjNM_D96@7VF zkc+^mOUzLboO$C7xrc>*6GC@cLhCHW@3&SNZ^`|-6dd19b3{l>_7PjF!jiwN%Kto7 z`9GGc{GT9winJ&G9U~1De~rIKbq8V<)m$J>Sy8n;w^r33KTm2rzP@R$)Yyb|vNhRw z*w(Jbv2`d|1tiM|3LqK{)m5) z|BwC}f3?5PU+0$3H#tG$DJEieFEqx-h-D3Wj;u)~b;xt9}l?+%}%j z4`ky*d7_-G{z9fg#2P4jmZbI`|6!4vDA`qgl04M}t4|^@$wPRi*{I2iWG(zVloal+@n+F@kgluRlwDLYpS{*I zq^1Ea4cM4VcTf##L0j!zWCOFJ{|>VY1+!v=s#+ea!mJdyhlfac;mUBy(7hMB1RSCnZguJ$%k(ag)uM*SPj{=%(>|GS-a ziq1x47yO@jeUHp8_;q5z@3Xr#yH>2(irrN?MO4}M-<9q(A)WExS-`i81?;ogRPEq@ zi&>gS?H#0n!>E6E6laT3RQ@NUc!s1!{YSA$jG`(#VSnZM8J(T5E5wA2$r7#=g#DOc z9)L0QyNU!GItKdeVg0Wef{@0rQiCZ0%mHc3*OV!Sk zalD1pks)nl$Tl*hj||yIh8!b9&XJ)U^Nfu5*+=+IqNP6N&~kkeh5pJ^U+26Mndb1!5Tj-rMvi(Q$f1?c};H-pV? zgb@VwKHWK>mqW5DIy4I&mIcSLcQHa}KCPd$=2=g@{^OH4I=Z@RzZP!Rc%F4P&pMm} zYvS8MKb+V2kbZ3VX|8eMOith$N3a`X?@9WbZZFJ*+)d0J1b2lb)!A$865uZ5Guc0i zo!~CNBl3;xKSaREz>9?XT>(e~Ph=vqt7H%ITiI_vJ=^Y`KJBtD@K`vLEX5>lHneXu ze^XcFMJfl|x>3p6QEbGM;Y%tQWZic%JCkL{(9p(Vpijv| z*)j6a#ukvL64`XK{xn6=?M0|x?*Cqn{2))0>qe$EQ-he%u0eNg7}p;aS-0n)?~zG= z+$^7-E{~ynMyA{!O`7F1ndHNm5PKEv1+69uiX^E%QKxUM(Wx=vPxzzY^&^y#nCeD= z&E9^k?$GVLV9GlN_wKbkq?UISg|zaHpX89OyrVFrmv{6KEU(QYZLTP=VWaoQ02Z>7 z{wBz1YD$j|cfijaDt@twT+qKX16pnc$zz=eQQvdCl;R6Wv-8IW!+)UX38 z5TLGz#T;I#))c8Mi~j|RoJ~TYxq|Fy3$Zj{R>oAlX-3z8Ns~j@1tbneN=xQ)vcnMRU|c@NA=fa zkZZ_)9{I1Kz%O$B`BXQwW~kJ7KGk1Bb%iyBQsX65f2IYVN%bqJuB4_!YFxoW&Ovym z9RjmL2W*L%zO%LJ{)|(p6U~l$(j6C$8qiVCP91S%V}f1W4>%pi8}97i z8bam2Foezhh5**b+wH1$H<`Rc`p(R|Ei}hOu|{^$ceYp^fZI&j<_wSx2Ng_Zm7H-! zYF5d%z{QquL48*W4yxi6yGNGi0ZW0^E3QC%x;QT&3Rx=Mj^Kc46(u@lOyn^;Nt6WO{2k188 zg9xnp3`Lr1f@m9rIXXjpsn=5#rlfhd=2vbj+{YZ@1A@S@5r>d&zIW>4d#7G;?=;k| zxQFpI9NL^C-&Rpzhr|NQyG_K7D@1b+H>qUAzGo^c6*^1d7m}eq65i5J!dtQu-og?# z{Oc0l@}En1%T5x8*_A}NSLYIQT;oz9OoUx*Y)y5!*@@4!p0Fa^wldW?M*;JN z%zQGR-7A%%e8J6Z8Cc3bQ4%%#kk4iEASQ!#PKJD*B4XNR?thfo{|54}QX;F(-d39e zn6y3;oX--pumnf21gPycd5We8iQR@JLY986pcgUye5J^hp`WjqA#`gW^p}okuA=J4NJ6#6 zYzL$JnCk!=y=s>>dIaVCt~_IWinK&S47se&1_mdn?+%qkyDhZehILUPpNu%r#bH6EkXmWlf>lOwl?D>TMe$o@+Rj5!DA9s~$yl1U1HYQk%kz*@eB|BH%!_0RuY zvjbJvo9*$|K0S7Wd3USoXacux@`|a^TmduJtFPDD9bO00A!zSwV7xINhrLFp6MFE}z z+!=Iaa97Z2E}M&#bsqPI_E0VeYt40J67tZ(9@Hy!t3^I=S1>1oyMgBfT^YOpxFhJy z;DteV(9M11u4DUT1pM>Xu~ilX3q(mLmoUO5ohSj1{Ghv1v3Lx!`bgtNzzc$f8N9gf z-0qx<`7~!cpZm+_eg-i#h#?`JkgABGB8G%qLY`uViWw?osE{F{GpA`OZZ;2R)>51q zOIb#aG8|A7phH^)S@)J!QF#IGSdr)P9qni;YTzlA(b3NaWncjVS zMJT7zp1z8}QF{YlN!6V~=B>0_Aqg#XZed^F*$!)SpeYQ{$yhGR99$-_yr9M%xI`=y z%z@?Qo0Qxv#SN>-TOV`*EPy2e%mv7{0;$|)AA^877}FUm0(*C`pmTkDs2JT@UXOVn4Cdd;l2nm zu@$b$#nKBpQSL65BsUNTlet0A;gL$eN~PSx&8qpktl4SREJ4kn+Kx?J#MwayuR*I% zb;E}0E3hKP8goGe-pjkq^@D-(M(&Y9`R;YhGY4BP*d)0)hl{b4KyY`Ngv-)|0zOQz zW-Jpb+__=5SaR8R0x#lbgMvj?v*Lep@@Dtb%pS`-^_F}rNLTUE7%RqJREaoAj6+LL zSV@l>~R z4Tkk?a4YKEnr)D+dzz>_M#MxY<%^xbT6x#Ea(#P4)6@AD(|Vb32K9-)&f)0mOzInc ziPuRhNrM-68T(M~8n@Kp-e%S|u2QP6RH&o4C&@R-S1HCiWpkiYN!G1Xx;!!n)$0@r ztXEL1@TWoCU;_ktMm9Do#x=@B*&y*T&^c81rQGEqZt|r}?Y+smujFpNgTAPvtfG*w z61u&sl$(JRV9VdftZH1VZ0;xvT&*M__gQ^MaygIWY~m|Gj_usJ{M7}I=W=qmTQJ2v z#&dGl5S$|($vqjt^>vDgk^4Ykn=DLrj|6tTg(>bKz&2Z$>P9H-W=P{3dXHw8%QmJ` z+7+o(-+v%MgXi=WZY2wMt`P40e!{JUaQ`EOyFp0{Hw#_fm=TUey(uFci+XcLI2QG9 z8R1yeTP#e=h-wp}+92xMRLXWV>yaMJRp~3KRNeL1#I6@#9$iS* zYxBB-ngYHKx`?n#^(Cz3Pd6>2g#Dt|B6Iy+EY}q#yJ`yM>gyF=ikA?7(6V>BFnxdD zUAtM4y8BnXlpHy#wpqM{E9N(DtK^U!2OUmJc3AnUx1+4byofY!&)2v2dZa{1t<>5> z+VGIAOWeQ?lccW54GQR8ksB5BjvA-9*6ksejx$;7W-Mu4eEM%vA~!4Mjih-iD6Z;i z|EAcx_^!2Z6|=hqvrrMz`&=Lhw%}OBJ4uMIEU+L$PIoCE%6d>FDBbElaFwz}od3md zr&3*!Tb1hD6jnq9Tom(F#;FRHedKnfD{=??vaZOT@X7plDUrJs|2<0NUeNDTYVTL< zZtT}xeC;bqeBN9pl=dl<22J#iWa}&D7njhT4RYy7c7B)XDDQYc2|1G(mvui4U7GqT zxAfK9PhBc22qzpIC6Y`{T$0saaQ_K6zay3s&`8}D z#OWUL$Q2&DhlUTY*`nBmk|0XKJPJIo)D`i1G2bKejOL=m1x3s5qj*n)>f4QOr3fc) zSJ1Jpy~llPAM&6q`7)5*7a_CovrNzvPoJ&@UW@tW+6v|dUBP_*wqc!Y3+D1i2UuNKl3t){&ny4@`=;)Wl8ORs_WNv& zte}pPj*?z>m^}8PCFX}2S1H)xOos$#f$4>`;{Xw^2gi}Z-#`FqIvEJsOwR(rpy^H^ zatdODbzzsmIbuWTf_xQN1a*sw%??!Lpz7|KJWr^^8JvSCY9=Fb(UX+!iPAj@X(|d- zzKOB5i$0Y;B)y$`cUt|}MNo-+6-TN$78QY^2sLv+&B0QFT@MLT~_)|aDEVB@CHPU`Ud`50C63E2*y*M-)&8}m{NDQxycq~ zCbhfI7VEaA)x~#$>1lOY(|Q%9zchM5F~1yAhc@ac=;Z(JBo$#({U&)tU=#1b>qxUJ z>iOomXC8HRgKGv$9fNziDjl^iD%9n3^24I`c0=j4TM^Yl?c(Xn;o@$DaY@v=PVVx# zx@)h)pv?w6RUyrNI-gytSt%LmQVI?+4>ykT9Z5dZs=E~#5(L${(xAY8ehS+cN-?gSY;h8&;M0^0+PQNW z*#_$OUE9^dKKZ@*E0%p~LmThsiW})#w$KkywSKc~ysq#u`UZ}A<4pm+#o*fle22kz z1^6C=?+fq)20s+wM+|-}z)u+bRDhoWoNEtkmg{ejjn5UI+xSB96&PPCzCzgI7{{IW1Ow}DvWbf-@eAVs?RXaQ++;TmFgR1tmf=| z)mLe(QGHd$1*&hfaiQuPV_c;A_A}OUcCqT)-?&8e9bjCl`VKTMQ+1-bSmO~Ee2eNk&UjSy9dA6QdY>oban*N%@r3HDGoDm^^~O`GZ@%%g>hl}VsJ?*l ztm+FI&#AtU@x1D5FkVo7VdF*B7csV~z6C}S>L;U1^)(vZs;|j-N%fs*Y*T#;jULt4 zZ1k$W7UN~r*J`|?`l80Gs;|v>P4%5*ysr9U#v7_HZoH}b62@DquibcC^(`{qQGJVz zcU9jK<2}{4)OcU@oosxd`u=2msQUhFe5CsRVtlOn{>S)4_5IcORP~|v&r~0x<N}T=?W*rQGXA0Za*bb9-zqYGReh_;*rEE)r@3n=a0BoD*O1Mj zY8zFAiYt1`SM-*z$OJyc{K2LOAB#hFtlMPutB828f;u!TCUGOi*s=_^Vw-=n9C=Mi zR==)L;W&;OsQL|sR`eu2vHg?+5#{YsU|KoeL#)rtgnj~iS zro{|fXQlzTdTiBiDRQQAgIW1)v+^SI@{K9)Sd_vMER5<&RX-)m#tO~64-9F{^AW=F z@dyb3FaSIL)x`S zI&<1WLA`p5Dbg8cs}HSKyYAm^wX$2Stp4r&`sWGp-yq*!SRj-MFo{y`!^2bMdHiiYl+|1uOVSBBYSahsP2F`gjMib(bHv|rd5BWkSA3H z--&Na&5Kh0u*bhbsdVw3D+{!~`eTJJP=hM`2EI)2NXy-DU%Roz=AlnKmb2g7y*%)~ zvPFgCAeXF7r4WarACG$@jDP|A#!av-K$~z{Uq?2ls-4O2lh!F_P43G3vAm(ZN07)O zc^Wpf_X>*KhXOwqGImveq7VX_j9l&ffljxF_-BnCY8O(qxA(Fylde$6zH7FI!f1PY zMM$kwa$Ss|dFT3ew^P8d^X~QQ+S#SWu-4-cg%4ZzAb)%e2bw&}hR*5-i4PmN_jpiyhol}`8Z*XC#U4UhD<0>#K2&8+RS&eVBuTLNC{)hPPX>$gBoY5Od(N*md zVKCcZGagA84SR&xYHXYGV1Uz6~s(@#*+I8Q9T#wV!$N%B9zw}g#PQvK7se4ggU ze-oE^{F>wms(YF$w!!UcuUOra?1E=xm+ch!TrpN@n=gV0w-A3b?G=W*o9b++sC{du z_LuVRYGdW0_H9Cr-|toqRJ%PBGJIiaK_A*U1LL@(&#n2Lq}Vju<6<^@%h|Or?|4^+ zlj+||=Ev7tDcQJ{>bq$~V>i`rqw>aW*tV6LFBPfdOQokP@|EJ(B3~>1E^!Ae@I4~m zDE_2*C+v|tIbv_SP%GL87MJ8rjyDwQ0)8&wif6Jgy2)o@bC}i05F>p`Jeg&b0~; z@=WoJ1zzAO@eD=PqdiA@#(4^Xc|o7-Y4I%Z%s|ayXmz;fC{Ls3B+m(+(ST>7?gGzJ z&k|4AGZnP?o}j1A(*Rm%-r;;-4{7*)l0&?W?uu+zlIuNVIQ#^hSZ zhbE4X_Fn%lO?P0svPJIl9A10}BBlaJR_dwrgeodLA;85L(L@U?1w7V-D^SHE z|657Ot@V{#`#0tOeAjYQWo~6&Wj>zn%7V(md3N?Z(2oe;n$Z_en)_Jp^nHFSksUw& ziHpna%6dHQ{Pft@Zvc0Huc-O?Z`a%+Z?W^yQhXI+XheU2&KB=V9wx;uzTh)&$;JL& zS6ovp2{lm2=AO;65rjNY++Snaf^XfBnjsSSrg1UcfAmEWL;ixZe~Y-?Mnnlx$%CmB z&ZG75-Yqt`SMHLHh~hOE{v)Jfm#Rh7_zu!EEPxd$Ur#XVy(-s_&k+U}6O zsTA?K&Kli8CYByxhQLEoF{Hi(9`Y8YyQ}!FRH|#uDsZg+UvlhP)7@z@O|B}rja??% zxD9i>u}8R3`6W}ttUXHnN%57$Thn?u-jj@Mkk_F5Q}|N1uGB-_8#(qbEQU|`9{JxR zruly$rfzC{kGdb}t^0xC;zWL+N7$1IYJnffuGv*og0lff6^&reMT6l8zQs`OTSS07 z5(DfD0YM$c>d{7T{DIIF&d>MwGO2Wtkn=I|qCW>4+_#kEyCcvZ7iTg54(csRTE2kA zJeh`3?Fq7#s#>qUwQ6MVKfB)C{YPjpU&J3jT7cY#+X{u(`)pn-x*Jq3-hEs3*I79TPG!C+Mldg!pRQ&Tn5d=)?c4)jVOpQEImJh8Ild9$afRek5JKQ%pX?lg5| z-+Aj#P0w3tnp)m>-uhGjlX)xuXY=OnH*Zz{Z|7}fzj-U|H*fZS^ENg!Z(GtccAS{8 z68BIMO_qpXOqK9w>_dRlCgC-Tv?jfJ`}EtL1v{^LNoyMV8M-(sI334Jql3-;E-i$6}K zGCcd%`+Vnm=<*p-(dA=urq7EqeXdt_>a)n=^(#1tO)<^RUHk#xY-fA>N59^3N-U|| z137K~O-`To<>XyQB_9nKV4Bruzsc(LT?2lXCrY z%h`>=DF58L#t$^+=Sa_2wFea8)A8#87g8=#9)HgaQ9Fk48*}g1WWst~ia)|lkqn>D zbVK=51^*}PRthgt{PbkI!b_OF$iw+l1r!Tr-Rt5ZXBuhm<@a$#{3i1?{sA`AYF~rD z;Ik-}Z-o8I^b)xc1(pkBl9}ciAIjnPua02OBiQJ2Jkre-{EB}c^c2iNC6BbZ08(%X z|K^RE+qhx(RCsuH_XhFaCLVI}WrAaiotxRsW)+3v({6h(=ca=9z`>5aH(mh+x$_P7 z=|g$^^4Gx}b>_Gw5Bn}kHa92Dx6o26cT-WpY$z^`&u(_u(>bbowo}oPdAPm^M#sT=j1CT z;b2ueiLaiRV9yd9OvqqP|CgAd%Hp7XePv0|vA(l%Fn`DH?I7Ok{a35P{}<$~?dG z$R5?Bi|BL+;?W9^;<0;Ni2L^N{NA&VXJ3y4fi1^?dm?rKWnjdG_}l z=qWVM^*zzg9Cu_Grbo#quZ{z~=#Fb5}N2&xz& zv=8x}o}78E#(z-#cELdAo4^V&Y{jEhc+?7$?O(uTJJo+m77Ga#3c#iiqZ{p+0Cs~A zjR(MVZ$#upo-y8K4VYOq}`yoJ&d6$zAq6Atig;s|^ zJHw&9kyhv*`7I`xt@85`;YM)>p2cFcLQSo z2qOMQPm||FPu!F6w0jnL7JHU>mU>S1p2EkKcLjM*Bk$?tJ%hYwl6NI}&nNF1@?JpR z3(0#CdDoKnV)9->-b-=Rkaq)luO{zC@?JyUYsq^Zc{h>wdh*^%-rLA~J9+OQ@15km zi@bM}_a5@zOWrNyeU!Y9k@s=(K0)3m$@>&}pC<1!6%Gkhhn-FO&Bb z^1e#mcgXuLc@gQnPu>s6`yqKhBJaoK{e--qlJ^_(eoNl($ooBce<1IVGrBRR5WnLM3AXSu4QoeMa@K@6H5Xr}~dFCLr^~E5i0QVFQA;7;HYG`d6}S z3IroI0UJzV0!%=M5u5~L*jtzY_!nXFg&|lb;D9;o2Q&DZt${5dNn-vzp8cT$j~64& z*h1D4{+o8Noc^zy!fxzf_eSsv)$hQr;49=^VF%TB$Z4cQuKy&nUwlIKUuKXmss5`B z@)gzpoI!r(|GEeN2SrqIzqBpsY2dqlPec1QkJOX&jB)WNyQ=q$5y=gF3iY`21^yH~ z6M6kQL8^P1YF{R)?nA2m5RV_>wf-nPgBQtWSGD_P^GGS@v%mgZ^V5*VZ!_V0I-F;} z|IVcLGWBPs!lB5L z`O_m2N8;4s&qc1}+a*D;J{ma{UJHwnzb{nAw=o?<_~XDI__n9Yk6!-84ui`tBr1*kFb9XX}?)Hk+iC!?_*KJvZU%6$lO<>U#R2dDatcjas zXt3}a6miel61!RAy2dm9x}6FgW}$7Dg-)|DU1E3I9d^4NA+lC{Y$_$<#V&r2w$8Xp zLpdXBEYLlkzaH}_SBEe!LEZYqZwP3JF>B)Az?3q+bzBQG4D3t=#g&Ei& z-nzU*noUt3F`X$*eBRlq_4#$kif!r7rdXY(CRKMUyTLchmd5zqH|wx)EO-uH~0L8?Q}WWc)DD_QtoP8DU073u#0|{Tzi%*acm)eM{tJR=XV6B$<_}E-V%I_ zo&FPEsGa@gz-a=p82*2aT}x~mRTTBTnQ`O%NJtt)%dkL2LXZUu7Hp6ZNJU}+tPq<# z%}gchAt8ih2`Rgf;Fg*=!Neqy?K(~@^HCO28VM%GjjY7AU3s|DW=yx%bZV z#Ezq15QSyXy6Tza2J(F{kgM5XtNDZM^N50KgyZKZb= z3~!#A$7f@D9>bFVQot}T$@aB7k8+RP8hngfY0i?ci4Cx^(18{;($>QB}XpMI~4O}Zv0&*Y%k!|=dkzP`tY z<+RSAXq~fv)$*pS50M+cTYOsHQoa%@e-rhA)2593 z9i8R6{Mc-ZmSs?e z(?w6Hs-Zm5RZvBmVCf>LbshrOLDg8+K;>EHK`l$JE4Jji3@Qg>tG3R8%EDOH)>%*` z82boy2~-Bg*1L2DRDouRT8M|fmu+>SL}E^^$G*G?3EiheA;BGJ{ zPT+2Vw?(21UYW#%Hxam1@TwFpf>$K54&FM68hAAldGPWimYL@yaC6}0NMymwk|=>! zB9Q?vL!y9Q0l6lAfCHp=1CAc0U;Lw7T|G_>{5)H69@Pe(K)r!#ljgK~ldbxx)9w5m zRq7d2skb@0$O(UGJJkLa;{A5KXexDq`n;7qYv~1YyK5n*3=Ju30vr&?w;kfY<3Xcx z0;_!S*tfz^w;cBQHXohES1SzBmP;AtjyzxYZv`2x%JY^GJl&_}qP8@L?i}CsO!$4m zx0taQyY^djlKZ-tl12V|ZqRXbAHDv)&7MERo_B<^vg52{-@F+y&$lA`Y!hnuabu|; zA9#jujIIb4m^ZK+0^v>gmf+rfn+u^=m1uBoRJlj#5~^9|;Swmj-|P2=MZmk*RXvM5 zU*j9z0;ab`cGJKvnFazC?y1WLH)l#px6G72Dw(Qt+!)TGDkCL#OSq!TlDyaNxpmnx zTsbV;Kjo1API9-DS!_h+1!UvyPEda;C;t9AxfVPA@J(FHv3=K)wUV=zTgFxT6%{WV zx|hqLd-*G>zPa}58=~s#r~LnJ^D+N`p%J3$8ec|3zw)p2*Lx@f_A-+Wb=L}>e8Y-) z73!nuFqoDObVEjhVd^Lq9tkR_(k#<#NK9Q9B&e7;^l{A=5G=!n+64Uh0VL{ZBrIEB8&y z8dMDJ;C7avd82^({T?&zdoUIY;r!+a4Bj0+*=#+*XfoI(lR=tm9^Y&>yM~(0N1Dy& zn@x)Few5#R^y&@Xg_^|Hz&vmK%k7SKnB)TY>vIY_eA0BJlQ%lUjy}dX>^7Zrr_Sj} z=Pjc%;^?~=Bi*L+8Z{7MT;+Ci0qQ(1OkRM!oj|{hJB{Q;&=-N^Ht20$X`IEDb6hZ) i9(8n$8_D1>|BtGZFL){axcB1AUMlt|#s33=Ra;d7rU|P6 literal 0 HcmV?d00001