vox-core
Version:
Runtime de aplicaciones multiplataforma
301 lines (272 loc) • 11.2 kB
JavaScript
/* LZJB compression: http://en.wikipedia.org/wiki/LZJB */
if (typeof define !== 'function') { var define = require('amdefine')(module); }
define(['./Stream','./Util'], function(Stream,Util) {
/**
$Id: Iuppiter.js 3026 2010-06-23 10:03:13Z Bear $
Copyright (c) 2010 Nuwa Information Co., Ltd, and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Nuwa Information nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
$Author: Bear $
$Date: 2010-06-23 18:03:13 +0800 (星期三, 23 六月 2010) $
$Revision: 3026 $
*/
var Lzjb = Object.create(null);
Lzjb.MAGIC = 'lzjb';
// Constants was used for compress/decompress function.
var NBBY = 8,
MATCH_BITS = 6,
MATCH_MIN = 3,
MATCH_MAX = ((1 << MATCH_BITS) + (MATCH_MIN - 1)),
OFFSET_MASK = ((1 << (16 - MATCH_BITS)) - 1),
LEMPEL_SIZE_BASE = 1024;
var EOF = Stream.EOF;
// set C_COMPAT to true if you need to decompress with the (untweaked) C lzjb
// implementation, which breaks if offset==0; the javascript
// implementation uses 0 to indicate an offset of OFFSET_MASK+1.
var C_COMPAT = true;
/**
* Compress string or byte array using fast and efficient algorithm.
*
* Because of weak of javascript's natural, many compression algorithm
* become useless in javascript implementation. The main problem is
* performance, even the simple Huffman, LZ77/78 algorithm will take many
* many time to operate. We use LZJB algorithm to do that, it suprisingly
* fulfills our requirement to compress string fastly and efficiently.
*
* Our implementation is based on
* http://src.opensolaris.org/source/raw/onnv/onnv-gate/usr/src/uts/common/fs/zfs/lzjb.c
* and
* http://src.opensolaris.org/source/raw/onnv/onnv-gate/usr/src/uts/common/os/compress.c
* It is licensed under CDDL.
*
* @param {Array|Uint8Array|Buffer|stream} input The stream or byte array
* that you want to compress.
* @param {stream} output Optional output stream.
* @return {Array|Uint8Array|Buffer} Compressed byte array, or 'output'
*/
Lzjb.compressFile = Util.compressFileHelper(Lzjb.MAGIC, function(inStream, outStream, fileSize, props) {
var sstart, dstart = [], slen,
src = 0, dst = 0,
cpy, copymap,
mlen, offset,
hash, hp,
lempel,
i, j;
var retval;
// in an improvement over the original C implementation, we expand
// the hash table to track a number of potential matches, not just the
// most recent. This doesn't require any changes to the decoder.
// Sample impact on compression size (on wikipedia data):
// EXPAND Time Size Option
// 1 0m20.321s 50185613 -1
// 2 0m22.437s 46503301 -2
// 3 0m23.773s 45744564 -3
// 4 0m25.666s 45199866 -4
// 5 0m35.810s 44821413 -5
// 6 0m40.947s 44666638 -6
// 8 0m49.639s 44413865 -7
// 12 0m49.927s 44124825 -8
// 16 1m01.180s 43972515 -9
// 32 1m30.530s 43554099
// 64 2m14.504s 43005530
// 128 3m43.570s 42361718
// 256 6m38.681s 41684853
var LEMPEL_SIZE = LEMPEL_SIZE_BASE;
var EXPAND = 1; // default to original C impl
if (typeof(props)==='number') {
LEMPEL_SIZE *= 2;
props = Math.max(1, Math.min(9, props)) - 1;
EXPAND = 1<<Math.floor(props/2);
if (props&1) EXPAND = Math.round(EXPAND * 1.5);
if (props >=2 && props <= 4) EXPAND++;
}
// use Uint16Array if available (zero-filled)
lempel = Util.makeU16Buffer(LEMPEL_SIZE * EXPAND);
var window = Util.makeU8Buffer(OFFSET_MASK+1);
var windowpos = 0;
var winput = function(_byte) {
window[windowpos++] = _byte;
if (windowpos >= window.length) {
windowpos = 0;
}
return _byte;
};
var outwindow = Util.makeU8Buffer(17);
var outpos = 0;
var dumpout = function() {
var i;
for (i=0; i<outpos; i++) {
outStream.writeByte(outwindow[i]);
}
outpos = 0;
};
var unbuffer = [];
var get = function() {
if (unbuffer.length)
return unbuffer.pop();
return inStream.readByte();
};
var unget = function(_byte) {
unbuffer.push(_byte);
};
var copymask = 1 << (NBBY - 1);
var matchpossibility = [];
while (true) {
var c1 = get();
if (c1 === EOF) break;
if ((copymask <<= 1) == (1 << NBBY)) {
dumpout();
copymask = 1;
outwindow[0] = 0;
outpos = 1;
}
var c2 = get();
if (c2 === EOF) {
outwindow[outpos++] = winput(c1);
break;
}
var c3 = get();
if (c3 === EOF) {
outwindow[outpos++] = winput(c1);
unget(c2);
continue;
}
hash = (c1 << 16) + (c2 << 8) + c3;
hash ^= (hash >> 9);
hash += (hash >> 5);
hash ^= c1;
hp = (hash & (LEMPEL_SIZE - 1)) * EXPAND;
matchpossibility.length = 0;
for (j=0; j<EXPAND; j++) {
offset = (windowpos - lempel[hp+j]) & OFFSET_MASK;
cpy = window.length + windowpos - offset;
var w1 = window[cpy & OFFSET_MASK];
var w2 = window[(cpy+1) & OFFSET_MASK];
var w3 = window[(cpy+2) & OFFSET_MASK];
// if offset is small, we might not have copied the tentative
// bytes into the window yet. (Note that offset=0 really means
// offset=(OFFSET_MASK+1).)
if (C_COMPAT && offset===0) {
w1 = c1 ^ 1; // ensure match will fail
} else if (offset==1) { w2 = c1; w3 = c2; }
else if (offset==2) { w3 = c1; }
if (c1 === w1 && c2 === w2 && c3 === w3) {
matchpossibility.push(offset);
}
}
// store this location in the hash, move the others over to make room
// oldest match drops off
for (j=EXPAND-1; j>0; j--)
lempel[hp+j] = lempel[hp+j-1];
lempel[hp] = windowpos;
// did we find any matches?
if (matchpossibility.length === 0) {
outwindow[outpos++] = winput(c1);
unget(c3);
unget(c2);
} else {
// find the longest of the possible matches
outwindow[0] |= copymask;
winput(c1); winput(c2); winput(c3);
var c4 = get(), last = matchpossibility[0];
var base = window.length + windowpos;
for (mlen = MATCH_MIN; mlen < MATCH_MAX; mlen++, base++) {
if (c4 === EOF) break;
for (j=0; j < matchpossibility.length; ) {
var w4 = window[(base - matchpossibility[j]) & OFFSET_MASK];
if (c4 !== w4) {
last = matchpossibility[j];
matchpossibility.splice(j, 1);
} else {
j++;
}
}
if (matchpossibility.length===0) break; // no more matches
winput(c4);
c4 = get();
}
if (matchpossibility.length !== 0) {
// maximum length match, rock on!
last = matchpossibility[0];
}
unget(c4);
outwindow[outpos++] = ((mlen - MATCH_MIN) << (NBBY - MATCH_BITS)) |
(last >> NBBY);
outwindow[outpos++] = last & 0xFF;
}
}
dumpout();
});
/**
* Decompress string or byte array using fast and efficient algorithm.
*
* Our implementation is based on
* http://src.opensolaris.org/source/raw/onnv/onnv-gate/usr/src/uts/common/fs/zfs/lzjb.c
* and
* http://src.opensolaris.org/source/raw/onnv/onnv-gate/usr/src/uts/common/os/compress.c
* It is licensed under CDDL.
*
* @param {Array|Uint8Array|Buffer|stream} input The stream or byte array
* that you want to decompress.
* @param {stream} output Optional output stream.
* @return {Array|Uint8Array|Buffer} Decompressed byte array, or 'output'
*/
Lzjb.decompressFile = Util.decompressFileHelper(Lzjb.MAGIC, function(inStream, outStream, outSize) {
var sstart, dstart = [], slen,
src = 0, dst = 0,
cpy, copymap,
mlen, offset,
i, c;
var retval;
var window = Util.makeU8Buffer(OFFSET_MASK+1);
var windowpos = 0;
var copymask = 1 << (NBBY - 1);
while (outSize !== 0) {
c = inStream.readByte();
if (c === EOF) break;
if ((copymask <<= 1) == (1 << NBBY)) {
copymask = 1;
copymap = c;
c = inStream.readByte();
}
if (copymap & copymask) {
mlen = (c >> (NBBY - MATCH_BITS)) + MATCH_MIN;
offset = ((c << NBBY) | inStream.readByte()) & OFFSET_MASK;
cpy = windowpos - offset;
if (cpy < 0) cpy += window.length;
if (outSize >= 0) outSize -= mlen;
while (--mlen >= 0) {
c = window[windowpos++] = window[cpy++];
outStream.writeByte(c);
if (windowpos >= window.length) { windowpos=0; }
if (cpy >= window.length) { cpy = 0; }
}
} else {
outStream.writeByte(c);
window[windowpos++] = c;
if (windowpos >= window.length) { windowpos=0; }
if (outSize >= 0) outSize--;
}
}
});
return Lzjb;
});