From 4613e292d2da614e33333eef4b0cbe8bf1b0bf8e Mon Sep 17 00:00:00 2001 From: Tobias Schneider Date: Wed, 13 Jan 2010 15:57:21 +0100 Subject: [PATCH] Initial commit --- MIT-LICENSE | 19 ++ README | 6 + gordon.js | 41 +++ src/Color.js | 40 +++ src/Cxform.js | 35 ++ src/Matrix.js | 35 ++ src/Movie.js | 811 +++++++++++++++++++++++++++++++++++++++++++++ src/Stream.js | 345 +++++++++++++++++++ src/SvgRenderer.js | 448 +++++++++++++++++++++++++ src/_base.js | 171 ++++++++++ src/base64.js | 107 ++++++ src/inflate.js | 737 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 2795 insertions(+) create mode 100644 MIT-LICENSE create mode 100644 README create mode 100644 gordon.js create mode 100644 src/Color.js create mode 100644 src/Cxform.js create mode 100644 src/Matrix.js create mode 100644 src/Movie.js create mode 100644 src/Stream.js create mode 100644 src/SvgRenderer.js create mode 100644 src/_base.js create mode 100644 src/base64.js create mode 100644 src/inflate.js diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..ca0e0c7 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Tobias Schneider + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..4d20836 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +Gordon: An open source Flash™ runtime written in pure JavaScript + +Copyright (c) 2010 Tobias Schneider +Gordon is freely distributable under the terms of the MIT license. + +... diff --git a/gordon.js b/gordon.js new file mode 100644 index 0000000..df278eb --- /dev/null +++ b/gordon.js @@ -0,0 +1,41 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var _g = Gordon = {}; + var _loadedUrls = {}; + + var scripts = document.getElementsByTagName("script"); + var i = scripts.length; + while(i--){ + var match = scripts[i].src.match(/(^|.*\/)gordon\.js$/); + if(match){ _g.ROOT = match[1]; } + } + + _g.xhr = function(){ + var request = new XMLHttpRequest(); + request.open.apply(request, arguments); + return request; + } + + _g.require = function(url){ + if(!url.match(/\.([^\/]*)$/)){ url += ".js"; } + if(!_loadedUrls[url]){ + with(_g.xhr("GET", _g.ROOT + url, false)){ + send(null); + if(status == 200){ + eval(responseText); + _loadedUrls[url] = true; + } + else{ throw new Error("Unable to load " + url + " status: " + status); } + } + } + } +})(); + +Gordon.require("src/_base"); +Gordon.require("src/Movie"); diff --git a/src/Color.js b/src/Color.js new file mode 100644 index 0000000..b8ba62e --- /dev/null +++ b/src/Color.js @@ -0,0 +1,40 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var _g = Gordon; + var _properties = { + r: 255, + g: 255, + b: 255, + a: 1 + }; + + _g.Color = function(color){ + if(typeof color == "string"){ + var match = color.match(/^#([0-9,a-z]{2})([0-9,a-z]{2})([0-9,a-z]{2})/i); + if(match){ color = { + r: parseInt(match[1], 16), + g: parseInt(match[2], 16), + b: parseInt(match[3], 16) + }; } + } + for(var p in _properties){ this[p] = isNaN(color[p]) ? _properties[p] : color[p]; } + }; + _g.Color.prototype = { + toArray: function(withAlpha){ + var t = this; + var array = [t.r, t.g, t.b]; + if(withAlpha){ array.push(t.a); } + return array; + }, + + toString: function(withAlpha){ + return "rgb" + (withAlpha ? 'a' : '') + '(' + this.toArray(withAlpha).join(", ") + ')'; + } + }; +})(); diff --git a/src/Cxform.js b/src/Cxform.js new file mode 100644 index 0000000..7d696c8 --- /dev/null +++ b/src/Cxform.js @@ -0,0 +1,35 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var _g = Gordon; + var _properties = { + multR: 1.0, + multG: 1.0, + multB: 1.0, + multA: 1.0, + addR: 0.0, + addG: 0.0, + addB: 0.0, + addA: 0.0 + }; + + _g.Cxform = function(cxform){ + for(var p in _properties){ this[p] = isNaN(cxform[p]) ? _properties[p] : cxform[p]; } + }; + _g.Cxform.prototype = { + toArray: function(){ + with(this){ + return [multR, 0, 0, 0, addR, 0, multG, 0, 0, addG, 0, 0, multB, 0, addB, 0, 0, 0, multA, addA]; + } + }, + + toString: function(){ + return this.toArray().join(' '); + } + }; +})(); diff --git a/src/Matrix.js b/src/Matrix.js new file mode 100644 index 0000000..9b01a28 --- /dev/null +++ b/src/Matrix.js @@ -0,0 +1,35 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var _g = Gordon; + var _properties = { + scaleX: 1.0, + scaleY: 1.0, + skewX: 0.0, + skewY: 0.0, + moveX: 0, + moveY: 0 + }; + + _g.Matrix = function(matrix){ + for(var p in _properties){ this[p] = isNaN(matrix[p]) ? _properties[p] : matrix[p]; } + }; + _g.Matrix.prototype = { + toArray: function(){ + with(this){ + return [scaleX, skewX, skewY, scaleY, moveX, moveY]; + } + }, + + toString: function(){ + with(this){ + return "matrix(" + this.toArray().join(' ') + ')'; + } + } + }; +})(); diff --git a/src/Movie.js b/src/Movie.js new file mode 100644 index 0000000..7f91941 --- /dev/null +++ b/src/Movie.js @@ -0,0 +1,811 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +Gordon.require("src/base64"); +Gordon.require("src/Color"); +Gordon.require("src/Cxform"); +Gordon.require("src/Matrix"); +Gordon.require("src/Stream"); + +(function(){ + var _g = Gordon; + var _m = _g.movieStates; + var _t2p = _g.twips2px; + var _options = { + id: null, + name: null, + width: 0, + height: 0, + autoplay: true, + loop: true, + quality: _g.scaleValues.HIGH, + scale: _g.scaleValues.DEFAULT, + bgcolor: null, + renderer: null, + onLoad: null, + onEnterFrame: null + }; + + _g.Movie = function(url, options){ + if(!url){ throw new Error("URL of a SWF movie file must be passed as first argument"); } + var t = this; + t._state = _m.LOADING; + var s = new _g.Stream(url); + var signature = s.readString(3); + var g = _g.signatures; + if(signature != g.SWF && signature != g.COMPRESSED_SWF){ throw new Error(url + " is not a SWF movie file"); } + t.url = url; + for(var o in _options){ t[o] = options[o] != undefined ? options[o] : _options[o]; } + t.stream = s; + t.version = s.readUI8(); + t.fileLength = s.readUI32(); + if(signature == g.COMPRESSED_SWF){ s.decompress(); } + var f = t.frameSize = s.readRect(); + t.frameRate = s.readUI16() / 256; + t.frameCount = s.readUI16(); + var frameWidth = f.right - f.left; + var frameHeight = f.bottom - f.top; + if(!(t.width && t.height)){ + t.width = frameWidth; + t.height = frameHeight; + } + t.currentFrame = 0; + if(!t.renderer){ + Gordon.require("src/SvgRenderer"); + t.renderer = _g.SvgRenderer; + } + t._renderer = new this.renderer(t.width, t.height, frameWidth, frameHeight, t.quality, t.scale, t.bgcolor); + t._tagsOffset = s.tell(); + t._dictionary = {}; + t._jpegTables = null; + t._frameActions = {}; + var i = t.frameCount; + while(i--){ t.nextFrame(); } + t.reset(); + if(t.id){ + var parent = document.getElementById(t.id); + parent.innerHTML = ''; + parent.appendChild(t._renderer.getNode()); + } + t._state = _m.LOADED; + if(t.onLoad){ t.onLoad(); } + if(t.autoplay){ t.play(); } + }; + _g.Movie.prototype = { + play: function(){ + var t = this; + if(t._state != _m.PLAYING){ + t._state = _m.PLAYING; + var interval = setInterval(function(){ + if(t._state == _m.PLAYING){ + t.nextFrame(); + if(t.currentFrame == t.frameCount && !t.loop){ t.stop(); } + } else { clearInterval(interval); } + }, 1000 / t.frameRate); + return t; + } + }, + + stop: function(){ + this._state = _m.STOPPED; + return this; + }, + + nextFrame: function(){ + var t = this; + if(t.currentFrame == t.frameCount){ t.reset(); } + var s = t.stream; + do{ + var header = s.readUI16(); + var code = header >> 6; + var length = header & 0x3f; + if(length >= 0x3f){ length = s.readUI32(); } + var handler = _g.tagHandlerMap[code]; + if(typeof t[handler] == "function"){ t[handler](s.tell(), length); } + else { s.seek(length); } + }while(code && code != _g.tagCodes.SHOW_FRAME); + if(t.onEnterFrame){ t.onEnterFrame(); } + return t; + }, + + _handleShowFrame: function(){ + this.currentFrame++; + return this; + }, + + _handleDefineShape: function(offset, length){ + var t = this; + var s = t.stream; + var id = s.readUI16(); + var d = t._dictionary; + if(!d[id]){ + var bounds = s.readRect(); + var fillStyles = t._readFillStyleArray(); + var lineStyles = t._readLineStyleArray(); + var numFillBits = s.readUB(4); + var numLineBits = s.readUB(4); + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + var segment = []; + var isFirst = true; + var edges = []; + var leftFill = 0; + var rightFill = 0; + var line = 0; + var fsOffset = 0; + var lsOffset = 0; + var leftFillEdges = {}; + var rightFillEdges = {}; + var lineEdges = {}; + var c = _g.styleChangeStates; + var countFillChanges = 0; + var countLineChanges = 0; + var useSinglePath = true; + var i = 0; + do{ + var type = s.readUB(1); + var flags = null; + if(type){ + var isStraight = s.readBool(); + var numBits = s.readUB(4) + 2; + x1 = x2, y1 = y2; + var cx = null; + var cy = null; + if(isStraight){ + var isGeneral = s.readBool(); + if(isGeneral){ + x2 += _t2p(s.readSB(numBits)); + y2 += _t2p(s.readSB(numBits)); + } + else{ + var isVertical = s.readBool(); + if(isVertical){ y2 += _t2p(s.readSB(numBits)); } + else{ x2 += _t2p(s.readSB(numBits)); } + } + }else{ + cx = x1 + _t2p(s.readSB(numBits)); + cy = y1 + _t2p(s.readSB(numBits)); + x2 = cx + _t2p(s.readSB(numBits)); + y2 = cy + _t2p(s.readSB(numBits)); + } + x2 = Math.round(x2 * 100) / 100; + y2 = Math.round(y2 * 100) / 100; + segment.push({ + i: i++, + f: isFirst, + x1: x1, + y1: y1, + cx: cx, + cy: cy, + x2: x2, + y2: y2 + }); + isFirst = false; + }else{ + isFirst = true; + if(segment.length){ + edges.push.apply(edges, segment); + if(leftFill){ + var index = fsOffset + leftFill; + var list = leftFillEdges[index]; + if(!list){ list = leftFillEdges[index] = []; } + for(j = 0; segment[j]; j++){ + var edge = _cloneEdge(segment[j]); + edge.i = i++; + var tx1 = edge.x1; + var ty1 = edge.y1; + edge.x1 = edge.x2; + edge.y1 = edge.y2; + edge.x2 = tx1; + edge.y2 = ty1; + list.push(edge); + } + } + if(rightFill){ + var index = fsOffset + rightFill; + var list = rightFillEdges[index]; + if(!list){ list = rightFillEdges[index] = []; } + list.push.apply(list, segment); + } + if(line){ + var index = lsOffset + line; + var list = lineEdges[index]; + if(!list){ list = lineEdges[index] = []; } + list.push.apply(list, segment); + } + segment = []; + } + var flags = s.readUB(5); + if(flags){ + if(flags & c.MOVE_TO){ + var numBits = s.readUB(5); + x2 = _t2p(s.readSB(numBits)); + y2 = _t2p(s.readSB(numBits)); + } + if(flags & c.LEFT_FILL_STYLE){ + leftFill = s.readUB(numFillBits); + countFillChanges++; + } + if(flags & c.RIGHT_FILL_STYLE){ + rightFill = s.readUB(numFillBits); + countFillChanges++; + } + if(flags & c.LINE_STYLE){ + line = s.readUB(numLineBits); + countLineChanges++; + } + if((leftFill && rightFill) || (countFillChanges + countLineChanges) > 2){ + useSinglePath = false; + } + if(flags & c.NEW_STYLES){ + fillStyles.push.apply(fillStyles, t._readFillStyleArray()); + lineStyles.push.apply(lineStyles, t._readLineStyleArray()); + numFillBits = s.readUB(4); + numLineBits = s.readUB(4); + fsOffset = fillStyles.length; + lsOffset = lineStyles.length; + useSinglePath = false; + } + } + } + }while(type || flags); + s.align(); + if(useSinglePath){ + var fill = leftFill || rightFill; + var fillStyle = fill ? fillStyles[fsOffset + fill - 1] : null; + var lineStyle = lineStyles[lsOffset + line - 1]; + d[id] = _buildShape(id, edges, fillStyle, lineStyle); + } + else{ + var fillShapes = []; + var i = fillStyles.length; + while(i--){ + var fill = i + 1; + fillEdges = []; + var list = leftFillEdges[fill]; + if(list){ fillEdges.push.apply(fillEdges, list); } + list = rightFillEdges[fill]; + if(list){ fillEdges.push.apply(fillEdges, list); } + var edgeMap = {}; + for(var j = 0; fillEdges[j]; j++){ + var edge = fillEdges[j]; + var key = _calcPointKey(edge.x1, edge.y1); + var list = edgeMap[key]; + if(!list){ list = edgeMap[key] = []; } + list.push(edge); + } + var pathEdges = []; + var countFillEdges = fillEdges.length; + for(var j = 0; j < countFillEdges && !pathEdges[countFillEdges - 1]; j++){ + var edge = fillEdges[j]; + if(!edge.c){ + var firstKey = _calcPointKey(edge.x1, edge.y1); + var segment = []; + var isUsed = {}; + do{ + segment.push(edge); + isUsed[edge.i] = true; + var key = _calcPointKey(edge.x2, edge.y2); + if(key == firstKey){ + var k = segment.length; + while(k--){ segment[k].c = true; } + pathEdges.push.apply(pathEdges, segment); + break; + } + var list = edgeMap[key]; + var favEdge = fillEdges[j + 1]; + var nextEdge = null; + for(var k = 0; list[k]; k++){ + var entry = list[k]; + if(entry == favEdge && !entry.c){ + list.splice(k, 1); + nextEdge = entry; + } + } + if(!nextEdge){ + for(var k = 0; list[k]; k++){ + var entry = list[k]; + if(!(entry.c || isUsed[entry.i])){ nextEdge = entry; } + } + } + }while(edge = nextEdge); + } + } + if(pathEdges.length){ + var shape = _buildShape(id, pathEdges, fillStyles[i]); + shape.index = pathEdges[pathEdges.length - 1].i; + fillShapes.push(shape); + } + } + var strokeShapes = []; + var i = lineStyles.length; + while(i--){ + var pathEdges = lineEdges[i + 1]; + var shape = _buildShape(id, pathEdges, null, lineStyles[i]); + shape.index = pathEdges[pathEdges.length - 1].i; + strokeShapes.push(shape); + } + var segments = fillShapes.concat(strokeShapes); + segments.sort(function(a, b){ + return a.index - b.index; + }); + d[id] = segments.length > 1 ? { + type: "shape", + id: id, + segments: segments + } : segments[0]; + } + }else{ s.seek(length - 2); } + return t; + }, + + _readFillStyleArray: function(){ + var s = this.stream; + var numStyles = s.readUI8(); + if(numStyles == 0xff){ numStyles = s.readUI16(); } + var styles = []; + var i = numStyles; + while(i--){ + var type = s.readUI8(); + var f = _g.fillStyleTypes; + switch(type){ + case f.SOLID: + styles.push(new Gordon.Color(s.readRgb())); + break; + case f.LINEAR_GRADIENT: + case f.RADIAL_GRADIENT: + var style = {type: type == f.LINEAR_GRADIENT ? "linear" : "radial"}; + style.matrix = new Gordon.Matrix(s.readMatrix()); + style.spread = s.readUB(2); + style.interpolation = s.readUB(2); + var numStops = s.readUB(4); + var stops = style.stops = []; + var j = numStops; + while(j--){ stops.push({ + offset: s.readUI8() / 255, + color: new Gordon.Color(s.readRgb()) + }); } + styles.push(style); + break; + case f.REPEATING_BITMAP: + case f.CLIPPED_BITMAP: + var style = {type: "pattern"}; + var imgId = s.readUI16(); + var matrix = style.matrix = new Gordon.Matrix(s.readMatrix()); + var img = style.image = this._dictionary[imgId]; + if(img){ + with(matrix){ + scaleX = _t2p(scaleX); + scaleY = _t2p(scaleY); + skewX = _t2p(skewX); + skewY = _t2p(skewY); + } + styles.push(style); + }else{ styles.push(null); } + break; + } + } + return styles; + }, + + _readLineStyleArray: function(){ + var s = this.stream; + var numStyles = s.readUI8(); + if(numStyles == 0xff){ numStyles = s.readUI16(); } + var styles = []; + var i = numStyles; + while(i--){ styles.push({ + width: _t2p(s.readUI16()), + color: new Gordon.Color(s.readRgb()) + }); } + return styles; + }, + + _handlePlaceObject: function(offset, length){ + var character = {}; + var t = this; + var s = t.stream; + var id = s.readUI16(); + character.object = t._dictionary[id]; + character.depth = s.readUI16(); + character.matrix = new Gordon.Matrix(s.readMatrix()); + character.cxform = s.tell() - offset == length ? null : new Gordon.Cxform(s.readCxform()); + t._renderer[(t._state == _m.LOADING ? "define" : "place") + "Character"](character); + return t; + }, + + _handleRemoveObject: function(){ + var t = this; + var s = t.stream; + var id = s.readUI16(); + var depth = s.readUI16(); + if(t._state != _m.LOADING){ t._renderer.removeCharacter({depth: depth}); } + return t; + }, + + _handleDefineBits: function(offset, length){ + var t = this; + var s = t.stream; + var id = s.readUI16(); + var d = t._dictionary; + if(!d[id]){ + var img = d[id] = { + type: "image", + id: id + }; + var data = s.seek(2).readString(length - 4); + var i = 0; + do{ + var highByte = data.charCodeAt(i); + var lowByte = data.charCodeAt(i + 1); + i += 2; + }while(!(highByte == 0xff && (lowByte == 0xc0 || lowByte == 0xc2))); + var header = t._jpegTables.substr(0, t._jpegTables.length - 2); + img.data = "data:image/jpeg;base64," + base64encode(header + data); + img.width = (data.charCodeAt(i + 5) << 8) | data.charCodeAt(i + 6); + img.height = (data.charCodeAt(i + 3) << 8) | data.charCodeAt(i + 4); + }else{ s.seek(length - 2); } + return t; + }, + + _handleDefineButton: function(offset, length){ + var t = this; + var s = t.stream; + var id = s.readUI16(); + var d = t._dictionary; + if(!d[id]){ + var button = d[id] = { + type: "button", + id: id + }; + var states = button.states = {}; + do{ + var flags = s.readUI8(); + if(flags){ + var objId = s.readUI16(); + var character = {object: d[objId]}; + var depth = character.depth = s.readUI16(); + character.matrix = new Gordon.Matrix(s.readMatrix()); + var state = 0x01; + while(state <= 0x08){ + if(flags & state){ + var list = states[state]; + if(!list){ list = states[state] = {}; } + list[depth] = character; + } + state <<= 1; + } + } + }while(flags); + button.onClick = t._readAction(); + }else{ s.seek(length - 2); } + return t; + }, + + _readAction: function(){ + var t = this; + var s = t.stream; + var actions = []; + do{ + var code = s.readUI8(); + var length = code > 0x80 ? s.readUI16() : 0; + var a = _g.actionCodes; + switch(code){ + case a.PLAY: + actions.push("t.play()"); + break; + case a.STOP: + actions.push("t.stop()"); + break; + case a.NEXT_FRAME: + actions.push("t.nextFrame()"); + break; + case a.PREVIOUS_FRAME: + actions.push("t.prevFrame()"); + break; + case a.GOTO_FRAME: + var frame = s.readUI16(); + actions.push("t.goto(" + frame + ')'); + break; + case a.GET_URL: + var url = s.readString(); + var target = s.readString(); + actions.push("_getUrl('" + url + "', '" + target + "')"); + break; + case a.TOGGLE_QUALITY: + actions.push("t._renderer.toggleQuality()"); + break; + } + }while(code); + eval("var action = function(){" + actions.join(';') + "}"); + return action; + }, + + _handleJpegTables: function(offset, length){ + var t = this; + var s = t.stream; + if(!t._jpegTables){ t._jpegTables = s.readString(length); } + else{ s.seek(length); } + return t; + }, + + _handleSetBackgroundColor: function(){ + this._renderer.setBgcolor(this.stream.readRgb()); + return this; + }, + + _handleDefineFont: function(offset, length){ + var t = this; + var s = t.stream; + var id = s.readUI16(); + var d = t._dictionary; + if(!d[id]){ + var font = d[id] = { + type: "font", + id: id + }; + var offsets = []; + var numGlyphs = 0; + var i = 0; + do{ + var offset = s.readUI16(); + offsets.push(offset); + if(!i){ i = numGlyphs = offset / 2; } + }while(--i); + var c = _g.styleChangeStates; + var glyphs = font.glyphs = []; + var i = numGlyphs; + while(i--){ + var numFillBits = s.readUB(4); + var numLineBits = s.readUB(4); + var x = 0; + var y = 0; + var commands = []; + do{ + var type = s.readUB(1); + var flags = null; + if(type){ + var isStraight = s.readBool(); + var numBits = s.readUB(4) + 2; + if(isStraight){ + var isGeneral = s.readBool(); + if(isGeneral){ + x += s.readSB(numBits); + y += s.readSB(numBits); + commands.push('L', x, y); + }else{ + var isVertical = s.readBool(); + if(isVertical){ + y += s.readSB(numBits); + commands.push('V', y); + } + else{ + x += s.readSB(numBits); + commands.push('H', x); + } + } + }else{ + var cx = x + s.readSB(numBits); + var cy = y + s.readSB(numBits); + x = cx + s.readSB(numBits); + y = cy + s.readSB(numBits); + commands.push('Q', cx, cy, x, y); + } + }else{ + var flags = s.readUB(5); + if(flags){ + if(flags & c.MOVE_TO){ + var numBits = s.readUB(5); + x = s.readSB(numBits); + y = s.readSB(numBits); + commands.push('M', x, y); + } + if(flags & c.LEFT_FILL_STYLE || flags & c.RIGHT_FILL_STYLE){ s.readUB(numFillBits); } + } + } + }while(type || flags); + s.align(); + glyphs.push({commands: commands}); + } + }else{ s.seek(length - 2); } + return t; + }, + + _handleDefineText: function(offset, length){ + var s = this.stream; + var id = s.readUI16(); + var d = this._dictionary; + if(!d[id]){ + var text = d[id] = { + type: "text", + id: id + }; + var bounds = s.readRect(); + text.matrix = new Gordon.Matrix(s.readMatrix()); + var numGlyphBits = s.readUI8(); + var numAdvBits = s.readUI8(); + var font = null; + var fill = null; + var x = 0; + var y = 0; + var size = 0; + var string = null; + var strings = text.strings = []; + do{ + var header = s.readUB(8); + if(header){ + var type = header >> 7; + if(type){ + var flags = header & 0x0f; + if(flags){ + var string = {}; + var t = _g.textStyleFlags; + if(flags & t.HAS_FONT){ + var fontId = s.readUI16(); + font = d[fontId]; + } + if(flags & t.HAS_COLOR){ fill = new Gordon.Color(s.readRgb()); } + if(flags & t.HAS_XOFFSET){ x = _t2p(s.readSI16()); } + if(flags & t.HAS_YOFFSET){ y = _t2p(s.readSI16()); } + if(flags & t.HAS_FONT){ size = _t2p(s.readUI16()); } + } + string = { + font: font, + size: size, + fill: fill, + x: x, + y: y + }; + strings.push(string); + }else{ + var numGlyphs = header & 0x7f; + var entries = string.entries = []; + var i = numGlyphs; + while(i--){ + var entry = {}; + entry.index = s.readUB(numGlyphBits); + entry.advance = _t2p(s.readSB(numAdvBits)); + entries.push(entry); + } + s.align(); + } + } + }while(header); + }else{ s.seek(length - 2); } + return this; + }, + + _handleDoAction: function(offset, length){ + var t = this; + var s = t.stream; + var action = t._frameActions[t.currentFrame]; + if(action){ s.seek(length); } + else{ action = t._frameActions[t.currentFrame] = t._readAction(); } + if(t._state != _m.LOADING){ action(); } + return t; + }, + + _handleDefineFontInfo: function(offset, length){ + var t = this; + var s = t.stream; + var fontId = s.readUI16(); + var font = t._dictionary[fontId]; + if(!font.info){ + var f = font.info = {}; + f.name = s.readString(s.readUI8()); + f.isSmall = s.readBool(3); + f.isShiftJis = s.readBool(); + f.isAnsi = s.readBool(); + f.isItalic = s.readBool(); + f.isBold = s.readBool(); + var useWideCodes = s.readBool(); + var codes = f.codes = []; + var i = font.glyphs.length; + while(i--){ + var code = useWideCodes ? s.readUI16() : s.readUI8(); + codes.push(code); + } + }else{ s.seek(length - 2); } + return t; + }, + + prevFrame: function(){ + this.goto(this.currentFrame - 1); + return this; + }, + + goto: function(frame){ + var t = this; + if(frame < 0){ frame = t.frameCount + frame; } + if(frame && frame <= t.frameCount && frame != t.currentFrame){ + if(frame < t.currentFrame){ t.reset(); } + while(t.currentFrame != frame){ t.nextFrame(); } + } + return t; + }, + + reset: function(){ + var t = this; + t._renderer.reset(); + var s = t.stream; + s.reset(); + s.seek(t._tagsOffset, true); + t.currentFrame = 0; + return t; + }, + + rewind: function(){ + this.stop(); + this.reset(); + return this; + } + }; + + function _cloneEdge(edge){ + return { + i: edge.i, + f: edge.f, + x1: edge.x1, + y1: edge.y1, + cx: edge.cx, + cy: edge.cy, + x2: edge.x2, + y2: edge.y2 + } + } + + function _buildShape(id, edges, fill, stroke){ + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + var commands = []; + for(var i = 0; edges[i]; i++){ + var edge = edges[i]; + x1 = edge.x1; + y1 = edge.y1; + if(x1 != x2 || y1 != y2){ commands.push('M', x1, y1); } + x2 = edge.x2; + y2 = edge.y2; + if(edge.cx == null || edge.cy == null){ + if(x2 == x1){ commands.push('V', y2); } + else if(y2 == y1){ commands.push('H', x2); } + else{ commands.push('L', x2, y2); } + }else{ commands.push('Q', edge.cx, edge.cy, x2, y2); } + } + return { + type: "shape", + id: id, + commands: commands, + fill: fill, + stroke: stroke + }; + } + + function _calcPointKey(x, y){ + return (x + 50000) * 100000 + y; + } + + function _getUrl(url, target){ + var u = _g.urlTargets; + switch(target){ + case u.BLANK: + window.open(url); + break; + case u.PARENT: + parent.location.href = url; + break; + case u.TOP: + top.location.href = url; + break; + default: + location.href = url; + } + } +})(); diff --git a/src/Stream.js b/src/Stream.js new file mode 100644 index 0000000..e719225 --- /dev/null +++ b/src/Stream.js @@ -0,0 +1,345 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +Gordon.require("src/inflate"); + +(function(){ + var _g = Gordon; + var _t2p = _g.twips2px; + + _g.Stream = function(url){ + var xhr = Gordon.xhr("GET", url, false); + xhr.overrideMimeType("text/plain; charset=x-user-defined"); + xhr.send(null); + if(xhr.status != 200){ throw new Error("Unable to load " + url + " status: " + xhr.status); } + var t = this; + t._buffer = xhr.responseText; + t._length = t._buffer.length; + t._offset = 0; + t._bitBuffer = null; + t._bitOffset = 8; + }; + _g.Stream.prototype = { + decompress: function() { + var t = this; + t._offset += 2; + var header = t._buffer.substr(0, t._offset); + var data = zip_inflate(t._buffer.substr(t._offset)); + t._buffer = header + data; + t._length = t._buffer.length; + return t; + }, + + readByteAt: function(position){ + return this._buffer.charCodeAt(position) & 0xff; + }, + + readNumber: function(numBytes){ + var value = 0; + var p = this._offset; + var i = p + numBytes; + while(i > p){ value = value * 256 + this.readByteAt(--i); } + this._offset += numBytes; + return value; + }, + + readSNumber: function(numBytes){ + var value = this.readNumber(numBytes); + var mask = 0x01 << (numBytes * 8 - 1); + if(value & mask){ value = (~value + 1) * -1; } + return value; + }, + + readSI8: function(){ + return this.readSNumber(1); + }, + + readSI16: function(){ + return this.readSNumber(2); + }, + + readSI32: function(){ + return this.readSNumber(4); + }, + + readUI8: function(){ + return this.readNumber(1); + }, + + readUI16: function(){ + return this.readNumber(2); + }, + + readUI24: function(){ + return this.readNumber(3); + }, + + readUI32: function(){ + return this.readNumber(4); + }, + + readFixed: function(){ + return this._readFixedPoint(32, 16); + }, + + readFixed8: function(){ + return this._readFixedPoint(16, 8); + }, + + _readFixedPoint: function(numBits, precision){ + var value = this.readSB(numBits); + value = value * Math.pow(2, -precision) + return value; + }, + + readFloat16: function(){ + return this._readFloatingPoint(5, 10); + }, + + readFloat: function(){ + return this._readFloatingPoint(8, 23); + }, + + readDouble: function(){ + return this._readFloatingPoint(11, 52); + }, + + _readFloatingPoint: function(numEbits, numSbits){ + var numBits = 1 + numEbits + numSbits; + var numBytes = numBits / 8; + var t = this; + if(numBytes > 4){ + var value = 0; + var numWords = Math.ceil(numBytes / 4); + var i = numWords; + while(i--){ + var p = t._offset; + var offset = numBytes >= 4 ? 4 : numBytes % 4; + var j = p + offset; + while(j > p){ value = value * 256 + String.fromCharCode(t.readByteAt(--j)); } + t._offset += numBytes; + numBytes -= numBytes; + } + var mask = 0x01 << (numBits - 1); + var sign = value & mask; + var expo = 0; + var i = numEbits; + while(i--){ + mask >>= 1; + expo |= buffer & mask ? 1 : 0; + expo <<= 1; + } + var mantissa = 0; + var i = numSbits; + while(i--){ + mask >>= 1; + if(buffer & mask){ mantissa += Math.pow(2, i - 1); } + } + }else{ + var sign = t.readUB(1); + var expo = t.readUB(numEbits); + var mantissa = t.readUB(numSbits); + } + var value = 0; + var maxExpo = Math.pow(2, numEbits); + var bias = Math.floor((maxExpo - 1) / 2); + var scale = Math.pow(2, numSbits); + var fraction = mantissa / scale; + if(bias){ + if(bias < maxExpo){ value = Math.pow(2, expo - bias) * (1 + faction); } + else if(fraction){ value = NaN; } + else{ value = Infinity; } + }else if(fraction){ value = Math.pow(2, 1 - bias) * fraction; } + if(value != NaN && sign){ value *= -1; } + return value; + }, + + readEncodedU32: function(){ + var value = 0; + var i = 5; + while(i--){ + var number = this.readNumber(); + value = value * 128 + number & 0x7F; + if(!(number & 0x80)){ break; } + } + return value; + }, + + readSB: function(numBits){ + var value = this.readUB(numBits); + var mask = 0x01 << (numBits - 1); + if(value & mask){ value -= Math.pow(2, numBits); } + return value; + }, + + readUB: function(numBits){ + var value = 0; + var t = this; + var i = numBits; + while(i--){ + if(t._bitOffset == 8){ + t._bitBuffer = t.readUI8(); + t._bitOffset = 0; + } + var mask = 0x80 >> t._bitOffset; + value = value * 2 + (t._bitBuffer & mask ? 1 : 0); + t._bitOffset++; + } + return value; + }, + + readFB: function(numBits){ + return this._readFixedPoint(numBits, 16); + }, + + readString: function(numChars){ + var chars = []; + var i = numChars || this._length - this._offset; + while(i--){ + var code = this.readNumber(1); + if(numChars || code){ chars.push(String.fromCharCode(code)); } + else{ break; } + } + return chars.join(''); + }, + + readBool: function(numBits){ + return !!this.readUB(numBits || 1); + }, + + readLanguageCode: function(){ + return this.readUI8(); + }, + + readRgb: function(){ + return { + r: this.readUI8(), + g: this.readUI8(), + b: this.readUI8() + } + }, + + readRgba: function(){ + var rgba = this.readRgb(); + rgba.a = this.readUI8() / 256; + return rgba; + }, + + readArgb: function(){ + var alpha = this.readUI8() / 256; + var rgba = this.readRGB(); + rgba.a = alpha; + return rgba; + }, + + align: function(){ + this._bitBuffer = null; + this._bitOffset = 8; + return this; + }, + + readRect: function(){ + var rect = { + top: 0, + right: 0, + bottom: 0, + left: 0 + }; + var t = this; + var numBits = t.readUB(5); + rect.left = _t2p(t.readSB(numBits)); + rect.right = _t2p(t.readSB(numBits)); + rect.top = _t2p(t.readSB(numBits)); + rect.bottom = _t2p(t.readSB(numBits)); + t.align(); + return rect; + }, + + readMatrix: function(){ + var matrix = { + scaleX: 1.0, + scaleY: 1.0, + skewX: 0.0, + skewY: 0.0, + moveX: 0, + moveY: 0 + }; + var t = this; + var hasScale = t.readBool(); + if(hasScale){ + var numBits = t.readUB(5); + matrix.scaleX = t.readFB(numBits); + matrix.scaleY = t.readFB(numBits); + } + var hasRotation = t.readBool(); + if(hasRotation){ + var numBits = t.readUB(5); + matrix.skewX = t.readFB(numBits); + matrix.skewY = t.readFB(numBits); + } + var numBits = t.readUB(5); + matrix.moveX = _t2p(t.readSB(numBits)); + matrix.moveY = _t2p(t.readSB(numBits)); + t.align(); + return matrix; + }, + + readCxform: function(){ + return this._readCxform(); + }, + + readCxformWithAlpha: function(){ + return this._readCxform(true); + }, + + _readCxform: function(withAlpha){ + var cxform = { + multR: 1.0, + multG: 1.0, + multB: 1.0, + multA: 1.0, + addR: 0.0, + addG: 0.0, + addB: 0.0, + addA: 0.0 + }; + var t = this; + var hasAddTerms = t.readBool(); + var hasMultTerms = t.readBool(); + var numBits = t.readUB(4); + if(hasMultTerms){ + cxform.multR = t.readSB(numBits) / 256; + cxform.multG = t.readSB(numBits) / 256; + cxform.multB = t.readSB(numBits) / 256; + if(withAlpha){ cxform.multA = t.readSB(numBits) / 256; } + } + if(hasAddTerms){ + cxform.addR = t.readSB(numBits); + cxform.addG = t.readSB(numBits); + cxform.addB = t.readSB(numBits); + if(withAlpha){ cxform.addA = t.readSB(numBits); } + } + t.align(); + return cxform; + }, + + tell: function(){ + return this._offset; + }, + + seek: function(offset, absolute){ + this._offset = (absolute ? 0 : this._offset) + offset; + return this; + }, + + reset: function(){ + this._offset = 0; + this.align(); + return this; + } + }; +})(); diff --git a/src/SvgRenderer.js b/src/SvgRenderer.js new file mode 100644 index 0000000..78041fa --- /dev/null +++ b/src/SvgRenderer.js @@ -0,0 +1,448 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var NAMESPACE_SVG = "http://www.w3.org/2000/svg"; + var NAMESPACE_XLINK = "http://www.w3.org/1999/xlink"; + + var _g = Gordon; + var _buttonMask = 0; + + document.addEventListener("mousedown", function(event){ + _buttonMask |= 0x01 << event.button; + }, true); + document.addEventListener("mouseup", function(event){ + _buttonMask ^= 0x01 << event.button; + }, true); + + _g.SvgRenderer = function(width, height, viewWidth, viewHeight, quality, scale, bgcolor){ + var t = this; + t.width = width; + t.height = height; + t.viewWidth = viewWidth; + t.viewHeight = viewHeight; + t.quality = null; + t.scale = _g.scaleValues.DEFAULT; + t.bgcolor = null; + t._dictionary = {}; + t._displayList = {}; + t._eventTarget = null; + var n = t._node = t._createElement("svg"); + var attributes = { + width: width, + height: height + }; + if(viewWidth && viewHeight && (width != viewWidth || height != viewHeight)){ + var viewBox = [0, 0, viewWidth, viewHeight]; + attributes.viewBox = viewBox.join(' '); + if(scale == _g.scaleValues.EXACT_FIT){ attributes.preserveAspectRatio = "none"; } + } + t._setAttributes(n, attributes); + t._defs = n.appendChild(t._createElement("defs")); + t._screen = n.appendChild(t._createElement('g')); + t._currentFillId = t._currentFilterId = 0; + t._setQuality(quality || _g.qualityValues.HIGH); + if(bgcolor){ t.setBgcolor(bgcolor); } + }; + _g.SvgRenderer.prototype = { + _createElement: function(name){ + return document.createElementNS(NAMESPACE_SVG, name); + }, + + _setAttributes: function(node, attributes, namespace){ + for(var name in attributes){ + var value = attributes[name]; + name = name.replace(/_/, '-'); + if(namespace){ node.setAttributeNS(namespace, name, value); } + else{ node.setAttribute(name, value); } + } + return node; + }, + + _setQuality: function(quality){ + var q = _g.qualityValues; + var t = this; + switch(quality){ + case q.LOW: + var attributes = { + color_rendering: "optimizeSpeed", + shape_rendering: "crispEdges", + text_rendering: "optimizeSpeed", + image_rendering: "optimizeSpeed" + } + break; + case q.AUTO_LOW: + case q.AUTO_HIGH: + var attributes = { + color_rendering: "auto", + shape_rendering: "auto", + text_rendering: "auto", + image_rendering: "auto" + } + break; + case q.MEDIUM: + var attributes = { + color_rendering: "optimizeSpeed", + shape_rendering: "optimizeSpeed", + text_rendering: "optimizeLegibility", + image_rendering: "optimizeSpeed" + } + break; + case q.HIGH: + var attributes = { + color_rendering: "optimizeQuality", + shape_rendering: "geometricPrecision", + text_rendering: "geometricPrecision", + image_rendering: "auto" + } + break; + case q.BEST: + var attributes = { + color_rendering: "optimizeQuality", + shape_rendering: "geometricPrecision", + text_rendering: "geometricPrecision", + image_rendering: "optimizeQuality" + } + break; + } + t._setAttributes(this._screen, attributes); + t.quality = quality; + return t; + }, + + getNode: function(){ + return this._node; + }, + + setBgcolor: function(rgb){ + var t = this; + if(!t.bgcolor){ + var color = new Gordon.Color(rgb); + t._node.style.background = color.toString(); + t.bgcolor = color; + } + return t; + }, + + defineCharacter: function(character){ + var object = character.object; + var id = object.id; + var t = this; + var d = t._dictionary; + if(!d[id]){ + var type = object.type; + switch(type){ + case "shape": + var segments = object.segments; + if(segments){ + var node = t._createElement('g'); + for(var i = 0; segments[i]; i++){ + var segment = segments[i]; + node.appendChild(t._buildShape(segments[i])); + } + }else{ var node = t._buildShape(object); } + t._setAttributes(t._defs.appendChild(node), {id: "shape" + id}); + break; + case "button": + var states = object.states; + for(var state in states){ + var list = states[state]; + for(var depth in list){ t.defineCharacter(list[depth]); } + } + break; + case "text": + t._setAttributes(t._defs.appendChild(t._buildText(object)), {id: "text" + id}); + break; + } + d[id] = object; + } + var cxform = character.cxform; + if(cxform){ + with(t._defs.appendChild(t._createElement("filter"))){ + t._setAttributes(appendChild(t._createElement("feColorMatrix")), { + type: "matrix", + values: cxform.toString(), + id: "cxform" + (++t._currentFilterId) + }); + } + } + return t; + }, + + _buildShape: function(shape){ + var t = this; + var node = t._createElement("path"); + var fill = shape.fill; + var stroke = shape.stroke; + var attributes = {d: shape.commands.join(' ')}; + if(fill){ + if(fill.type){ + t._defineFill(fill); + attributes.fill = "url(#" + fill.type + t._currentFillId + ')'; + }else{ attributes.fill = fill.toString(); } + attributes.fill_rule = "evenodd"; + }else{ attributes.fill = "none"; } + if(stroke){ + attributes.stroke = stroke.color.toString(); + attributes.stroke_width = Math.max(stroke.width, 1); + attributes.stroke_linecap = "round"; + attributes.stroke_linejoin = "round"; + } + t._setAttributes(node, attributes); + return node; + }, + + _defineFill: function(fill){ + var t = this; + var type = fill.type; + var attributes = {id: type + (++t._currentFillId)}; + switch(type){ + case "linear": + case "radial": + var node = t._createElement(type + "Gradient"); + attributes.gradientUnits = "userSpaceOnUse"; + attributes.gradientTransform = fill.matrix.toString(); + if(type == "linear"){ + attributes.x1 = -819.2; + attributes.x2 = 819.2; + }else{ + attributes.cx = attributes.cy = 0; + attributes.r = 819.2; + } + var s = _g.spreadModes; + switch(fill.spread){ + case s.REFLECT: + attributes.spreadMethod = "reflect"; + break; + case s.REPEAT: + attributes.spreadMethod = "repeat"; + break; + } + var i = _g.interpolationModes; + if(fill.interpolation == i.LINEAR_RGB){ attributes.color_interpolation = "linearRGB"; } + var stops = fill.stops; + for(var i = 0; stops[i]; i++){ + var stop = stops[i]; + t._setAttributes(node.appendChild(t._createElement("stop")), { + offset: stop.offset, + stop_color: stop.color.toString() + }); + } + break; + case "pattern": + var node = t._createElement("pattern"); + var img = fill.image; + var imgNode = node.appendChild(t._createElement("image")); + var attributes = { + width: img.width, + height: img.height + }; + t._setAttributes(imgNode, {href: img.data}, NAMESPACE_XLINK); + t._setAttributes(imgNode, attributes); + attributes.patternUnits = "userSpaceOnUse"; + attributes.patternTransform = fill.matrix.toString(); + + } + t._setAttributes(t._defs.appendChild(node), attributes); + return t; + }, + + _buildText: function(text){ + var t = this; + var node = t._createElement('g'); + t._setAttributes(node, {transform: text.matrix.toString()}); + var strings = text.strings; + for(var i = 0; strings[i]; i++){ + var string = strings[i]; + var font = string.font; + t._defineFont(font); + var entries = string.entries; + var advances = []; + var info = font.info; + var codes = info.codes + var characters = []; + var x = string.x; + for(var j = 0; entries[j]; j++){ + var entry = entries[j]; + advances.push(x); + characters.push(String.fromCharCode(codes[entry.index])); + x += entry.advance; + } + var textNode = node.appendChild(t._createElement("text")); + t._setAttributes(textNode, { + font_family: info.name, + font_size: string.size, + fill: string.fill.toString(), + x: advances.join(' '), + y: string.y + }); + textNode.appendChild(document.createTextNode(characters.join(''))); + } + return node; + }, + + _defineFont: function(font){ + var id = font.id; + var t = this; + var d = t._dictionary; + if(!d[id]){ + var node = t._defs.appendChild(t._createElement("font-face")); + var info = font.info; + var glyphs = font.glyphs; + var codes = info.codes; + t._setAttributes(node, { + font_family: info.name, + units_per_em: 1024 + }); + for(var i = 0; glyphs[i]; i++){t._setAttributes(t._createElement("glyph"), { + unicode: String.fromCharCode(codes[i]), + d: glyphs[i].commands.join(' ') + }); } + d[id] = font; + } + return t; + }, + + placeCharacter: function(character){ + var t = this; + var d = t._displayList; + var s = t._screen; + var node = t._buildCharacter(character); + var depth = character.depth; + if(depth == 1){ s.insertBefore(node, s.firstChild); } + else{ + var nextDepth = 0; + for(var entry in d){ + if(entry > depth){ + nextDepth = entry; + break; + } + } + if(nextDepth){ s.insertBefore(node, d[nextDepth]); } + else{ s.appendChild(node); } + } + d[depth] = node; + return t; + }, + + _buildCharacter: function(character){ + var object = character.object; + var type = object.type; + var t = this; + var id = object.id; + switch(type){ + case "button": + var node = t._buildButton(object); + break; + default: + var node = t._createElement("use"); + t._setAttributes(node, {href: "#" + type + id}, NAMESPACE_XLINK); + } + var attributes = {transform: character.matrix.toString()}; + if(character.cxform){ attributes.filter = "url(#cxform" + (++t._currentFilterId) + ')'; } + t._setAttributes(node, attributes); + return node; + }, + + _buildButton: function(button){ + var t = this; + var node = t._createElement('g'); + var activeArea = t._createElement('g'); + var displayMap = {}; + var b = _g.buttonStates; + var currentState = b.UP; + var states = button.states; + for(var s in states){ + var display = displayMap[s] = s == b.HIT ? activeArea : node.appendChild(t._createElement('g')); + if(s != currentState){ t._setAttributes(display, {opacity: 0}); } + var list = states[s]; + for(var depth in list){ display.appendChild(t._buildCharacter(list[depth])); } + } + var isMouseOver = false; + var mouseupHandle = function(event){ + if(!(_buttonMask & 0x01)){ + if(isMouseOver){ + setState(b.OVER); + button.onClick(); + }else{ setState(b.UP); } + document.removeEventListener("mouseup", mouseupHandle, false); + t.eventTarget = null; + } + return false; + }; + with(node.appendChild(activeArea)){ + onmouseover = function(event){ + isMouseOver = true; + if(!t.eventTarget){ + if(_buttonMask & 0x01){ this.onmousedown(event); } + else{ setState(b.OVER); } + } + return false; + }; + onmouseout = function(event){ + isMouseOver = false; + if(!t.eventTarget){ setState(t.eventTarget == this ? b.OVER : b.UP); } + return false; + }; + onmousedown = function(event){ + if(_buttonMask & 0x01){ + setState(b.DOWN); + document.addEventListener("mouseup", mouseupHandle, false); + t.eventTarget = this; + } + return false; + }; + onmouseup = function(event){ + setState(b.OVER); + return false; + }; + } + var setState = function(state){ + t._setAttributes(displayMap[currentState], {opacity: 0}); + t._setAttributes(displayMap[state], {opacity: 1}); + currentState = state; + }; + return node; + }, + + removeCharacter: function(character){ + var d = this._displayList; + var depth = character.depth; + this._screen.removeChild(d[depth]); + delete d[depth]; + return this; + }, + + reset: function(){ + var t = this; + var d = t._displayList; + for(var depth in d){ t.removeCharacter({depth: depth}); } + t._currentFillId = t._currentFilterId = 0; + return t; + }, + + toggleQuality: function(){ + var q = _g.qualityValues; + var t = this; + switch(t.quality){ + case q.LOW: + t._setQuality(q.HIGH); + break; + case q.AUTO_LOW: + t._setQuality(q.AUTO_HIGH); + break; + case q.AUTO_HIGH: + t._setQuality(q.AUTO_LOW); + break; + case q.HIGH: + t._setQuality(q.LOW); + break; + } + return t; + } + }; +})(); diff --git a/src/_base.js b/src/_base.js new file mode 100644 index 0000000..753d5ab --- /dev/null +++ b/src/_base.js @@ -0,0 +1,171 @@ +/* + * Gordon: An open source Flash™ runtime written in pure JavaScript + * + * Copyright (c) 2010 Tobias Schneider + * Gordon is freely distributable under the terms of the MIT license. + */ + +(function(){ + var _g = Gordon; + + _g.qualityValues = { + LOW: "low", + AUTO_LOW: "autolow", + AUTO_HIGH: "autohigh", + MEDIUM: "medium", + HIGH: "high", + BEST: "best" + }; + _g.scaleValues = { + DEFAULT: "default", + NO_ORDER: "noorder", + EXACT_FIT: "exactfit" + }; + _g.signatures = { + SWF: "FWS", + COMPRESSED_SWF: "CWS" + } + _g.movieStates = { + LOADING: 0, + LOADED: 1, + PLAYING: 2, + STOPPED: 3 + }, + _g.tagCodes = { + END: 0, + SHOW_FRAME: 1, + DEFINE_SHAPE: 2, + PLACE_OBJECT: 4, + REMOVE_OBJECT: 5, + DEFINE_BITS: 6, + DEFINE_BUTTON: 7, + JPEG_TABLES: 8, + SET_BACKGROUND_COLOR: 9, + DEFINE_FONT: 10, + DEFINE_TEXT: 11, + DO_ACTION: 12, + DEFINE_FONT_INFO: 13, + DEFINE_SOUND: 14, + START_SOUND: 15, + DEFINE_BUTTON_SOUND: 17, + SOUND_STREAM_HEAD: 18, + SOUND_STREAM_BLOCK: 19, + DEFINE_BITS_LOSSLESS: 20, + DEFINE_BITS_JPEG2: 21, + DEFINE_SHAPE2: 22, + DEFINE_BUTTON_CXFORM: 23, + PROTECT: 24, + PLACE_OBJECT2: 26, + REMOVE_OBJECT2: 28, + DEFINE_SHAPE3: 32, + DEFINE_TEXT2: 33, + DEFINE_BUTTON2: 34, + DEFINE_BITS_JPEG3: 35, + DEFINE_BITS_LOSSLESS2: 36, + DEFINE_EDIT_TEXT: 37, + DEFINE_SPRITE: 39, + FRAME_LABEL: 43, + SOUND_STREAM_HEAD2: 45, + DEFINE_MORPH_SHAPE: 46, + DEFINE_FONT2: 48, + EXPORT_ASSETS: 56, + IMPORT_ASSETS: 57, + ENABLE_DEBUGGER: 58, + DO_INIT_ACTION: 59, + DEFINE_VIDEO_STREAM: 60, + VIDEO_FRAME: 61, + DEFINE_FONT_INFO2: 62, + ENABLE_DEBUGGER2: 64, + SCRIPT_LIMITS: 65, + SET_TAB_INDEX: 66, + FILE_ATTRIBUTES: 69, + PLACE_OBJECT3: 70, + IMPORT_ASSETS2: 71, + DEFINE_FONT_ALIGN_ZONES: 73, + CSM_TEXT_SETTINGS: 74, + DEFINE_FONT3: 75, + SYMBOL_CLASS: 76, + METADATA: 77, + DEFINE_SCALING_GRID: 78, + DO_ABC: 82, + DEFINE_SHAPE4: 83, + DEFINE_MORPH_SHAPE2: 84, + DEFINE_SCENE_AND_FRAME_LABEL_DATA: 86, + DEFINE_BINARY_DATA: 87, + DEFINE_FONT_NAME: 88, + START_SOUND2: 89, + DEFINE_BITS_JPEG4: 90, + DEFINE_FONT4: 91 + }; + _g.tagNames = {}; + _g.tagHandlerMap = {}; + for(var name in _g.tagCodes){ + var code = _g.tagCodes[name]; + _g.tagNames[code] = name; + _g.tagHandlerMap[code] = "_handle" + name.toLowerCase().replace(/(^|_)([a-z])/g, function(match, p1, p2){ + return p2.toUpperCase(); + }); + } + _g.fillStyleTypes = { + SOLID: 0x00, + LINEAR_GRADIENT: 0x10, + RADIAL_GRADIENT: 0x12, + FOCAL_RADIAL_GRADIENT: 0x13, + REPEATING_BITMAP: 0x40, + CLIPPED_BITMAP: 0x41, + NON_SMOOTHED_REPEATING_BITMAP: 0x42, + NON_SMOOTHED_CLIPPED_BITMAP: 0x43 + }; + _g.spreadModes = { + PAD: 0, + REFLECT: 1, + REPEAT: 2 + }; + _g.interpolationModes = { + RGB: 0, + LINEAR_RGB: 1 + }; + _g.styleChangeStates = { + MOVE_TO: 0x01, + LEFT_FILL_STYLE: 0x02, + RIGHT_FILL_STYLE: 0x04, + LINE_STYLE: 0x08, + NEW_STYLES: 0x10 + }; + _g.buttonStates = { + UP: 0x01, + OVER: 0x02, + DOWN: 0x04, + HIT: 0x08 + }; + _g.textStyleFlags = { + HAS_FONT: 0x08, + HAS_COLOR: 0x04, + HAS_XOFFSET: 0x01, + HAS_YOFFSET: 0x02 + }; + _g.actionCodes = { + PLAY: 0x06, + STOP: 0x07, + NEXT_FRAME: 0x04, + PREVIOUS_FRAME: 0x05, + GOTO_FRAME: 0x81, + GOTO_LABEL: 0x08c, + WAIT_FOR_FRAME: 0x8a, + GET_URL: 0x83, + STOP_SOUNDS: 0x09, + TOGGLE_QUALITY: 0x08, + SET_TARGET: 0x08b + }; + _g.urlTargets = { + SELF: "_self", + BLANK: "_blank", + PARENT: "_parent", + TOP: "_top" + }; + _g.PX_IN_TWIPS = 20; + + _g.twips2px = function(twips){ + return twips / _g.PX_IN_TWIPS; + }; +})(); diff --git a/src/base64.js b/src/base64.js new file mode 100644 index 0000000..ae1b396 --- /dev/null +++ b/src/base64.js @@ -0,0 +1,107 @@ +/* Copyright (C) 1999 Masanao Izumo + * Version: 1.0 + * LastModified: Dec 25 1999 + * This library is free. You can redistribute it and/or modify it. + */ + +/* + * Interfaces: + * b64 = base64encode(data); + * data = base64decode(b64); + */ + + +var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +var base64DecodeChars = new Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1); + +base64encode = function(str) { + var out, i, len; + var c1, c2, c3; + + len = str.length; + i = 0; + out = ""; + while(i < len) { + c1 = str.charCodeAt(i++) & 0xff; + if(i == len) + { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = str.charCodeAt(i++); + if(i == len) + { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + c3 = str.charCodeAt(i++); + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); + out += base64EncodeChars.charAt(c3 & 0x3F); + } + return out; +} + +base64decode = function(str) { + var c1, c2, c3, c4; + var i, len, out; + + len = str.length; + i = 0; + out = ""; + while(i < len) { + /* c1 */ + do { + c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < len && c1 == -1); + if(c1 == -1) + break; + + /* c2 */ + do { + c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < len && c2 == -1); + if(c2 == -1) + break; + + out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); + + /* c3 */ + do { + c3 = str.charCodeAt(i++) & 0xff; + if(c3 == 61) + return out; + c3 = base64DecodeChars[c3]; + } while(i < len && c3 == -1); + if(c3 == -1) + break; + + out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); + + /* c4 */ + do { + c4 = str.charCodeAt(i++) & 0xff; + if(c4 == 61) + return out; + c4 = base64DecodeChars[c4]; + } while(i < len && c4 == -1); + if(c4 == -1) + break; + out += String.fromCharCode(((c3 & 0x03) << 6) | c4); + } + return out; +} diff --git a/src/inflate.js b/src/inflate.js new file mode 100644 index 0000000..c20f886 --- /dev/null +++ b/src/inflate.js @@ -0,0 +1,737 @@ +/* Copyright (C) 1999 Masanao Izumo + * Version: 1.0.0.1 + * LastModified: Dec 25 1999 + */ + +/* Interface: + * data = zip_inflate(src); + */ + +/* constant parameters */ +var zip_WSIZE = 32768; // Sliding Window size +var zip_STORED_BLOCK = 0; +var zip_STATIC_TREES = 1; +var zip_DYN_TREES = 2; + +/* for inflate */ +var zip_lbits = 9; // bits in base literal/length lookup table +var zip_dbits = 6; // bits in base distance lookup table +var zip_INBUFSIZ = 32768; // Input buffer size +var zip_INBUF_EXTRA = 64; // Extra buffer + +/* variables (inflate) */ +var zip_slide; +var zip_wp; // current position in slide +var zip_fixed_tl = null; // inflate static +var zip_fixed_td; // inflate static +var zip_fixed_bl, fixed_bd; // inflate static +var zip_bit_buf; // bit buffer +var zip_bit_len; // bits in bit buffer +var zip_method; +var zip_eof; +var zip_copy_leng; +var zip_copy_dist; +var zip_tl, zip_td; // literal/length and distance decoder tables +var zip_bl, zip_bd; // number of bits decoded by tl and td + +var zip_inflate_data; +var zip_inflate_pos; + + +/* constant tables (inflate) */ +var zip_MASK_BITS = new Array( + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff); +// Tables for deflate from PKZIP's appnote.txt. +var zip_cplens = new Array( // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0); +/* note: see note #13 above about the 258 in this list. */ +var zip_cplext = new Array( // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99); // 99==invalid +var zip_cpdist = new Array( // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577); +var zip_cpdext = new Array( // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13); +var zip_border = new Array( // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15); +/* objects (inflate) */ + +function zip_HuftList() { + this.next = null; + this.list = null; +} + +function zip_HuftNode() { + this.e = 0; // number of extra bits or operation + this.b = 0; // number of bits in this code or subcode + + // union + this.n = 0; // literal, length base, or distance base + this.t = null; // (zip_HuftNode) pointer to next level of table +} + +function zip_HuftBuild(b, // code lengths in bits (all assumed <= BMAX) + n, // number of codes (assumed <= N_MAX) + s, // number of simple-valued codes (0..s-1) + d, // list of base values for non-simple codes + e, // list of extra bits for non-simple codes + mm // maximum lookup bits + ) { + this.BMAX = 16; // maximum bit length of any code + this.N_MAX = 288; // maximum number of codes in any set + this.status = 0; // 0: success, 1: incomplete table, 2: bad input + this.root = null; // (zip_HuftList) starting table + this.m = 0; // maximum lookup bits, returns actual + +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return zero on success, one if + the given code set is incomplete (the tables are still built in this + case), two if the input is invalid (all zero length codes or an + oversubscribed set of lengths), and three if not enough memory. + The code with value 256 is special, and the tables are constructed + so that no bits beyond that code are fetched when that code is + decoded. */ + { + var a; // counter for codes of length k + var c = new Array(this.BMAX+1); // bit length count table + var el; // length of EOB code (value 256) + var f; // i repeats in table every f entries + var g; // maximum code length + var h; // table level + var i; // counter, current code + var j; // counter + var k; // number of bits in current code + var lx = new Array(this.BMAX+1); // stack of bits per table + var p; // pointer into c[], b[], or v[] + var pidx; // index of p + var q; // (zip_HuftNode) points to current table + var r = new zip_HuftNode(); // table entry for structure assignment + var u = new Array(this.BMAX); // zip_HuftNode[BMAX][] table stack + var v = new Array(this.N_MAX); // values in order of bit length + var w; + var x = new Array(this.BMAX+1);// bit offsets, then code stack + var xp; // pointer into x or c + var y; // number of dummy codes added + var z; // number of entries in current table + var o; + var tail; // (zip_HuftList) + + tail = this.root = null; + for(i = 0; i < c.length; i++) + c[i] = 0; + for(i = 0; i < lx.length; i++) + lx[i] = 0; + for(i = 0; i < u.length; i++) + u[i] = null; + for(i = 0; i < v.length; i++) + v[i] = 0; + for(i = 0; i < x.length; i++) + x[i] = 0; + + // Generate counts for each bit length + el = n > 256 ? b[256] : this.BMAX; // set length of EOB code, if any + p = b; pidx = 0; + i = n; + do { + c[p[pidx]]++; // assume all entries <= BMAX + pidx++; + } while(--i > 0); + if(c[0] == n) { // null input--all zero length codes + this.root = null; + this.m = 0; + this.status = 0; + return; + } + + // Find minimum and maximum length, bound *m by those + for(j = 1; j <= this.BMAX; j++) + if(c[j] != 0) + break; + k = j; // minimum code length + if(mm < j) + mm = j; + for(i = this.BMAX; i != 0; i--) + if(c[i] != 0) + break; + g = i; // maximum code length + if(mm > i) + mm = i; + + // Adjust last length count to fill out codes, if needed + for(y = 1 << j; j < i; j++, y <<= 1) + if((y -= c[j]) < 0) { + this.status = 2; // bad input: more codes than bits + this.m = mm; + return; + } + if((y -= c[i]) < 0) { + this.status = 2; + this.m = mm; + return; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = c; + pidx = 1; + xp = 2; + while(--i > 0) // note that i == g from above + x[xp++] = (j += p[pidx++]); + + // Make a table of values in order of bit lengths + p = b; pidx = 0; + i = 0; + do { + if((j = p[pidx++]) != 0) + v[x[j]++] = i; + } while(++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = v; pidx = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = lx[0] = 0; // no bits decoded yet + q = null; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for(; k <= g; k++) { + a = c[k]; + while(a-- > 0) { + // here i is the Huffman code of length k bits for value p[pidx] + // make tables up to required level + while(k > w + lx[1 + h]) { + w += lx[1 + h]; // add bits already decoded + h++; + + // compute minimum size table less than or equal to *m bits + z = (z = g - w) > mm ? mm : z; // upper limit + if((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + while(++j < z) { // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + if(w + j > el && w < el) + j = el - w; // make EOB code end at table + z = 1 << j; // table entries for j-bit table + lx[1 + h] = j; // set table size in stack + + // allocate and link in new table + q = new Array(z); + for(o = 0; o < z; o++) { + q[o] = new zip_HuftNode(); + } + + if(tail == null) + tail = this.root = new zip_HuftList(); + else + tail = tail.next = new zip_HuftList(); + tail.next = null; + tail.list = q; + u[h] = q; // table starts after link + + /* connect to last table, if there is one */ + if(h > 0) { + x[h] = i; // save pattern for backing up + r.b = lx[h]; // bits to dump before this table + r.e = 16 + j; // bits in this table + r.t = q; // pointer to this table + j = (i & ((1 << w) - 1)) >> (w - lx[h]); + u[h-1][j].e = r.e; + u[h-1][j].b = r.b; + u[h-1][j].n = r.n; + u[h-1][j].t = r.t; + } + } + + // set up table entry in r + r.b = k - w; + if(pidx >= n) + r.e = 99; // out of values--invalid code + else if(p[pidx] < s) { + r.e = (p[pidx] < 256 ? 16 : 15); // 256 is end-of-block code + r.n = p[pidx++]; // simple code is just the value + } else { + r.e = e[p[pidx] - s]; // non-simple--look up in lists + r.n = d[p[pidx++] - s]; + } + + // fill code-like entries with r // + f = 1 << (k - w); + for(j = i >> w; j < z; j += f) { + q[j].e = r.e; + q[j].b = r.b; + q[j].n = r.n; + q[j].t = r.t; + } + + // backwards increment the k-bit code i + for(j = 1 << (k - 1); (i & j) != 0; j >>= 1) + i ^= j; + i ^= j; + + // backup over finished tables + while((i & ((1 << w) - 1)) != x[h]) { + w -= lx[h]; // don't need to update q + h--; + } + } + } + + /* return actual size of base table */ + this.m = lx[1]; + + /* Return true (1) if we were given an incomplete table */ + this.status = ((y != 0 && g != 1) ? 1 : 0); + } /* end of constructor */ +} + + +/* routines (inflate) */ + +function zip_GET_BYTE() { + if(zip_inflate_data.length == zip_inflate_pos) + return -1; + return zip_inflate_data.charCodeAt(zip_inflate_pos++) & 0xff; +} + +function zip_NEEDBITS(n) { + while(zip_bit_len < n) { + zip_bit_buf |= zip_GET_BYTE() << zip_bit_len; + zip_bit_len += 8; + } +} + +function zip_GETBITS(n) { + return zip_bit_buf & zip_MASK_BITS[n]; +} + +function zip_DUMPBITS(n) { + zip_bit_buf >>= n; + zip_bit_len -= n; +} + +function zip_inflate_codes(buff, off, size) { + /* inflate (decompress) the codes in a deflated (compressed) block. + Return an error code or zero if it all goes ok. */ + var e; // table entry flag/number of extra bits + var t; // (zip_HuftNode) pointer to table entry + var n; + + if(size == 0) + return 0; + + // inflate the coded data + n = 0; + for(;;) { // do until end of block + zip_NEEDBITS(zip_bl); + t = zip_tl.list[zip_GETBITS(zip_bl)]; + e = t.e; + while(e > 16) { + if(e == 99) + return -1; + zip_DUMPBITS(t.b); + e -= 16; + zip_NEEDBITS(e); + t = t.t[zip_GETBITS(e)]; + e = t.e; + } + zip_DUMPBITS(t.b); + + if(e == 16) { // then it's a literal + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] = t.n; + if(n == size) + return size; + continue; + } + + // exit if end of block + if(e == 15) + break; + + // it's an EOB or a length + + // get length of block to copy + zip_NEEDBITS(e); + zip_copy_leng = t.n + zip_GETBITS(e); + zip_DUMPBITS(e); + + // decode distance of block to copy + zip_NEEDBITS(zip_bd); + t = zip_td.list[zip_GETBITS(zip_bd)]; + e = t.e; + + while(e > 16) { + if(e == 99) + return -1; + zip_DUMPBITS(t.b); + e -= 16; + zip_NEEDBITS(e); + t = t.t[zip_GETBITS(e)]; + e = t.e; + } + zip_DUMPBITS(t.b); + zip_NEEDBITS(e); + zip_copy_dist = zip_wp - t.n - zip_GETBITS(e); + zip_DUMPBITS(e); + + // do the copy + while(zip_copy_leng > 0 && n < size) { + zip_copy_leng--; + zip_copy_dist &= zip_WSIZE - 1; + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] + = zip_slide[zip_copy_dist++]; + } + + if(n == size) + return size; + } + + zip_method = -1; // done + return n; +} + +function zip_inflate_stored(buff, off, size) { + /* "decompress" an inflated type 0 (stored) block. */ + var n; + + // go to byte boundary + n = zip_bit_len & 7; + zip_DUMPBITS(n); + + // get the length and its complement + zip_NEEDBITS(16); + n = zip_GETBITS(16); + zip_DUMPBITS(16); + zip_NEEDBITS(16); + if(n != ((~zip_bit_buf) & 0xffff)) + return -1; // error in compressed data + zip_DUMPBITS(16); + + // read and output the compressed data + zip_copy_leng = n; + + n = 0; + while(zip_copy_leng > 0 && n < size) { + zip_copy_leng--; + zip_wp &= zip_WSIZE - 1; + zip_NEEDBITS(8); + buff[off + n++] = zip_slide[zip_wp++] = + zip_GETBITS(8); + zip_DUMPBITS(8); + } + + if(zip_copy_leng == 0) + zip_method = -1; // done + return n; +} + +function zip_inflate_fixed(buff, off, size) { + /* decompress an inflated type 1 (fixed Huffman codes) block. We should + either replace this with a custom decoder, or at least precompute the + Huffman tables. */ + + // if first time, set up tables for fixed blocks + if(zip_fixed_tl == null) { + var i; // temporary variable + var l = new Array(288); // length list for huft_build + var h; // zip_HuftBuild + + // literal table + for(i = 0; i < 144; i++) + l[i] = 8; + for(; i < 256; i++) + l[i] = 9; + for(; i < 280; i++) + l[i] = 7; + for(; i < 288; i++) // make a complete, but wrong code set + l[i] = 8; + zip_fixed_bl = 7; + + h = new zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext, + zip_fixed_bl); + if(h.status != 0) { + alert("HufBuild error: "+h.status); + return -1; + } + zip_fixed_tl = h.root; + zip_fixed_bl = h.m; + + // distance table + for(i = 0; i < 30; i++) // make an incomplete code set + l[i] = 5; + zip_fixed_bd = 5; + + h = new zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd); + if(h.status > 1) { + zip_fixed_tl = null; + alert("HufBuild error: "+h.status); + return -1; + } + zip_fixed_td = h.root; + zip_fixed_bd = h.m; + } + + zip_tl = zip_fixed_tl; + zip_td = zip_fixed_td; + zip_bl = zip_fixed_bl; + zip_bd = zip_fixed_bd; + return zip_inflate_codes(buff, off, size); +} + +function zip_inflate_dynamic(buff, off, size) { + // decompress an inflated type 2 (dynamic Huffman codes) block. + var i; // temporary variables + var j; + var l; // last length + var n; // number of lengths to get + var t; // (zip_HuftNode) literal/length code table + var nb; // number of bit length codes + var nl; // number of literal/length codes + var nd; // number of distance codes + var ll = new Array(286+30); // literal/length and distance code lengths + var h; // (zip_HuftBuild) + + for(i = 0; i < ll.length; i++) + ll[i] = 0; + + // read in table lengths + zip_NEEDBITS(5); + nl = 257 + zip_GETBITS(5); // number of literal/length codes + zip_DUMPBITS(5); + zip_NEEDBITS(5); + nd = 1 + zip_GETBITS(5); // number of distance codes + zip_DUMPBITS(5); + zip_NEEDBITS(4); + nb = 4 + zip_GETBITS(4); // number of bit length codes + zip_DUMPBITS(4); + if(nl > 286 || nd > 30) + return -1; // bad lengths + + // read in bit-length-code lengths + for(j = 0; j < nb; j++) + { + zip_NEEDBITS(3); + ll[zip_border[j]] = zip_GETBITS(3); + zip_DUMPBITS(3); + } + for(; j < 19; j++) + ll[zip_border[j]] = 0; + + // build decoding table for trees--single level, 7 bit lookup + zip_bl = 7; + h = new zip_HuftBuild(ll, 19, 19, null, null, zip_bl); + if(h.status != 0) + return -1; // incomplete code set + + zip_tl = h.root; + zip_bl = h.m; + + // read in literal and distance code lengths + n = nl + nd; + i = l = 0; + while(i < n) { + zip_NEEDBITS(zip_bl); + t = zip_tl.list[zip_GETBITS(zip_bl)]; + j = t.b; + zip_DUMPBITS(j); + j = t.n; + if(j < 16) // length of code in bits (0..15) + ll[i++] = l = j; // save last length in l + else if(j == 16) { // repeat last length 3 to 6 times + zip_NEEDBITS(2); + j = 3 + zip_GETBITS(2); + zip_DUMPBITS(2); + if(i + j > n) + return -1; + while(j-- > 0) + ll[i++] = l; + } else if(j == 17) { // 3 to 10 zero length codes + zip_NEEDBITS(3); + j = 3 + zip_GETBITS(3); + zip_DUMPBITS(3); + if(i + j > n) + return -1; + while(j-- > 0) + ll[i++] = 0; + l = 0; + } else { // j == 18: 11 to 138 zero length codes + zip_NEEDBITS(7); + j = 11 + zip_GETBITS(7); + zip_DUMPBITS(7); + if(i + j > n) + return -1; + while(j-- > 0) + ll[i++] = 0; + l = 0; + } + } + + // build the decoding tables for literal/length and distance codes + zip_bl = zip_lbits; + h = new zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl); + if(zip_bl == 0) // no literals or lengths + h.status = 1; + if(h.status != 0) { + if(h.status == 1) + ;// **incomplete literal tree** + return -1; // incomplete code set + } + zip_tl = h.root; + zip_bl = h.m; + + for(i = 0; i < nd; i++) + ll[i] = ll[i + nl]; + zip_bd = zip_dbits; + h = new zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd); + zip_td = h.root; + zip_bd = h.m; + + if(zip_bd == 0 && nl > 257) { // lengths but no distances + // **incomplete distance tree** + return -1; + } + + if(h.status == 1) { + ;// **incomplete distance tree** + } + if(h.status != 0) + return -1; + + // decompress until an end-of-block code + return zip_inflate_codes(buff, off, size); +} + +function zip_inflate_start() { + var i; + + if(zip_slide == null) + zip_slide = new Array(2 * zip_WSIZE); + zip_wp = 0; + zip_bit_buf = 0; + zip_bit_len = 0; + zip_method = -1; + zip_eof = false; + zip_copy_leng = zip_copy_dist = 0; + zip_tl = null; +} + +function zip_inflate_internal(buff, off, size) { + // decompress an inflated entry + var n, i; + + n = 0; + while(n < size) { + if(zip_eof && zip_method == -1) + return n; + + if(zip_copy_leng > 0) { + if(zip_method != zip_STORED_BLOCK) { + // STATIC_TREES or DYN_TREES + while(zip_copy_leng > 0 && n < size) { + zip_copy_leng--; + zip_copy_dist &= zip_WSIZE - 1; + zip_wp &= zip_WSIZE - 1; + buff[off + n++] = zip_slide[zip_wp++] = + zip_slide[zip_copy_dist++]; + } + } else { + while(zip_copy_leng > 0 && n < size) { + zip_copy_leng--; + zip_wp &= zip_WSIZE - 1; + zip_NEEDBITS(8); + buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8); + zip_DUMPBITS(8); + } + if(zip_copy_leng == 0) + zip_method = -1; // done + } + if(n == size) + return n; + } + + if(zip_method == -1) { + if(zip_eof) + break; + + // read in last block bit + zip_NEEDBITS(1); + if(zip_GETBITS(1) != 0) + zip_eof = true; + zip_DUMPBITS(1); + + // read in block type + zip_NEEDBITS(2); + zip_method = zip_GETBITS(2); + zip_DUMPBITS(2); + zip_tl = null; + zip_copy_leng = 0; + } + + switch(zip_method) { + case 0: // zip_STORED_BLOCK + i = zip_inflate_stored(buff, off + n, size - n); + break; + + case 1: // zip_STATIC_TREES + if(zip_tl != null) + i = zip_inflate_codes(buff, off + n, size - n); + else + i = zip_inflate_fixed(buff, off + n, size - n); + break; + + case 2: // zip_DYN_TREES + if(zip_tl != null) + i = zip_inflate_codes(buff, off + n, size - n); + else + i = zip_inflate_dynamic(buff, off + n, size - n); + break; + + default: // error + i = -1; + break; + } + + if(i == -1) { + if(zip_eof) + return 0; + return -1; + } + n += i; + } + return n; +} + +zip_inflate = function(str) { + var out, buff; + var i, j; + + zip_inflate_start(); + zip_inflate_data = str; + zip_inflate_pos = 0; + + buff = new Array(1024); + out = ""; + while((i = zip_inflate_internal(buff, 0, buff.length)) > 0) { + for(j = 0; j < i; j++) + out += String.fromCharCode(buff[j]); + } + zip_inflate_data = null; // G.C. + return out; +}