From 28571af9902b8c7a1c2a426deaaf1b17f189ed1a Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 25 Jun 2017 16:39:24 +0200 Subject: [PATCH 01/39] Replace .charAt and .indexOf with array/dict lookup --- libs/base64-string.js | 92 ++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/libs/base64-string.js b/libs/base64-string.js index 4bb19aa..52b27c8 100644 --- a/libs/base64-string.js +++ b/libs/base64-string.js @@ -7,18 +7,18 @@ // For more information, the home page: // http://pieroxy.net/blog/pages/lz-string/index.html // -// Base64 compression / decompression for already compressed content (gif, png, jpg, mp3, ...) +// Base64 compression / decompression for already compressed content (gif, png, jpg, mp3, ...) // version 1.4.1 var Base64String = { - + compressToUTF16 : function (input) { var output = [], i,c, current, status = 0; - + input = this.compress(input); - + for (i=0 ; i> 8; - + while (i < input.length*2 && (i < input.length*2-1 || odd==0)) { - + if (i%2==0) { chr1 = input.charCodeAt(i/2) >> 8; chr2 = input.charCodeAt(i/2) & 255; - if (i/2+1 < input.length) + if (i/2+1 < input.length) chr3 = input.charCodeAt(i/2+1) >> 8; - else + else chr3 = NaN; } else { chr1 = input.charCodeAt((i-1)/2) & 255; if ((i+1)/2 < input.length) { chr2 = input.charCodeAt((i+1)/2) >> 8; chr3 = input.charCodeAt((i+1)/2) & 255; - } else + } else chr2=chr3=NaN; } i+=3; - + enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; - + if (isNaN(chr2) || (i==input.length*2+1 && odd)) { enc3 = enc4 = 64; } else if (isNaN(chr3) || (i==input.length*2 && odd)) { enc4 = 64; } - - output.push(this._keyStr.charAt(enc1)); - output.push(this._keyStr.charAt(enc2)); - output.push(this._keyStr.charAt(enc3)); - output.push(this._keyStr.charAt(enc4)); + + output.push(this._keyStrDec[enc1]); + output.push(this._keyStrDec[enc2]); + output.push(this._keyStrDec[enc3]); + output.push(this._keyStrDec[enc4]); } - + return output.join(''); }, - + compress : function (input) { var output = [], - ol = 1, + ol = 1, output_, chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0, flush=false; - + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - + while (i < input.length) { - - enc1 = this._keyStr.indexOf(input.charAt(i++)); - enc2 = this._keyStr.indexOf(input.charAt(i++)); - enc3 = this._keyStr.indexOf(input.charAt(i++)); - enc4 = this._keyStr.indexOf(input.charAt(i++)); - + + enc1 = this._keyStrEnc[input.charAt(i++)]; + enc2 = this._keyStrEnc[input.charAt(i++)]; + enc3 = this._keyStrEnc[input.charAt(i++)]; + enc4 = this._keyStrEnc[input.charAt(i++)]; + chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; - + if (ol%2==0) { output_ = chr1 << 8; flush = true; - + if (enc3 != 64) { output.push(String.fromCharCode(output_ | chr2)); flush = false; @@ -258,7 +268,7 @@ var Base64String = { } else { output.push(String.fromCharCode(output_ | chr1)); flush = false; - + if (enc3 != 64) { output_ = chr2 << 8; flush = true; @@ -270,7 +280,7 @@ var Base64String = { } ol+=3; } - + if (flush) { output.push(String.fromCharCode(output_)); output = output.join(''); @@ -278,8 +288,8 @@ var Base64String = { } else { output = output.join(''); } - + return output; - + } } From 7899c1161ec1b19127ca8d5b2bb24594f58cd1f7 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 25 Jun 2017 16:57:39 +0200 Subject: [PATCH 02/39] Hoist lookup dictionary lookup Instead of: - function call - conditional check for alphabet existence - dictionary lookup - dictionary lookup ... the new version only does one dictionary lookup. Should lower overhead. --- libs/lz-string.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 1194e25..67d68d3 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -15,14 +15,14 @@ var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567 var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; var baseReverseDic = {}; -function getBaseValue(alphabet, character) { +function getReverseDic(alphabet){ if (!baseReverseDic[alphabet]) { baseReverseDic[alphabet] = {}; for (var i=0 ; i Date: Mon, 26 Jun 2017 17:19:19 +0200 Subject: [PATCH 03/39] Use IIFE for Base64String --- libs/base64-string.js | 127 ++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/libs/base64-string.js b/libs/base64-string.js index 52b27c8..b6e760b 100644 --- a/libs/base64-string.js +++ b/libs/base64-string.js @@ -9,6 +9,19 @@ // // Base64 compression / decompression for already compressed content (gif, png, jpg, mp3, ...) // version 1.4.1 + +var Base64String = (function(){ +var f = String.fromCharCode; +var keyStrEnc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); +var keyStrDec = (function (){ + var dict = {} + var i = keyStrEnc.length; + while(i--){ + dict[keyStrEnc[i]] = i; + } + return dict; + })(); + var Base64String = { compressToUTF16 : function (input) { @@ -23,68 +36,68 @@ var Base64String = { c = input.charCodeAt(i); switch (status++) { case 0: - output.push(String.fromCharCode((c >> 1)+32)); + output.push(f((c >> 1)+32)); current = (c & 1) << 14; break; case 1: - output.push(String.fromCharCode((current + (c >> 2))+32)); + output.push(f((current + (c >> 2))+32)); current = (c & 3) << 13; break; case 2: - output.push(String.fromCharCode((current + (c >> 3))+32)); + output.push(f((current + (c >> 3))+32)); current = (c & 7) << 12; break; case 3: - output.push(String.fromCharCode((current + (c >> 4))+32)); + output.push(f((current + (c >> 4))+32)); current = (c & 15) << 11; break; case 4: - output.push(String.fromCharCode((current + (c >> 5))+32)); + output.push(f((current + (c >> 5))+32)); current = (c & 31) << 10; break; case 5: - output.push(String.fromCharCode((current + (c >> 6))+32)); + output.push(f((current + (c >> 6))+32)); current = (c & 63) << 9; break; case 6: - output.push(String.fromCharCode((current + (c >> 7))+32)); + output.push(f((current + (c >> 7))+32)); current = (c & 127) << 8; break; case 7: - output.push(String.fromCharCode((current + (c >> 8))+32)); + output.push(f((current + (c >> 8))+32)); current = (c & 255) << 7; break; case 8: - output.push(String.fromCharCode((current + (c >> 9))+32)); + output.push(f((current + (c >> 9))+32)); current = (c & 511) << 6; break; case 9: - output.push(String.fromCharCode((current + (c >> 10))+32)); + output.push(f((current + (c >> 10))+32)); current = (c & 1023) << 5; break; case 10: - output.push(String.fromCharCode((current + (c >> 11))+32)); + output.push(f((current + (c >> 11))+32)); current = (c & 2047) << 4; break; case 11: - output.push(String.fromCharCode((current + (c >> 12))+32)); + output.push(f((current + (c >> 12))+32)); current = (c & 4095) << 3; break; case 12: - output.push(String.fromCharCode((current + (c >> 13))+32)); + output.push(f((current + (c >> 13))+32)); current = (c & 8191) << 2; break; case 13: - output.push(String.fromCharCode((current + (c >> 14))+32)); + output.push(f((current + (c >> 14))+32)); current = (c & 16383) << 1; break; case 14: - output.push(String.fromCharCode((current + (c >> 15))+32, (c & 32767)+32)); + output.push(f((current + (c >> 15))+32, (c & 32767)+32)); status = 0; break; } } - output.push(String.fromCharCode(current + 32)); + output.push(f(current + 32)); return output.join(''); }, @@ -103,63 +116,63 @@ var Base64String = { current = c << 1; break; case 1: - output.push(String.fromCharCode(current | (c >> 14))); + output.push(f(current | (c >> 14))); current = (c&16383) << 2; break; case 2: - output.push(String.fromCharCode(current | (c >> 13))); + output.push(f(current | (c >> 13))); current = (c&8191) << 3; break; case 3: - output.push(String.fromCharCode(current | (c >> 12))); + output.push(f(current | (c >> 12))); current = (c&4095) << 4; break; case 4: - output.push(String.fromCharCode(current | (c >> 11))); + output.push(f(current | (c >> 11))); current = (c&2047) << 5; break; case 5: - output.push(String.fromCharCode(current | (c >> 10))); + output.push(f(current | (c >> 10))); current = (c&1023) << 6; break; case 6: - output.push(String.fromCharCode(current | (c >> 9))); + output.push(f(current | (c >> 9))); current = (c&511) << 7; break; case 7: - output.push(String.fromCharCode(current | (c >> 8))); + output.push(f(current | (c >> 8))); current = (c&255) << 8; break; case 8: - output.push(String.fromCharCode(current | (c >> 7))); + output.push(f(current | (c >> 7))); current = (c&127) << 9; break; case 9: - output.push(String.fromCharCode(current | (c >> 6))); + output.push(f(current | (c >> 6))); current = (c&63) << 10; break; case 10: - output.push(String.fromCharCode(current | (c >> 5))); + output.push(f(current | (c >> 5))); current = (c&31) << 11; break; case 11: - output.push(String.fromCharCode(current | (c >> 4))); + output.push(f(current | (c >> 4))); current = (c&15) << 12; break; case 12: - output.push(String.fromCharCode(current | (c >> 3))); + output.push(f(current | (c >> 3))); current = (c&7) << 13; break; case 13: - output.push(String.fromCharCode(current | (c >> 2))); + output.push(f(current | (c >> 2))); current = (c&3) << 14; break; case 14: - output.push(String.fromCharCode(current | (c >> 1))); + output.push(f(current | (c >> 1))); current = (c&1) << 15; break; case 15: - output.push(String.fromCharCode(current | c)); + output.push(f(current | c)); status=0; break; } @@ -173,20 +186,6 @@ var Base64String = { }, - - // private property - _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - _keyStrDec : this._keyStr.split(''), - _keyStrEnc : (function (){ - // IIFE to create a dictionary - var dict = {} - var i = this._keyStrDec.length; - while(i--){ - dict[this._keyStrDec[i]] = i; - } - return dict; - })(), - decompress : function (input) { var output = []; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; @@ -223,10 +222,10 @@ var Base64String = { enc4 = 64; } - output.push(this._keyStrDec[enc1]); - output.push(this._keyStrDec[enc2]); - output.push(this._keyStrDec[enc3]); - output.push(this._keyStrDec[enc4]); + output.push(keyStrDec[enc1]); + output.push(keyStrDec[enc2]); + output.push(keyStrDec[enc3]); + output.push(keyStrDec[enc4]); } return output.join(''); @@ -244,10 +243,10 @@ var Base64String = { while (i < input.length) { - enc1 = this._keyStrEnc[input.charAt(i++)]; - enc2 = this._keyStrEnc[input.charAt(i++)]; - enc3 = this._keyStrEnc[input.charAt(i++)]; - enc4 = this._keyStrEnc[input.charAt(i++)]; + enc1 = keyStrEnc[input.charAt(i++)]; + enc2 = keyStrEnc[input.charAt(i++)]; + enc3 = keyStrEnc[input.charAt(i++)]; + enc4 = keyStrEnc[input.charAt(i++)]; chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); @@ -258,7 +257,7 @@ var Base64String = { flush = true; if (enc3 != 64) { - output.push(String.fromCharCode(output_ | chr2)); + output.push(f(output_ | chr2)); flush = false; } if (enc4 != 64) { @@ -266,7 +265,7 @@ var Base64String = { flush = true; } } else { - output.push(String.fromCharCode(output_ | chr1)); + output.push(f(output_ | chr1)); flush = false; if (enc3 != 64) { @@ -274,7 +273,7 @@ var Base64String = { flush = true; } if (enc4 != 64) { - output.push(String.fromCharCode(output_ | chr3)); + output.push(f(output_ | chr3)); flush = false; } } @@ -282,14 +281,24 @@ var Base64String = { } if (flush) { - output.push(String.fromCharCode(output_)); + output.push(f(output_)); output = output.join(''); - output = String.fromCharCode(output.charCodeAt(0)|256) + output.substring(1); + output = f(output.charCodeAt(0)|256) + output.substring(1); } else { output = output.join(''); } return output; - } + }, + + compressToArray : function (input) { + + }, + + decompressToArray : function (input) { + + }, } +return Base64String; +})() From d0b79427a9833d5962cb0ca10d780d22a8e51af4 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Mon, 26 Jun 2017 19:24:51 +0200 Subject: [PATCH 04/39] Avoid intermediate string in UTF16 compression/decompression + various tweaks To avoid creating a string, we introduce two new functions, `compressToArray` and `decompressFromArray`. The former can easily be wrapped by `compress` to minimize code duplication. Sadly `decompressFromArray` isn't wrapped so easily. To alleviate this somewhat, we introduce the private `_chrToOutput` function. Note that it's <500 chars, before minification, meaning that v8 will likely inline it anyway. --- libs/base64-string.js | 122 ++++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/libs/base64-string.js b/libs/base64-string.js index b6e760b..c925bdf 100644 --- a/libs/base64-string.js +++ b/libs/base64-string.js @@ -12,12 +12,12 @@ var Base64String = (function(){ var f = String.fromCharCode; -var keyStrEnc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); -var keyStrDec = (function (){ - var dict = {} - var i = keyStrEnc.length; +var keyEnc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); +var keyDec = (function (){ + var dict = {}, + i = keyEnc.length; while(i--){ - dict[keyStrEnc[i]] = i; + dict[keyEnc[i]] = i; } return dict; })(); @@ -30,10 +30,10 @@ var Base64String = { current, status = 0; - input = this.compress(input); + input = this.compressToArray(input); for (i=0 ; i> 1)+32)); @@ -181,22 +181,22 @@ var Base64String = { i++; } - return this.decompress(output.join('')); - //return output; + return this.decompressFromArray(output); }, decompress : function (input) { - var output = []; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 1; - var odd = input.charCodeAt(0) >> 8; + var output = [], + chrc, chr1, chr2, chr3, + i = 1, + odd = input.charCodeAt(0) >> 8; while (i < input.length*2 && (i < input.length*2-1 || odd==0)) { if (i%2==0) { - chr1 = input.charCodeAt(i/2) >> 8; - chr2 = input.charCodeAt(i/2) & 255; + chrc = input.charCodeAt(i/2); + chr1 = chrc >> 8; + chr2 = chrc & 255; if (i/2+1 < input.length) chr3 = input.charCodeAt(i/2+1) >> 8; else @@ -204,34 +204,76 @@ var Base64String = { } else { chr1 = input.charCodeAt((i-1)/2) & 255; if ((i+1)/2 < input.length) { - chr2 = input.charCodeAt((i+1)/2) >> 8; - chr3 = input.charCodeAt((i+1)/2) & 255; + chrc = input.charCodeAt((i+1)/2); + chr2 = chrc >> 8; + chr3 = chrc & 255; } else chr2=chr3=NaN; } i+=3; - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; + this._chrToOutput(i, input.length, chr1, chr2, chr3, output); + } - if (isNaN(chr2) || (i==input.length*2+1 && odd)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3) || (i==input.length*2 && odd)) { - enc4 = 64; + return output.join(''); + }, + + decompressFromArray : function (input) { + var output = [], + chrc, chr1, chr2, chr3, + i = 1, + odd = input[0].charCodeAt() >> 8; + + while (i < input.length*2 && (i < input.length*2-1 || odd==0)) { + + if (i%2==0) { + chrc = input[i/2].charCodeAt(); + chr1 = chrc >> 8; + chr2 = chrc & 255; + if (i/2+1 < input.length) + chr3 = input[i/2+1].charCodeAt() >> 8; + else + chr3 = NaN; + } else { + chr1 = input[(i-1)/2].charCodeAt() & 255; + if ((i+1)/2 < input.length) { + chrc = input[(i+1)/2].charCodeAt() + chr2 = chrc >> 8; + chr3 = chrc & 255; + } else + chr2=chr3=NaN; } + i+=3; - output.push(keyStrDec[enc1]); - output.push(keyStrDec[enc2]); - output.push(keyStrDec[enc3]); - output.push(keyStrDec[enc4]); + this._chrToOutput(i, input.length, chr1, chr2, chr3, output); } return output.join(''); }, + // private function to reduce code duplication between + // decompress and decompressFromArray + _chrToOutput : function (i, length, chr1, chr2, chr3, output) { + var enc1 = chr1 >> 2, + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4), + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6), + enc4 = chr3 & 63; + if (isNaN(chr2) || (i==length*2+1 && odd)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3) || (i==length*2 && odd)) { + enc4 = 64; + } + output.push(keyDec[enc1]); + output.push(keyDec[enc2]); + output.push(keyDec[enc3]); + output.push(keyDec[enc4]); + }, + compress : function (input) { + return this.compressToArray(input).join(''); + }, + + compressToArray : function (input) { var output = [], ol = 1, output_, @@ -243,10 +285,10 @@ var Base64String = { while (i < input.length) { - enc1 = keyStrEnc[input.charAt(i++)]; - enc2 = keyStrEnc[input.charAt(i++)]; - enc3 = keyStrEnc[input.charAt(i++)]; - enc4 = keyStrEnc[input.charAt(i++)]; + enc1 = keyEnc[input.charAt(i++)]; + enc2 = keyEnc[input.charAt(i++)]; + enc3 = keyEnc[input.charAt(i++)]; + enc4 = keyEnc[input.charAt(i++)]; chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); @@ -282,23 +324,11 @@ var Base64String = { if (flush) { output.push(f(output_)); - output = output.join(''); - output = f(output.charCodeAt(0)|256) + output.substring(1); - } else { - output = output.join(''); + output[0] = f(output[0].charCodeAt()|256); } - return output; - }, - compressToArray : function (input) { - - }, - - decompressToArray : function (input) { - - }, } return Base64String; })() From 66e713496e5adf46b33df45f08288a5f3fa5a8cc Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Mon, 26 Jun 2017 19:47:56 +0200 Subject: [PATCH 05/39] replace charAt() with array lookup Slightly faster on most browsers. --- libs/lz-string.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 67d68d3..8c3ef34 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -11,15 +11,15 @@ var LZString = (function() { // private property var f = String.fromCharCode; -var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; -var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; +var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); +var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''); var baseReverseDic = {}; -function getReverseDic(alphabet){ +function getReverseDict(alphabet){ if (!baseReverseDic[alphabet]) { baseReverseDic[alphabet] = {}; for (var i=0 ; i Date: Mon, 26 Jun 2017 20:24:09 +0200 Subject: [PATCH 06/39] Avoid creating intermediate strings where possible - compressToBase64 avoids string append - compressToUint8Array avoids intermediate string - decompressFromUint8Array avoids intermediate string As a side-effect, the UTF16 functions get slightly more complicated, but the only alternative was a *lot* of code duplication. --- libs/lz-string.js | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 8c3ef34..91cfa0e 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -28,14 +28,14 @@ function getReverseDict(alphabet){ var LZString = { compressToBase64 : function (input) { if (input == null) return ""; - var res = LZString._compress(input, 6, function(a){return keyStrBase64[a];}); - switch (res.length % 4) { // To produce valid Base64 - default: // When could this happen ? - case 0 : return res; - case 1 : return res+"==="; - case 2 : return res+"=="; - case 3 : return res+"="; + var res = LZString._compressToArray(input, 6, function(a){return keyStrBase64[a];}); + // To produce valid Base64 + var i = res.length % 4; + while(i--){ + res.push("="); } + + return res.join(''); }, decompressFromBase64 : function (input) { @@ -47,7 +47,9 @@ var LZString = { compressToUTF16 : function (input) { if (input == null) return ""; - return LZString._compress(input, 15, function(a){return f(a+32);}) + " "; + var compressed = LZString._compressToArray(input, 15, function(a){return f(a+32);}); + compressed.push(" "); + return compressed.join(''); }, decompressFromUTF16: function (compressed) { @@ -58,11 +60,11 @@ var LZString = { //compress into uint8array (UCS-2 big endian format) compressToUint8Array: function (uncompressed) { - var compressed = LZString.compress(uncompressed); + var compressed = LZString.compressToArray(uncompressed); var buf=new Uint8Array(compressed.length*2); // 2 bytes per character for (var i=0, TotalLen=compressed.length; i>> 8; buf[i*2+1] = current_value % 256; } @@ -72,7 +74,7 @@ var LZString = { //decompress from uint8array (UCS-2 big endian format) decompressFromUint8Array:function (compressed) { if (compressed===null || compressed===undefined){ - return LZString.decompress(compressed); + return LZString.decompressFromArray(compressed); } else { var buf=new Array(compressed.length/2); // 2 bytes per character for (var i=0, TotalLen=buf.length; i Date: Mon, 26 Jun 2017 20:51:21 +0200 Subject: [PATCH 07/39] Avoid intermediate array creation --- libs/lz-string.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 91cfa0e..759ca75 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -75,20 +75,10 @@ var LZString = { decompressFromUint8Array:function (compressed) { if (compressed===null || compressed===undefined){ return LZString.decompressFromArray(compressed); - } else { - var buf=new Array(compressed.length/2); // 2 bytes per character - for (var i=0, TotalLen=buf.length; i Date: Fri, 30 Jun 2017 14:11:38 +0200 Subject: [PATCH 08/39] Decode Base64 URI-safe with charCodeAt instead of charAt --- libs/lz-string.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 759ca75..75290bd 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -11,15 +11,15 @@ var LZString = (function() { // private property var f = String.fromCharCode; -var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); -var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''); +var Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); +var UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''); var baseReverseDic = {}; function getReverseDict(alphabet){ if (!baseReverseDic[alphabet]) { baseReverseDic[alphabet] = {}; for (var i=0 ; i>> 8; - buf[i*2+1] = current_value % 256; + buf[i*2+1] = current_value & 0xFF; } return buf; }, @@ -85,7 +85,7 @@ var LZString = { //compress into a string that is already URI encoded compressToEncodedURIComponent: function (input) { if (input == null) return ""; - return LZString._compressToArray(input, 6, function(a){return keyStrUriSafe[a];}).join(''); + return LZString._compressToArray(input, 6, function(a){return UriSafeCharArray[a];}).join(''); }, //decompress from an output of compressToEncodedURIComponent @@ -93,8 +93,8 @@ var LZString = { if (input == null) return ""; if (input == "") return null; input = input.replace(/ /g, "+"); - var reverseDict = getReverseDict(keyStrUriSafe); - return LZString._decompress(input.length, 32, function(index) { return reverseDict[input.charAt(index)]; }); + var reverseDict = getReverseDict(UriSafeCharArray); + return LZString._decompress(input.length, 32, function(index) { return reverseDict[input.charCodeAt(index)]; }); }, compress: function (uncompressed) { From c884d4187f9add85603754e9c43745fa25a2a5c6 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sat, 1 Jul 2017 01:31:13 +0200 Subject: [PATCH 09/39] Use charCodeAt in _compressToArray --- libs/lz-string.js | 289 ++++++++++++++++++++++++---------------------- 1 file changed, 149 insertions(+), 140 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 75290bd..9050492 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -105,219 +105,228 @@ var LZString = { }, _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt){ if (uncompressed == null) return []; - var i, value, - context_dictionary= {}, - context_dictionaryToCreate= {}, - context_c="", - context_wc="", - context_w="", - context_enlargeIn= 2, // Compensate for the first entry which should not count - context_dictSize= 3, - context_numBits= 2, - context_data=[], - context_data_val=0, - context_data_position=0, - ii; - - for (ii = 0; ii < uncompressed.length; ii += 1) { - context_c = uncompressed.charAt(ii); - if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) { - context_dictionary[context_c] = context_dictSize++; - context_dictionaryToCreate[context_c] = true; + var i=0, j=0, value=0, + dictionary={}, + dictionaryToCreate={}, + c=0, + node=dictionary, + node_c=0, + new_node={}, + enlargeIn= 2, // Compensate for the first entry which should not count + dictSize= 3, + numBits= 2, + data=[], + data_val=0, + data_position=0; + + for (j = 0; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + + // Is c a new character that needs to + // be stored at the root? + if (dictionary[c] == undefined) { + new_node = {}; + new_node[-1] = dictSize++; + new_node[-2] = c; + dictionary[c] = new_node; + dictionaryToCreate[c] = true; } - context_wc = context_w + context_c; - if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) { - context_w = context_wc; + new_node = node[c]; + if (new_node) { + node = new_node; } else { - if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { - if (context_w.charCodeAt(0)<256) { - for (i=0 ; i> 1; + value >>= 1; } } else { value = 1; - for (i=0 ; i> 1; + value >>= 1; } } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = Math.pow(2, context_numBits); - context_numBits++; + enlargeIn--; + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; } - delete context_dictionaryToCreate[context_w]; + dictionaryToCreate[node_c] = false; } else { - value = context_dictionary[context_w]; - for (i=0 ; i> 1; + value >>= 1; } - - } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = Math.pow(2, context_numBits); - context_numBits++; + enlargeIn--; + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; } - // Add wc to the dictionary. - context_dictionary[context_wc] = context_dictSize++; - context_w = String(context_c); + // Add prefix to the dictionary. + new_node = {}; + new_node[-1] = dictSize++; + new_node[-2] = c; + node[c] = new_node; + node = dictionary[c]; } } - // Output the code for w. - if (context_w !== "") { - if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { - if (context_w.charCodeAt(0)<256) { - for (i=0 ; i> 1; } } else { value = 1; - for (i=0 ; i> 1; } } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = Math.pow(2, context_numBits); - context_numBits++; + enlargeIn--; + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; } - delete context_dictionaryToCreate[context_w]; + dictionaryToCreate[node_c] = false; } else { - value = context_dictionary[context_w]; - for (i=0 ; i> 1; + value >>= 1; } } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = Math.pow(2, context_numBits); - context_numBits++; + enlargeIn--; + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; } } // Mark the end of the stream value = 2; - for (i=0 ; i> 1; + value >>= 1; } // Flush the last char while (true) { - context_data_val = (context_data_val << 1); - if (context_data_position == bitsPerChar-1) { - context_data.push(getCharFromInt(context_data_val)); + data_val = (data_val << 1); + if (data_position == bitsPerChar-1) { + data.push(getCharFromInt(data_val)); break; } - else context_data_position++; + else data_position++; } - return context_data; + return data; }, decompress: function (compressed) { From eacfb3bddc678ff9706decb04a123e8f7e43a609 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sat, 1 Jul 2017 13:35:25 +0200 Subject: [PATCH 10/39] Use offset instead of negative indexes for lookup --- libs/lz-string.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 9050492..5352f31 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -109,6 +109,7 @@ var LZString = { dictionary={}, dictionaryToCreate={}, c=0, + c0=2, node=dictionary, node_c=0, new_node={}, @@ -121,22 +122,22 @@ var LZString = { for (j = 0; j < uncompressed.length; j++) { c = uncompressed.charCodeAt(j); - + c0 = c+2; // Is c a new character that needs to // be stored at the root? - if (dictionary[c] == undefined) { + if (dictionary[c0] == undefined) { new_node = {}; - new_node[-1] = dictSize++; - new_node[-2] = c; - dictionary[c] = new_node; + new_node[0] = dictSize++; + new_node[1] = c; + dictionary[c0] = new_node; dictionaryToCreate[c] = true; } - new_node = node[c]; + new_node = node[c0]; if (new_node) { node = new_node; } else { - node_c = node[-2]; + node_c = node[1]; if (dictionaryToCreate[node_c]) { if (node_c<256) { for (i=0 ; i Date: Sat, 1 Jul 2017 17:07:17 +0200 Subject: [PATCH 11/39] Slightly shorten code Yes, this is pre- and post-fix increment/decrement abuse. But we're being low level enough here that I think we can handle it. --- libs/lz-string.js | 73 +++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 53 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 5352f31..722bed1 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -142,23 +142,19 @@ var LZString = { if (node_c<256) { for (i=0 ; i>= 1; } @@ -166,52 +162,42 @@ var LZString = { value = 1; for (i=0 ; i>= 1; } } - enlargeIn--; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; + if (--enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits++); } dictionaryToCreate[node_c] = false; } else { value = node[0]; for (i=0 ; i>= 1; } } - enlargeIn--; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; + if (--enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits++); } // Add prefix to the dictionary. new_node = {}; @@ -229,23 +215,19 @@ var LZString = { if (node_c<256) { for (i=0 ; i> 1; } @@ -253,24 +235,20 @@ var LZString = { value = 1; for (i=0 ; i> 1; } @@ -285,22 +263,17 @@ var LZString = { value = node[0]; for (i=0 ; i>= 1; } - - } - enlargeIn--; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; + + if (--enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits++); } } @@ -308,25 +281,19 @@ var LZString = { value = 2; for (i=0 ; i>= 1; } // Flush the last char - while (true) { + while (data_position++ < bitsPerChar) { data_val = (data_val << 1); - if (data_position == bitsPerChar-1) { - data.push(getCharFromInt(data_val)); - break; - } - else data_position++; } + data.push(getCharFromInt(data_val)); return data; }, From 4e4cea10f008cbe475674a53fd876e189050c1b5 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sat, 1 Jul 2017 20:23:16 +0200 Subject: [PATCH 12/39] Shortened decompress --- libs/lz-string.js | 189 +++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 120 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 722bed1..9e91091 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -309,162 +309,111 @@ var LZString = { return LZString._decompress(compressed.length, 32768, function(index) { return compressed[index].charCodeAt(0); }); }, _decompress: function (length, resetValue, getNextValue) { - var dictionary = [], - next, + var dictionary = [0, 1, 2], enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = [], - i, - w, - bits, resb, maxpower, power, - c, - data = {val:getNextValue(0), position:resetValue, index:1}; - - for (i = 0; i < 3; i += 1) { - dictionary[i] = i; - } + w = "", + bits = 0, + resb = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetValue, + data_index = 1; - bits = 0; - maxpower = Math.pow(2,2); - power=1; while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); + resb = data_val & data_position; + data_position >>= 1; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; + bits |= (resb>0 ? 1 : 0) << power++; } - switch (next = bits) { - case 0: - bits = 0; - maxpower = Math.pow(2,8); - power=1; - while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; - } - c = f(bits); - break; - case 1: - bits = 0; - maxpower = Math.pow(2,16); - power=1; - while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; - } - c = f(bits); - break; - case 2: - return ""; + if (bits == 2){ + return "" } + //Math.pow(2,8 + 8*bits); + maxpower = bits*8+8; + + bits = 0; + power = 0; + while (power!=maxpower) { + resb = data_val & data_position; + data_position >>= 1; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); + } + bits |= (resb>0 ? 1 : 0) << power++; + } + c = f(bits); dictionary[3] = c; w = c; result.push(c); + while (true) { - if (data.index > length) { + if (data_index > length) { return ""; } bits = 0; - maxpower = Math.pow(2,numBits); - power=1; + maxpower = numBits;//Math.pow(2,numBits); + power = 0; while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); + resb = data_val & data_position; + data_position >>= 1; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; + bits |= (resb>0 ? 1 : 0) << power++; } - switch (c = bits) { - case 0: - bits = 0; - maxpower = Math.pow(2,8); - power=1; - while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; - } - - dictionary[dictSize++] = f(bits); - c = dictSize-1; - enlargeIn--; - break; - case 1: - bits = 0; - maxpower = Math.pow(2,16); - power=1; - while (power!=maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb>0 ? 1 : 0) * power; - power <<= 1; + if (bits == 0 || bits == 1){ + power=0; + maxpower = (8+8*bits);//Math.pow(2,8 + 8*bits); + bits = 0; + while (power!=maxpower) { + resb = data_val & data_position; + data_position >>= 1; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); } - dictionary[dictSize++] = f(bits); - c = dictSize-1; - enlargeIn--; - break; - case 2: - return result.join(''); + bits |= (resb>0 ? 1 : 0) << power++; + } + dictionary[dictSize] = f(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits++); + } + } else if (bits == 2){ + return result.join(''); } - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; - } - if (dictionary[c]) { - entry = dictionary[c]; + if (dictionary[bits]) { + entry = dictionary[bits]; + } else if (bits === dictSize) { + entry = w + w.charAt(0); } else { - if (c === dictSize) { - entry = w + w.charAt(0); - } else { - return null; - } + return null; } result.push(entry); // Add w+entry[0] to the dictionary. dictionary[dictSize++] = w + entry.charAt(0); - enlargeIn--; w = entry; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } } From 9f645c99aa025871f72977606ee43df0ea3088e2 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 02:15:11 +0200 Subject: [PATCH 13/39] Bit hacks to reduce _decompress size No notable effect on performance, but should minify a bit better --- libs/lz-string.js | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 9e91091..5e997a2 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -309,6 +309,7 @@ var LZString = { return LZString._decompress(compressed.length, 32768, function(index) { return compressed[index].charCodeAt(0); }); }, _decompress: function (length, resetValue, getNextValue) { + resetValue = Math.log2(resetValue) + 1; var dictionary = [0, 1, 2], enlargeIn = 4, dictSize = 4, @@ -317,7 +318,6 @@ var LZString = { result = [], w = "", bits = 0, - resb = 0, maxpower = 2, power = 0, c = "", @@ -326,13 +326,11 @@ var LZString = { data_index = 1; while (power!=maxpower) { - resb = data_val & data_position; - data_position >>= 1; + bits += ((data_val >> --data_position)&1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) << power++; } if (bits == 2){ @@ -340,17 +338,13 @@ var LZString = { } //Math.pow(2,8 + 8*bits); maxpower = bits*8+8; - - bits = 0; - power = 0; + bits = power = 0; while (power!=maxpower) { - resb = data_val & data_position; - data_position >>= 1; + bits += ((data_val >> --data_position)&1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) << power++; } c = f(bits); dictionary[3] = c; @@ -362,31 +356,26 @@ var LZString = { return ""; } - bits = 0; maxpower = numBits;//Math.pow(2,numBits); - power = 0; + bits = power = 0; while (power!=maxpower) { - resb = data_val & data_position; - data_position >>= 1; + bits += ((data_val >> --data_position)&1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) << power++; } - if (bits == 0 || bits == 1){ - power=0; - maxpower = (8+8*bits);//Math.pow(2,8 + 8*bits); - bits = 0; + // 0 or 1 + if ((bits&1) == bits){ + maxpower = (8+8*bits); + bits = power=0; while (power!=maxpower) { - resb = data_val & data_position; - data_position >>= 1; + bits += ((data_val >> --data_position)&1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); } - bits |= (resb>0 ? 1 : 0) << power++; } dictionary[dictSize] = f(bits); bits = dictSize++; @@ -397,7 +386,6 @@ var LZString = { return result.join(''); } - if (dictionary[bits]) { entry = dictionary[bits]; } else if (bits === dictSize) { From 35f724335c04fdbf4c5bfb4eff907f4f50fb4687 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 10:48:46 +0200 Subject: [PATCH 14/39] Get rid of reverseDict Makes the code a bit more compact --- libs/lz-string.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 5e997a2..4283102 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -10,20 +10,16 @@ var LZString = (function() { // private property -var f = String.fromCharCode; -var Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); -var UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''); -var baseReverseDic = {}; - -function getReverseDict(alphabet){ - if (!baseReverseDic[alphabet]) { - baseReverseDic[alphabet] = {}; - for (var i=0 ; i Date: Sun, 2 Jul 2017 11:07:09 +0200 Subject: [PATCH 15/39] Hoist getCharFromInt functions Before: every time we call compress, these functions are compiled again, and then discared After: compile once, reuse. Reduces JIT and GC pressure, might help JIT optimisations. --- libs/lz-string.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 4283102..95d7a1d 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -20,11 +20,15 @@ var f = String.fromCharCode, Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; } + var getChar16Bits = function(a){return f(a);}, + getCharFromBase64 = function(a){return Base64CharArray[a];}, + getCharFromURISafe = function(a){return UriSafeCharArray[a];}, + getCharFromUTF16 = function(a){return f(a+32);}; var LZString = { compressToBase64 : function (input) { if (input == null) return ""; - var res = LZString._compressToArray(input, 6, function(a){return Base64CharArray[a];}); + var res = LZString._compressToArray(input, 6, getCharFromBase64); // To produce valid Base64 var i = res.length % 4; while(i--){ @@ -42,7 +46,7 @@ var LZString = { compressToUTF16 : function (input) { if (input == null) return ""; - var compressed = LZString._compressToArray(input, 15, function(a){return f(a+32);}); + var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); compressed.push(" "); return compressed.join(''); }, @@ -80,7 +84,7 @@ var LZString = { //compress into a string that is already URI encoded compressToEncodedURIComponent: function (input) { if (input == null) return ""; - return LZString._compressToArray(input, 6, function(a){return UriSafeCharArray[a];}).join(''); + return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); }, //decompress from an output of compressToEncodedURIComponent @@ -95,7 +99,7 @@ var LZString = { return LZString.compressToArray(uncompressed).join(''); }, compressToArray: function (uncompressed){ - return LZString._compressToArray(uncompressed, 16, function(a){return f(a);}); + return LZString._compressToArray(uncompressed, 16, getChar16Bits); }, _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt){ if (uncompressed == null) return []; From a74876b017d57fad1cb349e255770edfad9fad30 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 14:02:33 +0200 Subject: [PATCH 16/39] Add some documentation --- libs/lz-string.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 95d7a1d..9e22670 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -323,6 +323,9 @@ var LZString = { data_position = resetValue, data_index = 1; + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. while (power!=maxpower) { bits += ((data_val >> --data_position)&1) << power++; if (data_position == 0) { @@ -331,10 +334,12 @@ var LZString = { } } + // if end of stream token, return empty string if (bits == 2){ return ""; } - //Math.pow(2,8 + 8*bits); + + // else, get character maxpower = bits*8+8; bits = power = 0; while (power!=maxpower) { @@ -349,12 +354,14 @@ var LZString = { w = c; result.push(c); + // read rest of string while (true) { if (data_index > length) { return ""; } - maxpower = numBits;//Math.pow(2,numBits); + // read out next token + maxpower = numBits; bits = power = 0; while (power!=maxpower) { bits += ((data_val >> --data_position)&1) << power++; @@ -364,7 +371,7 @@ var LZString = { } } - // 0 or 1 + // 0 or 1 implies new character token if ((bits&1) == bits){ maxpower = (8+8*bits); bits = power=0; @@ -381,6 +388,7 @@ var LZString = { enlargeIn = Math.pow(2, numBits++); } } else if (bits == 2){ + // end of stream token return result.join(''); } From 06b4b889b68360e03c919b8df9c14f01073024ea Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 17:54:02 +0200 Subject: [PATCH 17/39] Last bit of code-golfing, I swear --- libs/lz-string.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 9e22670..9faf6e4 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -355,11 +355,7 @@ var LZString = { result.push(c); // read rest of string - while (true) { - if (data_index > length) { - return ""; - } - + while (data_index <= length) { // read out next token maxpower = numBits; bits = power = 0; @@ -372,7 +368,7 @@ var LZString = { } // 0 or 1 implies new character token - if ((bits&1) == bits){ + if (bits < 2){ maxpower = (8+8*bits); bits = power=0; while (power!=maxpower) { @@ -385,22 +381,18 @@ var LZString = { dictionary[dictSize] = f(bits); bits = dictSize++; if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); + enlargeIn = 1 << numBits++; } } else if (bits == 2){ // end of stream token return result.join(''); } - if (dictionary[bits]) { - entry = dictionary[bits]; - } else if (bits === dictSize) { - entry = w + w.charAt(0); - } else { + if (bits > dictionary.length){ return null; } + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); result.push(entry); - // Add w+entry[0] to the dictionary. dictionary[dictSize++] = w + entry.charAt(0); @@ -411,6 +403,7 @@ var LZString = { } } + return ""; } }; return LZString; From b2e43a6334d67926758da291e9697b4c89315a68 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 19:50:47 +0200 Subject: [PATCH 18/39] Autoformatter --- libs/lz-string.js | 729 +++++++++++++++++++++++----------------------- 1 file changed, 366 insertions(+), 363 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 9faf6e4..6ae6cbe 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -7,169 +7,261 @@ // http://pieroxy.net/blog/pages/lz-string/testing.html // // LZ-based compression algorithm, version 1.4.4 -var LZString = (function() { - -// private property -var f = String.fromCharCode, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - Base64ReverseDic = {}, - UriSafeReverseDic = {}, - i = 65; - while(i--) { - Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; - UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; - } - var getChar16Bits = function(a){return f(a);}, - getCharFromBase64 = function(a){return Base64CharArray[a];}, - getCharFromURISafe = function(a){return UriSafeCharArray[a];}, - getCharFromUTF16 = function(a){return f(a+32);}; - -var LZString = { - compressToBase64 : function (input) { - if (input == null) return ""; - var res = LZString._compressToArray(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while(i--){ - res.push("="); +var LZString = ( + function () { + + // private property + var f = String.fromCharCode, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + Base64ReverseDic = {}, + UriSafeReverseDic = {}, + i = 65; + while (i--) { + Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; + UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; } + var getChar16Bits = function (a) { return f(a); }, + getCharFromBase64 = function (a) { return Base64CharArray[a]; }, + getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, + getCharFromUTF16 = function (a) { return f(a + 32); }; + + var LZString = { + compressToBase64: function (input) { + if (input == null) return ""; + var res = LZString._compressToArray(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } - return res.join(''); - }, - - decompressFromBase64 : function (input) { - if (input == null) return ""; - if (input == "") return null; - return LZString._decompress(input.length, 32, function(index) { return Base64ReverseDic[input.charCodeAt(index)]; }); - }, - - compressToUTF16 : function (input) { - if (input == null) return ""; - var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = LZString.compressToArray(uncompressed); - var buf=new Uint8Array(compressed.length*2); // 2 bytes per character - - for (var i=0, TotalLen=compressed.length; i>> 8; - buf[i*2+1] = current_value & 0xFF; - } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array:function (compressed) { - if (compressed===null || compressed===undefined){ - return LZString.decompressFromArray(compressed); - } else if (compressed.length == 0){ - return null; - } - return LZString._decompress(compressed.length, 128, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent:function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return LZString._decompress(input.length, 32, function(index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return LZString.compressToArray(uncompressed).join(''); - }, - compressToArray: function (uncompressed){ - return LZString._compressToArray(uncompressed, 16, getChar16Bits); - }, - _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt){ - if (uncompressed == null) return []; - var i=0, j=0, value=0, - dictionary={}, - dictionaryToCreate={}, - c=0, - c0=2, - node=dictionary, - node_c=0, - new_node={}, - enlargeIn= 2, // Compensate for the first entry which should not count - dictSize= 3, - numBits= 2, - data=[], - data_val=0, - data_position=0; - - for (j = 0; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - c0 = c+2; - // Is c a new character that needs to - // be stored at the root? - if (dictionary[c0] == undefined) { - new_node = {}; - new_node[0] = dictSize++; - new_node[1] = c; - dictionary[c0] = new_node; - dictionaryToCreate[c] = true; - } + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return LZString._decompress(input.length, 32, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = LZString.compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return LZString.decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return LZString._decompress(compressed.length, 128, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return LZString._decompress(input.length, 32, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return LZString.compressToArray(uncompressed).join(''); + }, + compressToArray: function (uncompressed) { + return LZString._compressToArray(uncompressed, 16, getChar16Bits); + }, + _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, value = 0, + dictionary = {}, + dictionaryToCreate = {}, + c = 0, + c0 = 2, + node = dictionary, + node_c = 0, + new_node = {}, + enlargeIn = 2, // Compensate for the first entry which should not count + dictSize = 3, + numBits = 2, + data = [], + data_val = 0, + data_position = 0; + + for (j = 0; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + c0 = c + 2; + // Is c a new character that needs to + // be stored at the root? + if (dictionary[c0] == undefined) { + new_node = {}; + new_node[0] = dictSize++; + new_node[1] = c; + dictionary[c0] = new_node; + dictionaryToCreate[c] = true; + } - new_node = node[c0]; - if (new_node) { - node = new_node; - } else { - node_c = node[1]; - if (dictionaryToCreate[node_c]) { - if (node_c<256) { - for (i=0 ; i>= 1; + } + } else { + value = 1; + for (i = 0; i < numBits; i++) { + data_val = (data_val << 1) | value; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + value = 0; + } + value = node_c; + for (i = 0; i < 16; i++) { + data_val = (data_val << 1) | (value & 1); + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + value >>= 1; + } } - } - value = node_c; - for (i=0 ; i<8 ; i++) { - data_val = (data_val << 1) | (value&1); - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + if (--enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits++); + } + dictionaryToCreate[node_c] = false; + } else { + value = node[0]; + for (i = 0; i < numBits; i++) { + data_val = (data_val << 1) | (value & 1); + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + value >>= 1; } - value >>= 1; } - } else { - value = 1; - for (i=0 ; i> 1; } - value = 0; + } else { + value = 1; + for (i = 0; i < numBits; i++) { + data_val = (data_val << 1) | value; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + value = 0; + } + value = node_c; + for (i = 0; i < 16; i++) { + data_val = (data_val << 1) | (value & 1); + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + value = value >> 1; + } + } + enlargeIn--; + if (enlargeIn == 0) { + enlargeIn = Math.pow(2, numBits); + numBits++; } - value = node_c; - for (i=0 ; i<16 ; i++) { - data_val = (data_val << 1) | (value&1); + dictionaryToCreate[node_c] = false; + } else { + value = node[0]; + for (i = 0; i < numBits; i++) { + data_val = (data_val << 1) | (value & 1); if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); @@ -178,89 +270,16 @@ var LZString = { value >>= 1; } } + if (--enlargeIn == 0) { enlargeIn = Math.pow(2, numBits++); } - dictionaryToCreate[node_c] = false; - } else { - value = node[0]; - for (i=0 ; i>= 1; - } - } - if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); } - // Add prefix to the dictionary. - new_node = {}; - new_node[0] = dictSize++; - new_node[1] = c; - node[c0] = new_node; - node = dictionary[c0]; - } - } - // Output the code for node. - if (node !== undefined) { - node_c = node[1]; - if (dictionaryToCreate[node_c]) { - if (node_c<256) { - for (i=0 ; i> 1; - } - } else { - value = 1; - for (i=0 ; i> 1; - } - } - enlargeIn--; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; - } - dictionaryToCreate[node_c] = false; - } else { - value = node[0]; - for (i=0 ; i>= 1; } - } - if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); - } - } - - // Mark the end of the stream - value = 2; - for (i=0 ; i>= 1; - } - - // Flush the last char - while (data_position++ < bitsPerChar) { - data_val = (data_val << 1); - } - data.push(getCharFromInt(data_val)); - return data; - }, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return LZString._decompress(compressed.length, 32768, function(index) { return compressed[index].charCodeAt(0); }); }, - - _decompress: function (length, resetValue, getNextValue) { - resetValue = Math.log2(resetValue) + 1; - var dictionary = [0, 1, 2], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - w = "", - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetValue, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power!=maxpower) { - bits += ((data_val >> --data_position)&1) << power++; - if (data_position == 0) { - data_position = resetValue; - data_val = getNextValue(data_index++); - } - } - - // if end of stream token, return empty string - if (bits == 2){ - return ""; - } + return data; + }, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: function (compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return LZString._decompress(compressed.length, 32768, function (index) { return compressed[index].charCodeAt(0); }); + }, + + _decompress: function (length, resetValue, getNextValue) { + resetValue = Math.log2(resetValue) + 1; + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + w = "", + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetValue, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + bits += ((data_val >> --data_position) & 1) << power++; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); + } + } - // else, get character - maxpower = bits*8+8; - bits = power = 0; - while (power!=maxpower) { - bits += ((data_val >> --data_position)&1) << power++; - if (data_position == 0) { - data_position = resetValue; - data_val = getNextValue(data_index++); - } - } - c = f(bits); - dictionary[3] = c; - w = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; - bits = power = 0; - while (power!=maxpower) { - bits += ((data_val >> --data_position)&1) << power++; - if (data_position == 0) { - data_position = resetValue; - data_val = getNextValue(data_index++); + // if end of stream token, return empty string + if (bits == 2) { + return ""; } - } - // 0 or 1 implies new character token - if (bits < 2){ - maxpower = (8+8*bits); - bits = power=0; - while (power!=maxpower) { - bits += ((data_val >> --data_position)&1) << power++; + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + bits += ((data_val >> --data_position) & 1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); } } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2){ - // end of stream token - return result.join(''); - } + c = f(bits); + dictionary[3] = c; + w = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + bits += ((data_val >> --data_position) & 1) << power++; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); + } + } - if (bits > dictionary.length){ - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); - result.push(entry); - // Add w+entry[0] to the dictionary. - dictionary[dictSize++] = w + entry.charAt(0); + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); + bits = power = 0; + while (power != maxpower) { + bits += ((data_val >> --data_position) & 1) << power++; + if (data_position == 0) { + data_position = resetValue; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = f(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } - w = entry; + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); + result.push(entry); + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } + w = entry; - } - return ""; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + } + return ""; + } + }; + return LZString; } -}; - return LZString; -})(); +)(); if (typeof define === 'function' && define.amd) { define(function () { return LZString; }); -} else if( typeof module !== 'undefined' && module != null ) { +} else if (typeof module !== 'undefined' && module != null) { module.exports = LZString -} else if( typeof angular !== 'undefined' && angular != null ) { +} else if (typeof angular !== 'undefined' && angular != null) { angular.module('LZString', []) - .factory('LZString', function () { - return LZString; - }); + .factory('LZString', function () { + return LZString; + }); } From d486dc41a01198d3c8b0f7b31c8ac0a3a0a0502c Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Sun, 2 Jul 2017 23:45:59 +0200 Subject: [PATCH 19/39] Bit more code golfing Shifting has precedence over bitmasking. Bit of rewriting lets us get rid of `node_c` altogether. --- libs/lz-string.js | 78 +++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 6ae6cbe..79ea5af 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -110,7 +110,6 @@ var LZString = ( c = 0, c0 = 2, node = dictionary, - node_c = 0, new_node = {}, enlargeIn = 2, // Compensate for the first entry which should not count dictSize = 3, @@ -136,67 +135,67 @@ var LZString = ( if (new_node) { node = new_node; } else { - node_c = node[1]; - if (dictionaryToCreate[node_c]) { - if (node_c < 256) { + value = node[1]; + if (dictionaryToCreate[value]) { + if (value < 256) { + // insert "new 8 bit charCode" token + // into bitstream (value 0) for (i = 0; i < numBits; i++) { - data_val = (data_val << 1); + data_val <<= 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } - value = node_c; + // insert 8 bit charcode for (i = 0; i < 8; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value >>= 1; } } else { - value = 1; + // insert "new 16 bit charCode" token + // into bitstream (value 1) for (i = 0; i < numBits; i++) { - data_val = (data_val << 1) | value; + data_val = 1>>i | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value = 0; } - value = node_c; + // insert 16 bit charCode for (i = 0; i < 16; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value >>= 1; } } if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); + enlargeIn = 1 << numBits++; } - dictionaryToCreate[node_c] = false; + dictionaryToCreate[value] = false; } else { + // insert dictionary token into bitstream value = node[0]; for (i = 0; i < numBits; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value >>= 1; } } if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); + enlargeIn = 1 << numBits++; } // Add prefix to the dictionary. new_node = {}; @@ -209,89 +208,76 @@ var LZString = ( // Output the code for node. if (node !== undefined) { - node_c = node[1]; - if (dictionaryToCreate[node_c]) { - if (node_c < 256) { + value = node[1]; + if (dictionaryToCreate[value]) { + if (value < 256) { for (i = 0; i < numBits; i++) { - data_val = (data_val << 1); + data_val <<= 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } - value = node_c; for (i = 0; i < 8; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value = value >> 1; } } else { - value = 1; for (i = 0; i < numBits; i++) { - data_val = (data_val << 1) | value; + data_val = 1>>i | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value = 0; } - value = node_c; for (i = 0; i < 16; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value = value >> 1; } } - enlargeIn--; - if (enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits); - numBits++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } - dictionaryToCreate[node_c] = false; + dictionaryToCreate[value] = false; } else { value = node[0]; for (i = 0; i < numBits; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = value>>i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value >>= 1; } } if (--enlargeIn == 0) { - enlargeIn = Math.pow(2, numBits++); + enlargeIn = 1 << numBits++; } } // Mark the end of the stream - value = 2; for (i = 0; i < numBits; i++) { - data_val = (data_val << 1) | (value & 1); + data_val = 2>>i & 1 | data_val<<1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } - value >>= 1; } // Flush the last char - while (data_position++ < bitsPerChar) { - data_val = (data_val << 1); - } + data_val <<= bitsPerChar - data_position; data.push(getCharFromInt(data_val)); return data; }, From 06a46f675f6afbd9eb349001299980acb1088a70 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Tue, 4 Jul 2017 14:24:42 +0200 Subject: [PATCH 20/39] Got rid of dictionaryToCreate --- libs/lz-string.js | 230 +++++++++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 96 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 79ea5af..59a2204 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -106,87 +106,155 @@ var LZString = ( if (uncompressed == null) return []; var i = 0, j = 0, value = 0, dictionary = {}, - dictionaryToCreate = {}, + freshNode = true, c = 0, - c0 = 2, + c0 = 1, node = dictionary, new_node = {}, - enlargeIn = 2, // Compensate for the first entry which should not count + enlargeIn = 2, dictSize = 3, numBits = 2, data = [], data_val = 0, data_position = 0; - for (j = 0; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - c0 = c + 2; - // Is c a new character that needs to - // be stored at the root? - if (dictionary[c0] == undefined) { - new_node = {}; - new_node[0] = dictSize++; - new_node[1] = c; - dictionary[c0] = new_node; - dictionaryToCreate[c] = true; + if (uncompressed.length) { + // If there IS a charCode, the first is guaranteed to be new, + // so we write it to output stream, add it to the dictionary, + // initialize freshNode as true, and set it as the root node. + + c = uncompressed.charCodeAt(0); + c0 = c + 1; + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + for (i = 0; i < numBits; i++) { + // Value is 0 (8 bit) or 1 (16 bit). + // We shift it into the bitstream in reverse + // (shifting has precedence over bitmasking) + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + // insert charCode + // Nasty but effective hack: + // loop 8 or 16 times based on token value + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + // shifting has precedence over bitmasking + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Add charCode to the dictionary. + new_node = {}; + new_node[0] = dictSize++; + node[c0] = new_node; + // start in this node + node = new_node; + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } - new_node = node[c0]; - if (new_node) { - node = new_node; - } else { - value = node[1]; - if (dictionaryToCreate[value]) { - if (value < 256) { - // insert "new 8 bit charCode" token - // into bitstream (value 0) + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + c0 = c + 1; + // does the new charCode match an existing prefix? + new_node = node[c0]; + if (new_node) { + // continue with next prefix + node = new_node; + } else { + + // write out the current prefix token + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + value = node[0]; for (i = 0; i < numBits; i++) { - data_val <<= 1; + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } - // insert 8 bit charcode - for (i = 0; i < 8; i++) { - data_val = value>>i & 1 | data_val<<1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + } + + // Is the new charCode a new character + // that needs to be stored at the root? + new_node = dictionary[c0]; + if (new_node == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } - } else { - // insert "new 16 bit charCode" token - // into bitstream (value 1) + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 for (i = 0; i < numBits; i++) { - data_val = 1>>i | data_val<<1; + data_val = value >> i | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } - // insert 16 bit charCode - for (i = 0; i < 16; i++) { - data_val = value>>i & 1 | data_val<<1; + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } + new_node = {}; + new_node[0] = dictSize++; + dictionary[c0] = new_node; + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; } + // add node representing prefix + new charCode to trie + new_node = {}; + new_node[0] = dictSize++; + node[c0] = new_node; + // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } - dictionaryToCreate[value] = false; + // set node to first charCode of new prefix + node = dictionary[c0]; + } + } + + if (node) { // Write last node to output + if (freshNode) { + // character token already written to output + freshNode = false; } else { - // insert dictionary token into bitstream + // write out the prefix token value = node[0]; for (i = 0; i < numBits; i++) { - data_val = value>>i & 1 | data_val<<1; + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); @@ -194,50 +262,28 @@ var LZString = ( } } } - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // Add prefix to the dictionary. - new_node = {}; - new_node[0] = dictSize++; - new_node[1] = c; - node[c0] = new_node; - node = dictionary[c0]; - } - } - // Output the code for node. - if (node !== undefined) { - value = node[1]; - if (dictionaryToCreate[value]) { - if (value < 256) { - for (i = 0; i < numBits; i++) { - data_val <<= 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - for (i = 0; i < 8; i++) { - data_val = value>>i & 1 | data_val<<1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // Is c a new character? + new_node = dictionary[c0]; + if (new_node == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } - } else { + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 for (i = 0; i < numBits; i++) { - data_val = 1>>i | data_val<<1; + data_val = value >> i | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); data_val = 0; } } - for (i = 0; i < 16; i++) { - data_val = value>>i & 1 | data_val<<1; + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); @@ -245,30 +291,18 @@ var LZString = ( } } } + // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } - dictionaryToCreate[value] = false; - } else { - value = node[0]; - for (i = 0; i < numBits; i++) { - data_val = value>>i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; } } // Mark the end of the stream for (i = 0; i < numBits; i++) { - data_val = 2>>i & 1 | data_val<<1; + // shifting has precedence over bitmasking + data_val = 2 >> i & 1 | data_val << 1; if (++data_position == bitsPerChar) { data_position = 0; data.push(getCharFromInt(data_val)); @@ -315,7 +349,8 @@ var LZString = ( // a new character token (8 or 16 bits) // or end of stream token. while (power != maxpower) { - bits += ((data_val >> --data_position) & 1) << power++; + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); @@ -331,7 +366,8 @@ var LZString = ( maxpower = bits * 8 + 8; bits = power = 0; while (power != maxpower) { - bits += ((data_val >> --data_position) & 1) << power++; + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); @@ -348,7 +384,8 @@ var LZString = ( maxpower = numBits; bits = power = 0; while (power != maxpower) { - bits += ((data_val >> --data_position) & 1) << power++; + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); @@ -360,7 +397,8 @@ var LZString = ( maxpower = (8 + 8 * bits); bits = power = 0; while (power != maxpower) { - bits += ((data_val >> --data_position) & 1) << power++; + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { data_position = resetValue; data_val = getNextValue(data_index++); From e796777c455334914b225f9092d9b6aa5a53de9b Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Wed, 5 Jul 2017 17:42:40 +0200 Subject: [PATCH 21/39] Math.log2 is ES6, so different method used. --- libs/lz-string.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 59a2204..f748b17 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -329,7 +329,11 @@ var LZString = ( }, _decompress: function (length, resetValue, getNextValue) { - resetValue = Math.log2(resetValue) + 1; + // "Math.log2(resetValue)" is ES6, so we use + // this while loop instead for backwards compatibility + var _resetValue = 0; + while(resetValue >> ++_resetValue){} + var dictionary = [0, 1, 2], enlargeIn = 4, dictSize = 4, @@ -342,7 +346,7 @@ var LZString = ( power = 0, c = "", data_val = getNextValue(0), - data_position = resetValue, + data_position = _resetValue, data_index = 1; // Get first token, guaranteed to be either @@ -352,7 +356,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = resetValue; + data_position = _resetValue; data_val = getNextValue(data_index++); } } @@ -369,7 +373,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = resetValue; + data_position = _resetValue; data_val = getNextValue(data_index++); } } @@ -387,7 +391,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = resetValue; + data_position = _resetValue; data_val = getNextValue(data_index++); } } @@ -400,7 +404,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = resetValue; + data_position = _resetValue; data_val = getNextValue(data_index++); } } From 3e3e5d8b7b94ca58ee4989508dcb75bdddbe6b88 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Thu, 6 Jul 2017 00:51:13 +0200 Subject: [PATCH 22/39] Use object literal for new_node --- libs/lz-string.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index f748b17..a24fa38 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -159,8 +159,7 @@ var LZString = ( } // Add charCode to the dictionary. - new_node = {}; - new_node[0] = dictSize++; + new_node = { 0: dictSize++ }; node[c0] = new_node; // start in this node node = new_node; @@ -225,16 +224,14 @@ var LZString = ( data_val = 0; } } - new_node = {}; - new_node[0] = dictSize++; + new_node = { 0: dictSize++ }; dictionary[c0] = new_node; // Note of that we already wrote // the charCode token to the bitstream freshNode = true; } // add node representing prefix + new charCode to trie - new_node = {}; - new_node[0] = dictSize++; + new_node = { 0: dictSize++ }; node[c0] = new_node; // increase token bitlength if necessary if (--enlargeIn == 0) { From 965da38b921b69ef73d6c3fc508a70c9b89089f8 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Thu, 6 Jul 2017 01:01:11 +0200 Subject: [PATCH 23/39] Removed redundant if statement `node` is always defined here --- libs/lz-string.js | 88 +++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index a24fa38..876651e 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -242,57 +242,55 @@ var LZString = ( } } - if (node) { // Write last node to output - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; } } + } - // Is c a new character? - new_node = dictionary[c0]; - if (new_node == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } + // Is c a new character? + new_node = dictionary[c0]; + if (new_node == undefined) { // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } - + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; } } @@ -329,7 +327,7 @@ var LZString = ( // "Math.log2(resetValue)" is ES6, so we use // this while loop instead for backwards compatibility var _resetValue = 0; - while(resetValue >> ++_resetValue){} + while (resetValue >> ++_resetValue) { } var dictionary = [0, 1, 2], enlargeIn = 4, From f1d2422859860132f8543a38ba0be6c6eaad3e22 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Thu, 6 Jul 2017 10:25:05 +0200 Subject: [PATCH 24/39] Initialise first node + comments The first prefix in the dictionary is always the same, so we can "statically" initialise it instead of in code. Also, since the algorithm is pretty complicated, I tried adding more explanatory comments. --- libs/lz-string.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 876651e..c635a32 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -109,19 +109,21 @@ var LZString = ( freshNode = true, c = 0, c0 = 1, - node = dictionary, - new_node = {}, - enlargeIn = 2, - dictSize = 3, + new_node = { 0: 3 }, // first node will always be + node = new_node, // initialised like this. + enlargeIn = 1, + dictSize = 4, numBits = 2, data = [], data_val = 0, data_position = 0; if (uncompressed.length) { - // If there IS a charCode, the first is guaranteed to be new, - // so we write it to output stream, add it to the dictionary, - // initialize freshNode as true, and set it as the root node. + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). c = uncompressed.charCodeAt(0); c0 = c + 1; @@ -144,7 +146,7 @@ var LZString = ( data_val = 0; } } - // insert charCode + // insert charCode bits into bitstream // Nasty but effective hack: // loop 8 or 16 times based on token value value = 8 + 8 * value; @@ -159,14 +161,7 @@ var LZString = ( } // Add charCode to the dictionary. - new_node = { 0: dictSize++ }; - node[c0] = new_node; - // start in this node - node = new_node; - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } + dictionary[c0] = new_node; for (j = 1; j < uncompressed.length; j++) { c = uncompressed.charCodeAt(j); @@ -178,11 +173,18 @@ var LZString = ( node = new_node; } else { - // write out the current prefix token + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + if (freshNode) { - // character token already written to output + // Prefix is a freshly added character token, + // which was already written to the bitstream freshNode = false; } else { + // write out the current prefix token value = node[0]; for (i = 0; i < numBits; i++) { // shifting has precedence over bitmasking From c3f7bc6a1ed630874e2450cbf382d2e5ee41c77a Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Thu, 6 Jul 2017 13:01:56 +0200 Subject: [PATCH 25/39] `resetValue` -> `resetBits` If we're always going to convert resetvalue to it's number of bits... why not just pass the number of bits immediately? --- libs/lz-string.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index c635a32..4cf526c 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -42,7 +42,7 @@ var LZString = ( decompressFromBase64: function (input) { if (input == null) return ""; if (input == "") return null; - return LZString._decompress(input.length, 32, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); + return LZString._decompress(input.length, 6, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); }, compressToUTF16: function (input) { @@ -55,7 +55,7 @@ var LZString = ( decompressFromUTF16: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; - return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; }); + return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); }, //compress into uint8array (UCS-2 big endian format) @@ -78,7 +78,7 @@ var LZString = ( } else if (compressed.length == 0) { return null; } - return LZString._decompress(compressed.length, 128, function (index) { return compressed[index]; }); + return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); }, @@ -93,7 +93,7 @@ var LZString = ( if (input == null) return ""; if (input == "") return null; input = input.replace(/ /g, "+"); - return LZString._decompress(input.length, 32, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); + return LZString._decompress(input.length, 6, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); }, compress: function (uncompressed) { @@ -316,21 +316,16 @@ var LZString = ( decompress: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; - return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); }); + return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); }, decompressFromArray: function (compressed) { if (compressed == null) return ""; if (compressed.length == 0) return null; - return LZString._decompress(compressed.length, 32768, function (index) { return compressed[index].charCodeAt(0); }); + return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); }, - _decompress: function (length, resetValue, getNextValue) { - // "Math.log2(resetValue)" is ES6, so we use - // this while loop instead for backwards compatibility - var _resetValue = 0; - while (resetValue >> ++_resetValue) { } - + _decompress: function (length, resetBits, getNextValue) { var dictionary = [0, 1, 2], enlargeIn = 4, dictSize = 4, @@ -343,7 +338,7 @@ var LZString = ( power = 0, c = "", data_val = getNextValue(0), - data_position = _resetValue, + data_position = resetBits, data_index = 1; // Get first token, guaranteed to be either @@ -353,7 +348,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = _resetValue; + data_position = resetBits; data_val = getNextValue(data_index++); } } @@ -370,7 +365,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = _resetValue; + data_position = resetBits; data_val = getNextValue(data_index++); } } @@ -388,7 +383,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = _resetValue; + data_position = resetBits; data_val = getNextValue(data_index++); } } @@ -401,7 +396,7 @@ var LZString = ( // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = _resetValue; + data_position = resetBits; data_val = getNextValue(data_index++); } } From 628399ec0f47f6988758328b22d661807bb257b2 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Thu, 6 Jul 2017 13:27:32 +0200 Subject: [PATCH 26/39] Turns out `new_node` is redundant Makes code easier to read IMO --- libs/lz-string.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 4cf526c..4f43e6f 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -109,8 +109,7 @@ var LZString = ( freshNode = true, c = 0, c0 = 1, - new_node = { 0: 3 }, // first node will always be - node = new_node, // initialised like this. + node = { 0: 3 }, // first node will always be initialised like this. enlargeIn = 1, dictSize = 4, numBits = 2, @@ -161,16 +160,15 @@ var LZString = ( } // Add charCode to the dictionary. - dictionary[c0] = new_node; + dictionary[c0] = node; for (j = 1; j < uncompressed.length; j++) { c = uncompressed.charCodeAt(j); c0 = c + 1; // does the new charCode match an existing prefix? - new_node = node[c0]; - if (new_node) { + if (node[c0]) { // continue with next prefix - node = new_node; + node = node[c0]; } else { // Prefix+charCode does not exist in trie yet. @@ -199,8 +197,7 @@ var LZString = ( // Is the new charCode a new character // that needs to be stored at the root? - new_node = dictionary[c0]; - if (new_node == undefined) { + if (dictionary[c0] == undefined) { // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; @@ -226,15 +223,13 @@ var LZString = ( data_val = 0; } } - new_node = { 0: dictSize++ }; - dictionary[c0] = new_node; + dictionary[c0] = { 0: dictSize++ }; // Note of that we already wrote // the charCode token to the bitstream freshNode = true; } // add node representing prefix + new charCode to trie - new_node = { 0: dictSize++ }; - node[c0] = new_node; + node[c0] = { 0: dictSize++ }; // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; @@ -263,8 +258,7 @@ var LZString = ( } // Is c a new character? - new_node = dictionary[c0]; - if (new_node == undefined) { + if (dictionary[c0] == undefined) { // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; From 2b03bc38b5605aeb673059c7e7ffb397398db116 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Tue, 11 Jul 2017 17:14:26 +0200 Subject: [PATCH 27/39] Avoid doing same dictionary lookup twice --- libs/lz-string.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 4f43e6f..d33994c 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -110,6 +110,7 @@ var LZString = ( c = 0, c0 = 1, node = { 0: 3 }, // first node will always be initialised like this. + nextNode, enlargeIn = 1, dictSize = 4, numBits = 2, @@ -166,9 +167,10 @@ var LZString = ( c = uncompressed.charCodeAt(j); c0 = c + 1; // does the new charCode match an existing prefix? - if (node[c0]) { + nextNode = node[c0]; + if (nextNode) { // continue with next prefix - node = node[c0]; + node = nextNode; } else { // Prefix+charCode does not exist in trie yet. From 457adbfa4662cd39086b44088781e327ec369de5 Mon Sep 17 00:00:00 2001 From: JobLeonard Date: Tue, 18 Jul 2017 16:54:30 +0200 Subject: [PATCH 28/39] Faster implementation with unsafe worst-case behaviour --- lz-string-unsafe.js | 449 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 lz-string-unsafe.js diff --git a/lz-string-unsafe.js b/lz-string-unsafe.js new file mode 100644 index 0000000..8e914e8 --- /dev/null +++ b/lz-string-unsafe.js @@ -0,0 +1,449 @@ + +var LZStringUnsafe = ( + function () { + + // private property + var f = String.fromCharCode, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + Base64ReverseDic = {}, + UriSafeReverseDic = {}, + i = 65; + while (i--) { + Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; + UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; + } + var getChar16Bits = function (a) { return f(a); }, + getCharFromBase64 = function (a) { return Base64CharArray[a]; }, + getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, + getCharFromUTF16 = function (a) { return f(a + 32); }; + + var LZString = { + compressToBase64: function (input) { + if (input == null) return ""; + var res = LZString._compressToArray(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } + + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return LZString._decompress(input.length, 6, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = LZString.compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return LZString.decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return LZString._decompress(input.length, 6, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return LZString.compressToArray(uncompressed).join(''); + }, + compressToArray: function (uncompressed) { + return LZString._compressToArray(uncompressed, 16, getChar16Bits); + }, + _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, k = 0, value = 0, + node = [3], // first node will always be initialised like this. + // we should never output the root anyway, + // so we initiate with terminating token + // Also, dictionary[1] will be overwritten + // by the firs charCode + dictionary = [2, 2, node], + freshNode = true, + c = 0, + nextNode, + enlargeIn = 1, + dictSize = 4, + numBits = 2, + data = [], + data_val = 0, + data_position = 0; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + for (i = 0; i < numBits; i++) { + // Value is 0 (8 bit) or 1 (16 bit). + // We shift it into the bitstream in reverse + // (shifting has precedence over bitmasking) + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + // insert charCode bits into bitstream + // Nasty but effective hack: + // loop 8 or 16 times based on token value + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + // shifting has precedence over bitmasking + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Add charCode to the dictionary. + dictionary[1] = c; + + nextchar: + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + for (k = 1; k < node.length; k += 2) { + if (node[k] == c) { + node = node[k + 1]; + continue nextchar; + } + } + // we only end up here if there is no matching char + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is the new charCode a new character + // that needs to be stored at the root? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + dictionary.push(c); + dictionary.push([dictSize++]); + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.push(c); + node.push([dictSize++]); + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // set node to first charCode of new prefix + // k is guaranteed to be at the current charCode, + // since we either broke out of the while loop + // when it matched, or just added the new charCode + node = dictionary[k + 1]; + + } + + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is c a new character? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } + + // Mark the end of the stream + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = 2 >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Flush the last char + data_val <<= bitsPerChar - data_position; + data.push(getCharFromInt(data_val)); + return data; + }, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: function (compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + }, + + _decompress: function (length, resetBits, getNextValue) { + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + w = "", + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } + + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = f(bits); + dictionary[3] = c; + w = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = f(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } + + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); + result.push(entry); + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); + + w = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + } + return ""; + } + }; + return LZString; + } +)(); + +if (typeof define === 'function' && define.amd) { + define(function () { return LZStringUnsafe; }); +} else if (typeof module !== 'undefined' && module != null) { + module.exports = LZStringUnsafe +} else if (typeof angular !== 'undefined' && angular != null) { + angular.module('LZStringUnsafe', []) + .factory('LZStringUnsafe', function () { + return LZStringUnsafe; + }); +} From 1ffb4e3fc8663b8e88f97175b4933ab77765c3eb Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Fri, 1 Dec 2017 12:40:57 +0100 Subject: [PATCH 29/39] Code-golfing: 47 chars shaved off minified file - merge Base64 and UriSafe dictionaries into one (microscopically smaller memory overhead! :P) - use concat to duplicate char arrays - removed redundant `w` variable from decompress --- libs/lz-string.js | 849 +++++++++++++++++++++++----------------------- 1 file changed, 425 insertions(+), 424 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index d33994c..4eabf99 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -8,433 +8,434 @@ // // LZ-based compression algorithm, version 1.4.4 var LZString = ( - function () { - - // private property - var f = String.fromCharCode, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - Base64ReverseDic = {}, - UriSafeReverseDic = {}, - i = 65; - while (i--) { - Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; - UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; - } - var getChar16Bits = function (a) { return f(a); }, - getCharFromBase64 = function (a) { return Base64CharArray[a]; }, - getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }; - - var LZString = { - compressToBase64: function (input) { - if (input == null) return ""; - var res = LZString._compressToArray(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while (i--) { - res.push("="); - } - - return res.join(''); - }, - - decompressFromBase64: function (input) { - if (input == null) return ""; - if (input == "") return null; - return LZString._decompress(input.length, 6, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); - }, - - compressToUTF16: function (input) { - if (input == null) return ""; - var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = LZString.compressToArray(uncompressed); - var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character - - for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { - var current_value = compressed[i].charCodeAt(0); - buf[i * 2] = current_value >>> 8; - buf[i * 2 + 1] = current_value & 0xFF; - } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array: function (compressed) { - if (compressed === null || compressed === undefined) { - return LZString.decompressFromArray(compressed); - } else if (compressed.length == 0) { - return null; - } - return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent: function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return LZString._decompress(input.length, 6, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return LZString.compressToArray(uncompressed).join(''); - }, - compressToArray: function (uncompressed) { - return LZString._compressToArray(uncompressed, 16, getChar16Bits); - }, - _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, value = 0, - dictionary = {}, - freshNode = true, - c = 0, - c0 = 1, - node = { 0: 3 }, // first node will always be initialised like this. - nextNode, - enlargeIn = 1, - dictSize = 4, - numBits = 2, - data = [], - data_val = 0, - data_position = 0; - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - c0 = c + 1; - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 - - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - for (i = 0; i < numBits; i++) { - // Value is 0 (8 bit) or 1 (16 bit). - // We shift it into the bitstream in reverse - // (shifting has precedence over bitmasking) - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - // insert charCode bits into bitstream - // Nasty but effective hack: - // loop 8 or 16 times based on token value - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - // shifting has precedence over bitmasking - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - - // Add charCode to the dictionary. - dictionary[c0] = node; - - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - c0 = c + 1; - // does the new charCode match an existing prefix? - nextNode = node[c0]; - if (nextNode) { - // continue with next prefix - node = nextNode; - } else { - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - - // Is the new charCode a new character - // that needs to be stored at the root? - if (dictionary[c0] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - dictionary[c0] = { 0: dictSize++ }; - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node[c0] = { 0: dictSize++ }; - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // set node to first charCode of new prefix - node = dictionary[c0]; - } - } - - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - - // Is c a new character? - if (dictionary[c0] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } - - // Mark the end of the stream - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = 2 >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - - // Flush the last char - data_val <<= bitsPerChar - data_position; - data.push(getCharFromInt(data_val)); - return data; - }, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); - }, - - _decompress: function (length, resetBits, getNextValue) { - var dictionary = [0, 1, 2], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - w = "", - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // if end of stream token, return empty string - if (bits == 2) { - return ""; - } - - // else, get character - maxpower = bits * 8 + 8; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - c = f(bits); - dictionary[3] = c; - w = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); - } - - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); - result.push(entry); - // Add w+entry[0] to the dictionary. - dictionary[dictSize++] = w + entry.charAt(0); - - w = entry; - - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - } - return ""; - } - }; - return LZString; - } + function () { + + // private property + var f = String.fromCharCode, + reverseDict = {}, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; + i = 0; + while (i < 65) { + if (i > 62) { + reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; + } + reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + } + + var getChar16Bits = function (a) { return f(a); }, + getCharFromBase64 = function (a) { return Base64CharArray[a]; }, + getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, + getCharFromUTF16 = function (a) { return f(a + 32); }; + + var LZString = { + compressToBase64: function (input) { + if (input == null) return ""; + var res = LZString._compressToArray(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } + + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return LZString._decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = LZString.compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return LZString.decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return LZString._decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return LZString.compressToArray(uncompressed).join(''); + }, + compressToArray: function (uncompressed) { + return LZString._compressToArray(uncompressed, 16, getChar16Bits); + }, + _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, value = 0, + dictionary = {}, + freshNode = true, + c = 0, + c0 = 1, + node = { 0: 3 }, // first node will always be initialised like this. + nextNode, + enlargeIn = 1, + dictSize = 4, + numBits = 2, + data = [], + data_val = 0, + data_position = 0; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + c0 = c + 1; + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + for (i = 0; i < numBits; i++) { + // Value is 0 (8 bit) or 1 (16 bit). + // We shift it into the bitstream in reverse + // (shifting has precedence over bitmasking) + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + // insert charCode bits into bitstream + // Nasty but effective hack: + // loop 8 or 16 times based on token value + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + // shifting has precedence over bitmasking + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Add charCode to the dictionary. + dictionary[c0] = node; + + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + c0 = c + 1; + // does the new charCode match an existing prefix? + nextNode = node[c0]; + if (nextNode) { + // continue with next prefix + node = nextNode; + } else { + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is the new charCode a new character + // that needs to be stored at the root? + if (dictionary[c0] == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + dictionary[c0] = { 0: dictSize++ }; + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node[c0] = { 0: dictSize++ }; + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // set node to first charCode of new prefix + node = dictionary[c0]; + } + } + + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is c a new character? + if (dictionary[c0] == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } + + // Mark the end of the stream + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = 2 >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Flush the last char + data_val <<= bitsPerChar - data_position; + data.push(getCharFromInt(data_val)); + return data; + }, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: function (compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + }, + + _decompress: function (length, resetBits, getNextValue) { + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } + + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = f(bits); + dictionary[3] = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = f(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } + + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : c + c.charAt(0); + result.push(entry); + // Add c+entry[0] to the dictionary. + dictionary[dictSize++] = c + entry.charAt(0); + + c = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + } + return ""; + } + }; + return LZString; + } )(); if (typeof define === 'function' && define.amd) { - define(function () { return LZString; }); + define(function () { return LZString; }); } else if (typeof module !== 'undefined' && module != null) { - module.exports = LZString + module.exports = LZString } else if (typeof angular !== 'undefined' && angular != null) { - angular.module('LZString', []) - .factory('LZString', function () { - return LZString; - }); + angular.module('LZString', []) + .factory('LZString', function () { + return LZString; + }); } From 1894f7c3765f5544299727885cbb4d9cbadd5db5 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 17 Jan 2018 15:26:58 +0100 Subject: [PATCH 30/39] Fixes + perf improvements + better minification - prevent accidentally making i global - better type stability for nodes in improves performance https://run.perf.zone/view/LZ-String-2018-01-18-v2-1516198507426 https://run.perf.zone/view/LZ-String-2018-01-18-v1-1516197522874 - renamed internal functions for better minification (100 bytes) --- libs/lz-string.js | 57 ++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 4eabf99..cedb051 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -9,14 +9,13 @@ // LZ-based compression algorithm, version 1.4.4 var LZString = ( function () { - // private property - var f = String.fromCharCode, + var i = 0, + f = String.fromCharCode, reverseDict = {}, Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; - i = 0; while (i < 65) { if (i > 62) { reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; @@ -27,12 +26,13 @@ var LZString = ( var getChar16Bits = function (a) { return f(a); }, getCharFromBase64 = function (a) { return Base64CharArray[a]; }, getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }; + getCharFromUTF16 = function (a) { return f(a + 32); }, + makeNode = function(val){ return { v: val, d: {} }; var LZString = { compressToBase64: function (input) { if (input == null) return ""; - var res = LZString._compressToArray(input, 6, getCharFromBase64); + var res = LZString._c(input, 6, getCharFromBase64); // To produce valid Base64 var i = res.length % 4; while (i--) { @@ -45,12 +45,12 @@ var LZString = ( decompressFromBase64: function (input) { if (input == null) return ""; if (input == "") return null; - return LZString._decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + return LZString._d(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); }, compressToUTF16: function (input) { if (input == null) return ""; - var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); + var compressed = LZString._c(input, 15, getCharFromUTF16); compressed.push(" "); return compressed.join(''); }, @@ -58,7 +58,7 @@ var LZString = ( decompressFromUTF16: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; - return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + return LZString._d(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); }, //compress into uint8array (UCS-2 big endian format) @@ -81,14 +81,14 @@ var LZString = ( } else if (compressed.length == 0) { return null; } - return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); + return LZString._d(compressed.length, 8, function (index) { return compressed[index]; }); }, //compress into a string that is already URI encoded compressToEncodedURIComponent: function (input) { if (input == null) return ""; - return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); + return LZString._c(input, 6, getCharFromURISafe).join(''); }, //decompress from an output of compressToEncodedURIComponent @@ -96,23 +96,22 @@ var LZString = ( if (input == null) return ""; if (input == "") return null; input = input.replace(/ /g, "+"); - return LZString._decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + return LZString._d(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); }, compress: function (uncompressed) { return LZString.compressToArray(uncompressed).join(''); }, compressToArray: function (uncompressed) { - return LZString._compressToArray(uncompressed, 16, getChar16Bits); + return LZString._c(uncompressed, 16, getChar16Bits); }, - _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { + _c: function (uncompressed, bitsPerChar, getCharFromInt) { if (uncompressed == null) return []; var i = 0, j = 0, value = 0, dictionary = {}, freshNode = true, c = 0, - c0 = 1, - node = { 0: 3 }, // first node will always be initialised like this. + node = makeNode(3), // first node will always be initialised like this. nextNode, enlargeIn = 1, dictSize = 4, @@ -129,7 +128,6 @@ var LZString = ( // it was already added to the dictionary (see above). c = uncompressed.charCodeAt(0); - c0 = c + 1; // == Write first charCode token to output == @@ -164,13 +162,12 @@ var LZString = ( } // Add charCode to the dictionary. - dictionary[c0] = node; + dictionary[c] = node; for (j = 1; j < uncompressed.length; j++) { c = uncompressed.charCodeAt(j); - c0 = c + 1; // does the new charCode match an existing prefix? - nextNode = node[c0]; + nextNode = node.d[c]; if (nextNode) { // continue with next prefix node = nextNode; @@ -188,7 +185,7 @@ var LZString = ( freshNode = false; } else { // write out the current prefix token - value = node[0]; + value = node.v; for (i = 0; i < numBits; i++) { // shifting has precedence over bitmasking data_val = value >> i & 1 | data_val << 1; @@ -202,7 +199,7 @@ var LZString = ( // Is the new charCode a new character // that needs to be stored at the root? - if (dictionary[c0] == undefined) { + if (dictionary[c] == undefined) { // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; @@ -228,19 +225,19 @@ var LZString = ( data_val = 0; } } - dictionary[c0] = { 0: dictSize++ }; + dictionary[c] = makeNode(dictSize++) // Note of that we already wrote // the charCode token to the bitstream freshNode = true; } // add node representing prefix + new charCode to trie - node[c0] = { 0: dictSize++ }; + node.d[c] = makeNode(dictSize++) // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } // set node to first charCode of new prefix - node = dictionary[c0]; + node = dictionary[c]; } } @@ -250,7 +247,7 @@ var LZString = ( freshNode = false; } else { // write out the prefix token - value = node[0]; + value = node.v; for (i = 0; i < numBits; i++) { // shifting has precedence over bitmasking data_val = value >> i & 1 | data_val << 1; @@ -263,7 +260,7 @@ var LZString = ( } // Is c a new character? - if (dictionary[c0] == undefined) { + if (dictionary[c] == undefined) { // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; @@ -315,17 +312,17 @@ var LZString = ( decompress: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + return LZString._d(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); }, decompressFromArray: function (compressed) { if (compressed == null) return ""; if (compressed.length == 0) return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + return LZString._d(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); }, - _decompress: function (length, resetBits, getNextValue) { - var dictionary = [0, 1, 2], + _d: function (length, resetBits, getNextValue) { + var dictionary = ['', '', ''], enlargeIn = 4, dictSize = 4, numBits = 3, From cd1621c10a742b05ac3cebb7b2a123c9e99c9e05 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 17 Jan 2018 16:51:13 +0100 Subject: [PATCH 31/39] Properly private functions, better minification --- libs/lz-string.js | 848 +++++++++++++++++++++--------------------- libs/lz-string.min.js | 2 +- 2 files changed, 425 insertions(+), 425 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index cedb051..45fe86f 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -7,432 +7,432 @@ // http://pieroxy.net/blog/pages/lz-string/testing.html // // LZ-based compression algorithm, version 1.4.4 -var LZString = ( - function () { - // private property - var i = 0, - f = String.fromCharCode, - reverseDict = {}, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; - while (i < 65) { - if (i > 62) { - reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; - } - reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; - } - - var getChar16Bits = function (a) { return f(a); }, - getCharFromBase64 = function (a) { return Base64CharArray[a]; }, - getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }, - makeNode = function(val){ return { v: val, d: {} }; - - var LZString = { - compressToBase64: function (input) { - if (input == null) return ""; - var res = LZString._c(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while (i--) { - res.push("="); - } - - return res.join(''); - }, - - decompressFromBase64: function (input) { - if (input == null) return ""; - if (input == "") return null; - return LZString._d(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compressToUTF16: function (input) { - if (input == null) return ""; - var compressed = LZString._c(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._d(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = LZString.compressToArray(uncompressed); - var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character - - for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { - var current_value = compressed[i].charCodeAt(0); - buf[i * 2] = current_value >>> 8; - buf[i * 2 + 1] = current_value & 0xFF; - } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array: function (compressed) { - if (compressed === null || compressed === undefined) { - return LZString.decompressFromArray(compressed); - } else if (compressed.length == 0) { - return null; - } - return LZString._d(compressed.length, 8, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return LZString._c(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent: function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return LZString._d(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return LZString.compressToArray(uncompressed).join(''); - }, - compressToArray: function (uncompressed) { - return LZString._c(uncompressed, 16, getChar16Bits); - }, - _c: function (uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, value = 0, - dictionary = {}, - freshNode = true, - c = 0, - node = makeNode(3), // first node will always be initialised like this. - nextNode, - enlargeIn = 1, - dictSize = 4, - numBits = 2, - data = [], - data_val = 0, - data_position = 0; - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 - - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - for (i = 0; i < numBits; i++) { - // Value is 0 (8 bit) or 1 (16 bit). - // We shift it into the bitstream in reverse - // (shifting has precedence over bitmasking) - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - // insert charCode bits into bitstream - // Nasty but effective hack: - // loop 8 or 16 times based on token value - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - // shifting has precedence over bitmasking - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - - // Add charCode to the dictionary. - dictionary[c] = node; - - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - nextNode = node.d[c]; - if (nextNode) { - // continue with next prefix - node = nextNode; - } else { - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - value = node.v; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - - // Is the new charCode a new character - // that needs to be stored at the root? - if (dictionary[c] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - dictionary[c] = makeNode(dictSize++) - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.d[c] = makeNode(dictSize++) - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // set node to first charCode of new prefix - node = dictionary[c]; - } - } - - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - value = node.v; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - - // Is c a new character? - if (dictionary[c] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } - - // Mark the end of the stream - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = 2 >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - - // Flush the last char - data_val <<= bitsPerChar - data_position; - data.push(getCharFromInt(data_val)); - return data; - }, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._d(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return LZString._d(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); - }, - - _d: function (length, resetBits, getNextValue) { - var dictionary = ['', '', ''], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // if end of stream token, return empty string - if (bits == 2) { - return ""; - } - - // else, get character - maxpower = bits * 8 + 8; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - c = f(bits); - dictionary[3] = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); - } - - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : c + c.charAt(0); - result.push(entry); - // Add c+entry[0] to the dictionary. - dictionary[dictSize++] = c + entry.charAt(0); - - c = entry; - - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - } - return ""; - } - }; - return LZString; - } -)(); +var LZString = (function () { + // private property + var i = 0, + f = String.fromCharCode, + reverseDict = {}, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; + while (i < 65) { + if (i > 62) { + reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; + } + reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + } + + var getChar16Bits = function (a) { return f(a); }, + getCharFromBase64 = function (a) { return Base64CharArray[a]; }, + getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, + getCharFromUTF16 = function (a) { return f(a + 32); }, + _node = function (val) { return {v: val, d: {} }; }, + _compress = function (uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, value = 0, + dictionary = {}, + freshNode = true, + c = 0, + node = _node(3), // first node will always be initialised like this. + nextNode, + enlargeIn = 1, + dictSize = 4, + numBits = 2, + data = [], + data_val = 0, + data_position = 0; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + for (i = 0; i < numBits; i++) { + // Value is 0 (8 bit) or 1 (16 bit). + // We shift it into the bitstream in reverse + // (shifting has precedence over bitmasking) + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + // insert charCode bits into bitstream + // Nasty but effective hack: + // loop 8 or 16 times based on token value + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + // shifting has precedence over bitmasking + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Add charCode to the dictionary. + dictionary[c] = node; + + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + nextNode = node.d[c]; + if (nextNode) { + // continue with next prefix + node = nextNode; + } else { + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + value = node.v; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is the new charCode a new character + // that needs to be stored at the root? + if (dictionary[c] == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + dictionary[c] = _node(dictSize++) + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.d[c] = _node(dictSize++) + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // set node to first charCode of new prefix + node = dictionary[c]; + } + } + + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + value = node.v; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + + // Is c a new character? + if (dictionary[c] == undefined) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } + + // Mark the end of the stream + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = 2 >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + + // Flush the last char + data_val <<= bitsPerChar - data_position; + data.push(getCharFromInt(data_val)); + return data; + }, + _decompress = function (length, resetBits, getNextValue) { + var dictionary = ['', '', ''], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } + + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = f(bits); + dictionary[3] = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = f(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } + + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : c + c.charAt(0); + result.push(entry); + // Add c+entry[0] to the dictionary. + dictionary[dictSize++] = c + entry.charAt(0); + + c = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + } + return ""; + }, + _compressToArray = function (uncompressed) { + return _compress(uncompressed, 16, getChar16Bits); + }, + _decompressFromArray = function (compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + }; + + return { + compressToBase64: function (input) { + if (input == null) return ""; + var res = _compress(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } + + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = _compress(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = _compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return _decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return _compress(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return _compressToArray(uncompressed).join(''); + }, + + compressToArray: _compressToArray, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: _decompressFromArray + }; +})(); + if (typeof define === 'function' && define.amd) { - define(function () { return LZString; }); + define(function () { return LZString; }); } else if (typeof module !== 'undefined' && module != null) { - module.exports = LZString + module.exports = LZString } else if (typeof angular !== 'undefined' && angular != null) { - angular.module('LZString', []) - .factory('LZString', function () { - return LZString; - }); + angular.module('LZString', []) + .factory('LZString', function () { + return LZString; + }); } diff --git a/libs/lz-string.min.js b/libs/lz-string.min.js index 2d1900a..0054498 100644 --- a/libs/lz-string.min.js +++ b/libs/lz-string.min.js @@ -1 +1 @@ -var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString); +var LZString=function(){var n=0,r=String.fromCharCode,e={},t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),o=t.concat();for(o[63]="-",o[64]="$";n<65;)n>62&&(e[o[n].charCodeAt(0)]=n),e[t[n].charCodeAt(0)]=n++;var u=function(n){return r(n)},f=function(n){return t[n]},i=function(n){return o[n]},l=function(n){return r(n+32)},c=function(n){return{v:n,d:{}}},s=function(n,r,e){if(null==n)return[];var t,o=0,u=0,f=0,i={},l=!0,s=0,h=c(3),a=1,d=4,p=2,g=[],m=0,A=0;if(n.length){for(f=(s=n.charCodeAt(0))<256?0:1,o=0;o>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(i[s]=h,u=1;u>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);if(void 0==i[s]){for(0==--a&&(a=1<>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);i[s]=c(d++),l=!0}h.d[s]=c(d++),0==--a&&(a=1<>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);if(void 0==i[s]){for(0==--a&&(a=1<>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0)}0==--a&&(a=1<>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);return m<<=r-A,g.push(e(m)),g},h=function(n,e,t){for(var o=["","",""],u=4,f=4,i=3,l="",c=[],s=0,h=2,a=0,d="",p=t(0),g=e,m=1;a!=h;)s+=(p>>--g&1)<>--g&1)<>--g&1)<>--g&1)<o.length)return null;l=s>>8,e[2*t+1]=255&u}return e},decompressFromUint8Array:function(n){return null===n||void 0===n?d(n):0==n.length?null:h(n.length,8,function(r){return n[r]})},compressToEncodedURIComponent:function(n){return null==n?"":s(n,6,i).join("")},decompressFromEncodedURIComponent:function(n){return null==n?"":""==n?null:(n=n.replace(/ /g,"+"),h(n.length,6,function(r){return e[n.charCodeAt(r)]}))},compress:function(n){return a(n).join("")},compressToArray:a,decompress:function(n){return null==n?"":""==n?null:h(n.length,16,function(r){return n.charCodeAt(r)})},decompressFromArray:d}}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); \ No newline at end of file From 108287cfd4d63821e6943b84ff3c7456f5b097d0 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 17 Jan 2018 17:22:44 +0100 Subject: [PATCH 32/39] Update lz-string-unsafe with recent optimisations --- lz-string-unsafe.js | 689 ++++++++++++++++++++++---------------------- 1 file changed, 344 insertions(+), 345 deletions(-) diff --git a/lz-string-unsafe.js b/lz-string-unsafe.js index 8e914e8..22e95a3 100644 --- a/lz-string-unsafe.js +++ b/lz-string-unsafe.js @@ -1,257 +1,109 @@ -var LZStringUnsafe = ( - function () { - - // private property - var f = String.fromCharCode, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - Base64ReverseDic = {}, - UriSafeReverseDic = {}, - i = 65; - while (i--) { - Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; - UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; +var LZStringUnsafe = (function () { + // private property + var i = 0, + f = String.fromCharCode, + reverseDict = {}, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; + while (i < 65) { + if (i > 62) { + reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; } - var getChar16Bits = function (a) { return f(a); }, - getCharFromBase64 = function (a) { return Base64CharArray[a]; }, - getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }; - - var LZString = { - compressToBase64: function (input) { - if (input == null) return ""; - var res = LZString._compressToArray(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while (i--) { - res.push("="); - } - - return res.join(''); - }, - - decompressFromBase64: function (input) { - if (input == null) return ""; - if (input == "") return null; - return LZString._decompress(input.length, 6, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); - }, - - compressToUTF16: function (input) { - if (input == null) return ""; - var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = LZString.compressToArray(uncompressed); - var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character - - for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { - var current_value = compressed[i].charCodeAt(0); - buf[i * 2] = current_value >>> 8; - buf[i * 2 + 1] = current_value & 0xFF; + reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + } + var getChar16Bits = function (a) { return f(a); }, + getCharFromBase64 = function (a) { return Base64CharArray[a]; }, + getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, + getCharFromUTF16 = function (a) { return f(a + 32); }, + _compress = function (uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, k = 0, value = 0, + node = [3], // first node will always be initialised like this. + // we should never output the root anyway, + // so we initiate with terminating token + // Also, dictionary[1] will be overwritten + // by the firs charCode + dictionary = [2, 2, node], + freshNode = true, + c = 0, + nextNode, + enlargeIn = 1, + dictSize = 4, + numBits = 2, + data = [], + data_val = 0, + data_position = 0; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + for (i = 0; i < numBits; i++) { + // Value is 0 (8 bit) or 1 (16 bit). + // We shift it into the bitstream in reverse + // (shifting has precedence over bitmasking) + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array: function (compressed) { - if (compressed === null || compressed === undefined) { - return LZString.decompressFromArray(compressed); - } else if (compressed.length == 0) { - return null; + // insert charCode bits into bitstream + // Nasty but effective hack: + // loop 8 or 16 times based on token value + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + // shifting has precedence over bitmasking + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } } - return LZString._decompress(compressed.length, 8, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent: function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return LZString._decompress(input.length, 6, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return LZString.compressToArray(uncompressed).join(''); - }, - compressToArray: function (uncompressed) { - return LZString._compressToArray(uncompressed, 16, getChar16Bits); - }, - _compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, k = 0, value = 0, - node = [3], // first node will always be initialised like this. - // we should never output the root anyway, - // so we initiate with terminating token - // Also, dictionary[1] will be overwritten - // by the firs charCode - dictionary = [2, 2, node], - freshNode = true, - c = 0, - nextNode, - enlargeIn = 1, - dictSize = 4, - numBits = 2, - data = [], - data_val = 0, - data_position = 0; - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 - - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - for (i = 0; i < numBits; i++) { - // Value is 0 (8 bit) or 1 (16 bit). - // We shift it into the bitstream in reverse - // (shifting has precedence over bitmasking) - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - // insert charCode bits into bitstream - // Nasty but effective hack: - // loop 8 or 16 times based on token value - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - // shifting has precedence over bitmasking - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + // Add charCode to the dictionary. + dictionary[1] = c; + + nextchar: + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + for (k = 1; k < node.length; k += 2) { + if (node[k] == c) { + node = node[k + 1]; + continue nextchar; } } + // we only end up here if there is no matching char - // Add charCode to the dictionary. - dictionary[1] = c; - - nextchar: - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - for (k = 1; k < node.length; k += 2) { - if (node[k] == c) { - node = node[k + 1]; - continue nextchar; - } - } - // we only end up here if there is no matching char - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - - // Is the new charCode a new character - // that needs to be stored at the root? - k = 1; - while (dictionary[k] != c && k < dictionary.length) { - k += 2; - } - if (k == dictionary.length) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - dictionary.push(c); - dictionary.push([dictSize++]); - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.push(c); - node.push([dictSize++]); - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // set node to first charCode of new prefix - // k is guaranteed to be at the current charCode, - // since we either broke out of the while loop - // when it matched, or just added the new charCode - node = dictionary[k + 1]; - - } + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. - // === Write last prefix to output === if (freshNode) { - // character token already written to output + // Prefix is a freshly added character token, + // which was already written to the bitstream freshNode = false; } else { - // write out the prefix token + // write out the current prefix token value = node[0]; for (i = 0; i < numBits; i++) { // shifting has precedence over bitmasking @@ -264,7 +116,8 @@ var LZStringUnsafe = ( } } - // Is c a new character? + // Is the new charCode a new character + // that needs to be stored at the root? k = 1; while (dictionary[k] != c && k < dictionary.length) { k += 2; @@ -274,6 +127,7 @@ var LZStringUnsafe = ( if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } + // insert "new 8/16 bit charCode" token, // see comments above for explanation value = c < 256 ? 0 : 1 @@ -294,77 +148,151 @@ var LZStringUnsafe = ( data_val = 0; } } + dictionary.push(c); + dictionary.push([dictSize++]); + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; } + // add node representing prefix + new charCode to trie + node.push(c); + node.push([dictSize++]); // increase token bitlength if necessary if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } + // set node to first charCode of new prefix + // k is guaranteed to be at the current charCode, + // since we either broke out of the while loop + // when it matched, or just added the new charCode + node = dictionary[k + 1]; + } - // Mark the end of the stream - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = 2 >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + value = node[0]; + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = value >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } } } - // Flush the last char - data_val <<= bitsPerChar - data_position; - data.push(getCharFromInt(data_val)); - return data; - }, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return LZString._decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); - }, - - _decompress: function (length, resetBits, getNextValue) { - var dictionary = [0, 1, 2], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - w = "", - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); + // Is c a new character? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + for (i = 0; i < numBits; i++) { + data_val = value >> i | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } } + value = 8 + 8 * value; + for (i = 0; i < value; i++) { + data_val = c >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; + } + } + } + // increase token bitlength if necessary + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } + + // Mark the end of the stream + for (i = 0; i < numBits; i++) { + // shifting has precedence over bitmasking + data_val = 2 >> i & 1 | data_val << 1; + if (++data_position == bitsPerChar) { + data_position = 0; + data.push(getCharFromInt(data_val)); + data_val = 0; } + } - // if end of stream token, return empty string - if (bits == 2) { - return ""; + // Flush the last char + data_val <<= bitsPerChar - data_position; + data.push(getCharFromInt(data_val)); + return data; + }, + _decompress = function (length, resetBits, getNextValue) { + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + w = "", + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); } + } - // else, get character - maxpower = bits * 8 + 8; + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } + + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = f(bits); + dictionary[3] = c; + w = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; bits = power = 0; while (power != maxpower) { // shifting has precedence over bitmasking @@ -374,15 +302,10 @@ var LZStringUnsafe = ( data_val = getNextValue(data_index++); } } - c = f(bits); - dictionary[3] = c; - w = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; + + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); bits = power = 0; while (power != maxpower) { // shifting has precedence over bitmasking @@ -392,50 +315,126 @@ var LZStringUnsafe = ( data_val = getNextValue(data_index++); } } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); - } - - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); - result.push(entry); - // Add w+entry[0] to the dictionary. - dictionary[dictSize++] = w + entry.charAt(0); - - w = entry; - + dictionary[dictSize] = f(bits); + bits = dictSize++; if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } + if (bits > dictionary.length) { + return null; } - return ""; + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); + result.push(entry); + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); + + w = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } + return ""; + }, + _compressToArray = function (uncompressed) { + return _compress(uncompressed, 16, getChar16Bits); + }, + _decompressFromArray = function (compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); }; - return LZString; - } -)(); + + return { + compressToBase64: function (input) { + if (input == null) return ""; + var res = _compress(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } + + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = _compress(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = _compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return _decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return _compress(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return _compressToArray(uncompressed).join(''); + }, + compressToArray: _compressToArray, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: _decompressFromArray + }; +})(); if (typeof define === 'function' && define.amd) { define(function () { return LZStringUnsafe; }); From a87eb896c55c7bba9064f3dde051bd2834fd9153 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 4 Jul 2018 16:53:56 +0200 Subject: [PATCH 33/39] Added StringStream class --- libs/lz-string.js | 499 +++++++++++++++++++++------------------------- 1 file changed, 225 insertions(+), 274 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 45fe86f..0089b85 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -22,248 +22,217 @@ var LZString = (function () { reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; } - var getChar16Bits = function (a) { return f(a); }, - getCharFromBase64 = function (a) { return Base64CharArray[a]; }, - getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }, - _node = function (val) { return {v: val, d: {} }; }, - _compress = function (uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, value = 0, - dictionary = {}, - freshNode = true, - c = 0, - node = _node(3), // first node will always be initialised like this. - nextNode, - enlargeIn = 1, - dictSize = 4, - numBits = 2, - data = [], - data_val = 0, - data_position = 0; - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 + function StringStream(bitsPerChar, getChar) { + // data + this.d = []; // empty stream + // davaVal + this.v = 0; + // dataPosition + this.p = 0; + this.b = bitsPerChar; + this.g = getChar; + } - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - for (i = 0; i < numBits; i++) { - // Value is 0 (8 bit) or 1 (16 bit). - // We shift it into the bitstream in reverse - // (shifting has precedence over bitmasking) - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - // insert charCode bits into bitstream - // Nasty but effective hack: - // loop 8 or 16 times based on token value - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - // shifting has precedence over bitmasking - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } + StringStream.prototype.s = function (value, numBitsMask) { //streamBits + for (var i = 0; numBitsMask >>= 1; i++) { + // shifting has precedence over bitmasking + this.v = value >> i & 1 | this.v << 1; + if (++this.p === this.b) { + this.p = 0; + this.d.push(this.g(this.v)); + this.v = 0; + } + } + } - // Add charCode to the dictionary. - dictionary[c] = node; + StringStream.prototype.f = function () { // finalise + // Flush the last char + this.v <<= this.b - this.p; + this.d.push(this.g(this.v)); + return this.d; + } - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - nextNode = node.d[c]; - if (nextNode) { - // continue with next prefix - node = nextNode; - } else { + function getChar16Bits(a) { return f(a); } + function getCharFromBase64(a) { return Base64CharArray[a]; } + function getCharFromURISafe(a) { return UriSafeCharArray[a]; } + function getCharFromUTF16(a) { return f(a + 32); } + function _node(val) { return { v: val, d: {} }; } + function _compress(uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, value = 0, + dictionary = {}, + freshNode = true, + c = 0, + node = _node(3), // first node will always be initialised like this. + nextNode, + dictSize = 3, + numBitsMask = 0b100, + stringStream = new StringStream(bitsPerChar, getCharFromInt); + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + stringStream.s(value, numBitsMask); + stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + + // Add charCode to the dictionary. + dictionary[c] = node; + + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + nextNode = node.d[c]; + if (nextNode) { + // continue with next prefix + node = nextNode; + } else { - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - value = node.v; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. - // Is the new charCode a new character - // that needs to be stored at the root? - if (dictionary[c] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - dictionary[c] = _node(dictSize++) - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.d[c] = _node(dictSize++) - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // set node to first charCode of new prefix - node = dictionary[c]; + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + value = node.v; + stringStream.s(value, numBitsMask); } - } - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - value = node.v; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + // Is the new charCode a new character + // that needs to be stored at the root? + if (dictionary[c] == undefined) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; } - } - } - // Is c a new character? - if (dictionary[c] == undefined) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + stringStream.s(value, numBitsMask); + stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + + dictionary[c] = _node(dictSize) + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // add node representing prefix + new charCode to trie + node.d[c] = _node(++dictSize) + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; } - } - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; + + // set node to first charCode of new prefix + node = dictionary[c]; } } - // Mark the end of the stream - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = 2 >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + stringStream.s(node.v, numBitsMask); } - // Flush the last char - data_val <<= bitsPerChar - data_position; - data.push(getCharFromInt(data_val)); - return data; - }, - _decompress = function (length, resetBits, getNextValue) { - var dictionary = ['', '', ''], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); + // Is c a new character? + if (dictionary[c] == undefined) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + stringStream.s(value, numBitsMask); + stringStream.s(c, 0b100000000 << value); } + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + } + + // Mark the end of the stream + stringStream.s(2, numBitsMask); + // Flush the last char + return stringStream.f(); - // if end of stream token, return empty string - if (bits == 2) { - return ""; + } + function _decompress(length, resetBits, getNextValue) { + var dictionary = ['', '', ''], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); } + } - // else, get character - maxpower = bits * 8 + 8; + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } + + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = f(bits); + dictionary[3] = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; bits = power = 0; while (power != maxpower) { // shifting has precedence over bitmasking @@ -273,14 +242,10 @@ var LZString = (function () { data_val = getNextValue(data_index++); } } - c = f(bits); - dictionary[3] = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; + + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); bits = power = 0; while (power != maxpower) { // shifting has precedence over bitmasking @@ -290,54 +255,41 @@ var LZString = (function () { data_val = getNextValue(data_index++); } } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); - } - - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : c + c.charAt(0); - result.push(entry); - // Add c+entry[0] to the dictionary. - dictionary[dictSize++] = c + entry.charAt(0); - - c = entry; - + dictionary[dictSize] = f(bits); + bits = dictSize++; if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } + if (bits > dictionary.length) { + return null; } - return ""; - }, - _compressToArray = function (uncompressed) { - return _compress(uncompressed, 16, getChar16Bits); - }, - _decompressFromArray = function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); - }; + entry = bits < dictionary.length ? dictionary[bits] : c + c.charAt(0); + result.push(entry); + // Add c+entry[0] to the dictionary. + dictionary[dictSize++] = c + entry.charAt(0); + + c = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + + } + return ""; + } + function _compressToArray(uncompressed) { + return _compress(uncompressed, 16, getChar16Bits); + } + function _decompressFromArray(compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + } return { compressToBase64: function (input) { @@ -425,7 +377,6 @@ var LZString = (function () { }; })(); - if (typeof define === 'function' && define.amd) { define(function () { return LZString; }); } else if (typeof module !== 'undefined' && module != null) { @@ -435,4 +386,4 @@ if (typeof define === 'function' && define.amd) { .factory('LZString', function () { return LZString; }); -} +} \ No newline at end of file From e0bafe542a23b13e6f0131ee6b8a484337ca8908 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 4 Jul 2018 16:54:17 +0200 Subject: [PATCH 34/39] Made a Uint8Array-to-UTF16 version (hack, WIP) --- libs/lz-uint8.js | 396 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 libs/lz-uint8.js diff --git a/libs/lz-uint8.js b/libs/lz-uint8.js new file mode 100644 index 0000000..4a58c53 --- /dev/null +++ b/libs/lz-uint8.js @@ -0,0 +1,396 @@ +/** + * Using modern module syntax, because seriously + */ + + + + +var LZu8 = (function () { + + /** + * Integer to (safe) UTF16 character + * + * Basic ranges of printable UTF16 values (as found in LZ-string): + * [32, 127), [160, 55296), [63744, 65536) + * One use of this library is to embed the string into code. For that reason, + * we want to filter out out string characters like: + * " (34) + * ' (39) + * ` (44) + * (Forward tick is safe: ´ (96)) + * So: + * 32, 33, [35, 39), [40, 44), [45, 127), [160, 55296), [63744, 65536) + */ + function itou(i) { + i += 32; + if (i > 33 && i < 39) { + i++; + } else if (i > 38 && i < 44) { + i += 2; + } else if (i > 43 && i < 127) { + i += 3; + } else if (i > 126 && i < 55258) { + i += 37; // === 160 - 128 + 3 + } else if (i > 55295) { + i += 8485; // === 63744 - 55296 + 37 + } + return String.fromCharCode(i); + } + + /** + * UTF16 charCode to integer + */ + function utoi(i) { + return i - (i > 63743 ? 8517 : + i > 159 ? 69 : + i > 46 && i < 130 ? 35 : + i > 40 && i < 46 ? 34 : + i > 34 && i < 40 ? 33 : + 32); + } + + function StringStream(length) { + // data + this.d = length ? [ + // Write length of the input as the first four unicode characters, + // Or 45 bits. 1<<45 bytes is about 35 terabytes, so more than enough. + itou(length / 40000000 & 0x7FFF), + itou((length >>> 15) & 0x7FFF), + itou(length & 0x7FFF), + ] : + []; // empty stream + // davaVal + this.v = 0; + // dataPosition + this.p = 0; + } + + StringStream.prototype.s = function (value, numBitsMask) { //streamBits + for (let i = 0, t = this; numBitsMask>>=1; i++) { + // shifting has precedence over bitmasking + t.v = value >> i & 1 | t.v << 1; + if (++t.p === 15) { + t.p = 0; + t.d.push(itou(t.v)); + t.v = 0; + } + } + } + + StringStream.prototype.f = function () { // finalise + let t = this; + // Flush the last char + t.v <<= 15 - t.p; + t.d.push(itou(t.v)); + t.d.push(' '); + return t.d.join(''); + } + /* + function Uint8Stream(length){ + // data + this.d = length ? [ + // Write length of the input as the first four bytes, + // Or 45 bits. 1<<45 bytes is about 35 terabytes, so more than enough. + length >>> 24, + (length >>> 16) & 255, + (length >>> 8) & 255, + length & 255, + ] : + []; // empty stream + // davaVal + this.v = 0; + // dataPosition + this.p = 0; + } + Uint8Stream.prototype.s = function(value, numBitsMask){ + for (let i = 0, t=this; numBitsMask>>i; i++) { + // shifting has precedence over bitmasking + t.v = value >> i & 1 | t.v << 1; + if (++t.p === 8) { + t.p = 0; + t.d.push(t.v); + t.v = 0; + } + } + } + Uint8Stream.prototype.f = function(){ + let t = this; + // Flush the last char + t.v <<= 8 - t.p; + t.d.push(t.v); + t.d.push(0); + return Uint8Array.from(t.d); + } + */ + + var _node = function (val) { return { v: val, d: {} }; } + return { + itou, + utoi, + compress: function (input) { + if (input === null) return ''; + let i = 0, + j = 0, + freshNode = true, + c = 0, + node = _node(2), // first node will always be initialised like this. + nextNode, + numBitsMask = 0b100, + dictionary = _node(0), + dictSize = 2, + stringStream = new StringStream(input.length); + + if (input.length) { + + // If there is an array, the first byte is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = input[0]; + + // === Write first byte token to output == + + // insert new byte token into bitstream + stringStream.s(0, numBitsMask); + // insert byte bits into bitstream + stringStream.s(c, 0b100000000); + + // Add charCode to the dictionary. + dictionary[c] = node; + + for (j = 1; j < input.length; j++) { + c = input[j]; + // does the new charCode match an existing prefix? + nextNode = node.d[c]; + if (nextNode) { + // continue with next prefix + node = nextNode; + } else { + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + stringStream.s(node.v, numBitsMask); + } + + // Is the byte a new byte + // that needs to be stored at the root? + if (dictionary[c] === undefined) { + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + + // insert new byte token into bitstream + stringStream.s(0, numBitsMask); + // insert byte bits into bitstream + stringStream.s(c, 0b100000000); + + dictionary[c] = _node(++dictSize); + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.d[c] = _node(++dictSize); + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // set node to first charCode of new prefix + node = dictionary[c]; + } + } + + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + stringStream.s(node.v, numBitsMask); + } + + // Is c a new character? + if (dictionary[c] === undefined) { + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // insert new byte token into bitstream + stringStream.s(0, numBitsMask); + // insert byte bits into bitstream + stringStream.s(c, 0b100000000); + + } + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + } + + // Mark the end of the stream + stringStream.s(1, numBitsMask); + + // Flush the last char + return stringStream.f(); + + }, + + decompress: function (compressed) { + if (compressed === null || compressed.length < 4) return null; + + let length = compressed.length, + getNextValue = function (index) { return utoi(compressed.charCodeAt(index)); }; + let dictionary = [0, 1], + enlargeIn = 1, + dictSize = 3, + numBits = 2, + bytes = null, + bytes_concat = null, + result = new Uint8Array( + getNextValue(0) * 0x40000000 + + (getNextValue(1) << 15) + + getNextValue(2)), + result_index = 0, + bits = 0, + maxPower = 2, + power = 0, + data_val = getNextValue(3), + data_position = 15, + data_index = 4; + + // Get first token, guaranteed to be either + // a new byte token or end of stream token. + while (power < maxPower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position === 0) { + data_position = 15; + data_val = getNextValue(data_index++); + } + } + + if (bits === 1) { + return null; + } + + // else, get byte value + bits = power = 0; + while (power < 8) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position === 0) { + data_position = 15; + data_val = getNextValue(data_index++); + } + } + bytes = [bits]; + dictionary[2] = bytes; + result[result_index++] = bits; + + // read rest of string + while (data_index <= length) { + // read out next token + maxPower = numBits; + bits = power = 0; + while (power < maxPower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position === 0) { + data_position = 15; + data_val = getNextValue(data_index++); + } + } + + // 0 implies new byte + if (!bits) { + bits = power = 0; + while (power < 8) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position === 0) { + data_position = 15; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = [bits]; + bits = dictSize++; + if (--enlargeIn === 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits === 1) { + // end of stream token + return result; + } + + if (bits > dictionary.length) { + return null; + } + bytes_concat = bits < dictionary.length ? dictionary[bits] : bytes.concat(bytes[0]); + for (let i = 0; i < bytes_concat.length; i++) { + result[result_index++] = bytes_concat[i]; + } + dictionary[dictSize++] = bytes.concat(bytes_concat[0]); + bytes = bytes_concat; + + if (--enlargeIn === 0) { + enlargeIn = 1 << numBits++; + } + + } + return null; + }, + + compressToUint8Array: function () { }, + decompressFromUint8Array: function () { }, + }; +})(); + +function testCompression(LZ) { + console.log('Testing utoi/itou functions'); + let utoiMismatches = []; + for (let i = 0; i < 1 << 15; i++) { + let j = LZ.utoi(LZ.itou(i).charCodeAt(0)); + if (i !== j) { + utoiMismatches.push({ itou: i, utio: j }); + } + } + + if (utoiMismatches.length) { + console.log("Errors in itou/utoi conversion detected:", utoiMismatches); + } else { + console.log('No errors in itou/utoi conversion detected'); + } + + let input = new Uint16Array(1 << 15); + for (let i = 0; i < input.length; i++) { + input[i] = i & 256; + } + let inputUint8 = new Uint8Array(input.buffer); + let compressed = LZ.compress(inputUint8); + let decompressed = new Uint16Array(LZ.decompress(compressed).buffer); + let mismatches = []; + for (let i = 0; i < input.length; i++) { + if (input[i] !== decompressed[i]) { + mismatches.push({ index: i, input: input[i], decompressed: decompressed[i] }); + } + } + console.log({ + compressed, + mismatches, + length: compressed.length, + inputLength: input.length, + }); +} From 2911de855c869383756f992d46b19bba2aae050c Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Thu, 5 Jul 2018 02:57:10 +0200 Subject: [PATCH 35/39] Cloned test for lz-string-unsafe.js --- tests/SpecRunner-unsafe.html | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/SpecRunner-unsafe.html diff --git a/tests/SpecRunner-unsafe.html b/tests/SpecRunner-unsafe.html new file mode 100644 index 0000000..da23a80 --- /dev/null +++ b/tests/SpecRunner-unsafe.html @@ -0,0 +1,54 @@ + + + + LZString tests + + + + + + + + + + + + + + + + + + + + From ff26e406c8da7d92ed49a7e7c0b0d51bfcc13abf Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Thu, 5 Jul 2018 02:58:46 +0200 Subject: [PATCH 36/39] getChar16Bits = fromCharCode The functions are equivalent, so we might as well remove the indirection --- libs/lz-string.js | 11 +++++------ libs/lz-string.min.js | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 0089b85..58060c1 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -10,7 +10,7 @@ var LZString = (function () { // private property var i = 0, - f = String.fromCharCode, + fromCharCode = String.fromCharCode, reverseDict = {}, Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), @@ -52,10 +52,9 @@ var LZString = (function () { return this.d; } - function getChar16Bits(a) { return f(a); } function getCharFromBase64(a) { return Base64CharArray[a]; } function getCharFromURISafe(a) { return UriSafeCharArray[a]; } - function getCharFromUTF16(a) { return f(a + 32); } + function getCharFromUTF16(a) { return fromCharCode(a + 32); } function _node(val) { return { v: val, d: {} }; } function _compress(uncompressed, bitsPerChar, getCharFromInt) { if (uncompressed == null) return []; @@ -225,7 +224,7 @@ var LZString = (function () { data_val = getNextValue(data_index++); } } - c = f(bits); + c = fromCharCode(bits); dictionary[3] = c; result.push(c); @@ -255,7 +254,7 @@ var LZString = (function () { data_val = getNextValue(data_index++); } } - dictionary[dictSize] = f(bits); + dictionary[dictSize] = fromCharCode(bits); bits = dictSize++; if (--enlargeIn == 0) { enlargeIn = 1 << numBits++; @@ -283,7 +282,7 @@ var LZString = (function () { return ""; } function _compressToArray(uncompressed) { - return _compress(uncompressed, 16, getChar16Bits); + return _compress(uncompressed, 16, fromCharCode); } function _decompressFromArray(compressed) { if (compressed == null) return ""; diff --git a/libs/lz-string.min.js b/libs/lz-string.min.js index 0054498..c44591d 100644 --- a/libs/lz-string.min.js +++ b/libs/lz-string.min.js @@ -1 +1 @@ -var LZString=function(){var n=0,r=String.fromCharCode,e={},t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),o=t.concat();for(o[63]="-",o[64]="$";n<65;)n>62&&(e[o[n].charCodeAt(0)]=n),e[t[n].charCodeAt(0)]=n++;var u=function(n){return r(n)},f=function(n){return t[n]},i=function(n){return o[n]},l=function(n){return r(n+32)},c=function(n){return{v:n,d:{}}},s=function(n,r,e){if(null==n)return[];var t,o=0,u=0,f=0,i={},l=!0,s=0,h=c(3),a=1,d=4,p=2,g=[],m=0,A=0;if(n.length){for(f=(s=n.charCodeAt(0))<256?0:1,o=0;o>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(i[s]=h,u=1;u>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);if(void 0==i[s]){for(0==--a&&(a=1<>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);i[s]=c(d++),l=!0}h.d[s]=c(d++),0==--a&&(a=1<>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);if(void 0==i[s]){for(0==--a&&(a=1<>o|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);for(f=8+8*f,o=0;o>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0)}0==--a&&(a=1<>o&1|m<<1,++A==r&&(A=0,g.push(e(m)),m=0);return m<<=r-A,g.push(e(m)),g},h=function(n,e,t){for(var o=["","",""],u=4,f=4,i=3,l="",c=[],s=0,h=2,a=0,d="",p=t(0),g=e,m=1;a!=h;)s+=(p>>--g&1)<>--g&1)<>--g&1)<>--g&1)<o.length)return null;l=s>>8,e[2*t+1]=255&u}return e},decompressFromUint8Array:function(n){return null===n||void 0===n?d(n):0==n.length?null:h(n.length,8,function(r){return n[r]})},compressToEncodedURIComponent:function(n){return null==n?"":s(n,6,i).join("")},decompressFromEncodedURIComponent:function(n){return null==n?"":""==n?null:(n=n.replace(/ /g,"+"),h(n.length,6,function(r){return e[n.charCodeAt(r)]}))},compress:function(n){return a(n).join("")},compressToArray:a,decompress:function(n){return null==n?"":""==n?null:h(n.length,16,function(r){return n.charCodeAt(r)})},decompressFromArray:d}}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); \ No newline at end of file +var LZString=function(){function b(s,t){this.d=[],this.v=0,this.p=0,this.b=s,this.g=t}function d(s){return q[s]}function e(s){return r[s]}function f(s){return o(s+32)}function g(s){return{v:s,d:{}}}function h(s,t,u){if(null==s)return[];var C,w=0,x=0,y={},z=!0,A=0,B=g(3),D=3,E=4,F=new b(t,u);if(s.length){for(A=s.charCodeAt(0),x=256>A?0:1,F.s(x,E),F.s(A,x?65536:256),y[A]=B,w=1;w=E&&(E<<=1),x=256>A?0:1,F.s(x,E),F.s(A,x?65536:256),y[A]=g(D),z=!0),B.d[A]=g(++D),D>=E&&(E<<=1),B=y[A]);z?z=!1:F.s(B.v,E),void 0==y[A]&&(++D>=E&&(E<<=1),x=256>A?0:1,F.s(x,E),F.s(A,256<=E&&(E<<=1)}return F.s(2,E),F.f()}function k(s,t,u){for(var v=['','',''],w=4,x=4,y=3,z='',A=[],B=0,C=2,D=0,E='',F=u(0),G=t,H=1;D!=C;)B+=(1&F>>--G)<>--G)<>--G)<B){for(C=8+8*B,B=D=0;D!=C;)B+=(1&F>>--G)<v.length)return null;z=Bn;)62>=1;u++)this.v=1&s>>u|this.v<<1,++this.p===this.b&&(this.p=0,this.d.push(this.g(this.v)),this.v=0)},b.prototype.f=function(){return this.v<<=this.b-this.p,this.d.push(this.g(this.v)),this.d},{compressToBase64:function(s){if(null==s)return'';for(var t=h(s,6,d),u=t.length%4;u--;)t.push('=');return t.join('')},decompressFromBase64:function(s){return null==s?'':''==s?null:k(s.length,6,function(t){return p[s.charCodeAt(t)]})},compressToUTF16:function(s){if(null==s)return'';var t=h(s,15,f);return t.push(' '),t.join('')},decompressFromUTF16:function(s){return null==s?'':''==s?null:k(s.length,15,function(t){return s.charCodeAt(t)-32})},compressToUint8Array:function(s){for(var x,t=l(s),u=new Uint8Array(2*t.length),v=0,w=t.length;v>>8,u[2*v+1]=255&x;return u},decompressFromUint8Array:function(s){return null===s||void 0===s?m(s):0==s.length?null:k(s.length,8,function(t){return s[t]})},compressToEncodedURIComponent:function(s){return null==s?'':h(s,6,e).join('')},decompressFromEncodedURIComponent:function(s){return null==s?'':''==s?null:(s=s.replace(/ /g,'+'),k(s.length,6,function(t){return p[s.charCodeAt(t)]}))},compress:function(s){return l(s).join('')},compressToArray:l,decompress:function(s){return null==s?'':''==s?null:k(s.length,16,function(t){return s.charCodeAt(t)})},decompressFromArray:m}}();'function'==typeof define&&define.amd?define(function(){return LZString}):'undefined'!=typeof module&&null!=module?module.exports=LZString:'undefined'!=typeof angular&&null!=angular&&angular.module('LZString',[]).factory('LZString',function(){return LZString}); \ No newline at end of file From 5722681bb732b0deb924804fce16cc915a013858 Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Thu, 5 Jul 2018 03:01:30 +0200 Subject: [PATCH 37/39] Add StringStream to lz-string-usafe --- lz-string-unsafe.js | 770 +++++++++++++++++++--------------------- lz-string-unsafe.min.js | 1 + 2 files changed, 360 insertions(+), 411 deletions(-) create mode 100644 lz-string-unsafe.min.js diff --git a/lz-string-unsafe.js b/lz-string-unsafe.js index 22e95a3..36ba25e 100644 --- a/lz-string-unsafe.js +++ b/lz-string-unsafe.js @@ -1,448 +1,396 @@ - var LZStringUnsafe = (function () { - // private property - var i = 0, - f = String.fromCharCode, - reverseDict = {}, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; - while (i < 65) { - if (i > 62) { - reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; - } - reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; - } - var getChar16Bits = function (a) { return f(a); }, - getCharFromBase64 = function (a) { return Base64CharArray[a]; }, - getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, - getCharFromUTF16 = function (a) { return f(a + 32); }, - _compress = function (uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, k = 0, value = 0, - node = [3], // first node will always be initialised like this. - // we should never output the root anyway, - // so we initiate with terminating token - // Also, dictionary[1] will be overwritten - // by the firs charCode - dictionary = [2, 2, node], - freshNode = true, - c = 0, - nextNode, - enlargeIn = 1, - dictSize = 4, - numBits = 2, - data = [], - data_val = 0, - data_position = 0; - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 - - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - for (i = 0; i < numBits; i++) { - // Value is 0 (8 bit) or 1 (16 bit). - // We shift it into the bitstream in reverse - // (shifting has precedence over bitmasking) - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - // insert charCode bits into bitstream - // Nasty but effective hack: - // loop 8 or 16 times based on token value - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - // shifting has precedence over bitmasking - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // private property + var i = 0, + reverseDict = {}, + fromCharCode = String.fromCharCode, + Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), + //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), + UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; + while (i < 65) { + if (i > 62) { + reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; } + reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + } - // Add charCode to the dictionary. - dictionary[1] = c; - - nextchar: - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - for (k = 1; k < node.length; k += 2) { - if (node[k] == c) { - node = node[k + 1]; - continue nextchar; + function StringStream(bitsPerChar, getChar) { + // data + this.d = []; // empty stream + // davaVal + this.v = 0; + // dataPosition + this.p = 0; + this.b = bitsPerChar; + this.g = getChar; + } + + StringStream.prototype.s = function (value, numBitsMask) { //streamBits + for (var i = 0; numBitsMask >>= 1; i++) { + // shifting has precedence over bitmasking + this.v = value >> i & 1 | this.v << 1; + if (++this.p === this.b) { + this.d.push(this.g(this.v)); + this.v = 0; + this.p = 0; } - } - // we only end up here if there is no matching char - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + } + } + + StringStream.prototype.f = function () { // finalise + // Flush the last char + this.d.push(this.g(this.v << this.b - this.p)); + return this.d; + } + + function getCharFromBase64(a) { return Base64CharArray[a]; } + function getCharFromURISafe(a) { return UriSafeCharArray[a]; } + function getCharFromUTF16(a) { return fromCharCode(a + 32); } + function _compress(uncompressed, bitsPerChar, getCharFromInt) { + if (uncompressed == null) return []; + var i = 0, j = 0, k = 0, value = 0, + node = [3], // first node will always be initialised like this. + // we should never output the root anyway, + // so we initiate with terminating token + // Also, dictionary[1] will be overwritten + // by the first charCode + dictionary = [2, 2, node], + freshNode = true, + c = 0, + dictSize = 3, + numBitsMask = 0b100, + stringStream = new StringStream(bitsPerChar, getCharFromInt); + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + stringStream.s(value, numBitsMask); + stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + + // Add charCode to the dictionary. + dictionary[1] = c; + + nextchar: + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + for (k = 1; k < node.length; k += 2) { + if (node[k] == c) { + node = node[k + 1]; + continue nextchar; + } + } + // we only end up here if there is no matching char + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + stringStream.s(node[0], numBitsMask); + } + + // Is the new charCode a new character + // that needs to be stored at the root? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + stringStream.s(value, numBitsMask); + stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + + dictionary.push(c); + dictionary.push([dictSize]); + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.push(c); + node.push([++dictSize]); + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // set node to first charCode of new prefix + // k is guaranteed to be at the current charCode, + // since we either broke out of the while loop + // when it matched, or just added the new charCode + node = dictionary[k + 1]; + } - } - - // Is the new charCode a new character - // that needs to be stored at the root? - k = 1; - while (dictionary[k] != c && k < dictionary.length) { - k += 2; - } - if (k == dictionary.length) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; + + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + stringStream.s(node[0], numBitsMask); + } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } + // Is c a new character? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + if (k == dictionary.length) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + stringStream.s(value, numBitsMask); + stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + } + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; } } - dictionary.push(c); - dictionary.push([dictSize++]); - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.push(c); - node.push([dictSize++]); - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // set node to first charCode of new prefix - // k is guaranteed to be at the current charCode, - // since we either broke out of the while loop - // when it matched, or just added the new charCode - node = dictionary[k + 1]; - } + // Mark the end of the stream + stringStream.s(2, numBitsMask); + - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - value = node[0]; - for (i = 0; i < numBits; i++) { + // Flush the last char + return stringStream.f(); + } + + function _decompress(length, resetBits, getNextValue) { + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + w = "", + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { // shifting has precedence over bitmasking - data_val = value >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); } - } } - // Is c a new character? - k = 1; - while (dictionary[k] != c && k < dictionary.length) { - k += 2; - } - if (k == dictionary.length) { - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - for (i = 0; i < numBits; i++) { - data_val = value >> i | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - value = 8 + 8 * value; - for (i = 0; i < value; i++) { - data_val = c >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - } - // increase token bitlength if necessary - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } - - // Mark the end of the stream - for (i = 0; i < numBits; i++) { - // shifting has precedence over bitmasking - data_val = 2 >> i & 1 | data_val << 1; - if (++data_position == bitsPerChar) { - data_position = 0; - data.push(getCharFromInt(data_val)); - data_val = 0; - } - } - - // Flush the last char - data_val <<= bitsPerChar - data_position; - data.push(getCharFromInt(data_val)); - return data; - }, - _decompress = function (length, resetBits, getNextValue) { - var dictionary = [0, 1, 2], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - w = "", - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); + // if end of stream token, return empty string + if (bits == 2) { + return ""; } - } - // if end of stream token, return empty string - if (bits == 2) { - return ""; - } - - // else, get character - maxpower = bits * 8 + 8; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - c = f(bits); - dictionary[3] = c; - w = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; + // else, get character + maxpower = bits * 8 + 8; bits = power = 0; while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { // shifting has precedence over bitmasking bits += (data_val >> --data_position & 1) << power++; if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); + data_position = resetBits; + data_val = getNextValue(data_index++); } - } - dictionary[dictSize] = f(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); } + c = fromCharCode(bits); + dictionary[3] = c; + w = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); - result.push(entry); - // Add w+entry[0] to the dictionary. - dictionary[dictSize++] = w + entry.charAt(0); + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + dictionary[dictSize] = fromCharCode(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } - w = entry; + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); + result.push(entry); + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); + + w = entry; + + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; } + return ""; + } + function _compressToArray(uncompressed) { + return _compress(uncompressed, 16, fromCharCode); + } + function _decompressFromArray(compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + } - } - return ""; - }, - _compressToArray = function (uncompressed) { - return _compress(uncompressed, 16, getChar16Bits); - }, - _decompressFromArray = function (compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); - }; + return { + compressToBase64: function (input) { + if (input == null) return ""; + var res = _compress(input, 6, getCharFromBase64); + // To produce valid Base64 + var i = res.length % 4; + while (i--) { + res.push("="); + } - return { - compressToBase64: function (input) { - if (input == null) return ""; - var res = _compress(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while (i--) { - res.push("="); - } - - return res.join(''); - }, - - decompressFromBase64: function (input) { - if (input == null) return ""; - if (input == "") return null; - return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compressToUTF16: function (input) { - if (input == null) return ""; - var compressed = _compress(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = _compressToArray(uncompressed); - var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character - - for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { - var current_value = compressed[i].charCodeAt(0); - buf[i * 2] = current_value >>> 8; - buf[i * 2 + 1] = current_value & 0xFF; - } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array: function (compressed) { - if (compressed === null || compressed === undefined) { - return _decompressFromArray(compressed); - } else if (compressed.length == 0) { - return null; - } - return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return _compress(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent: function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return _compressToArray(uncompressed).join(''); - }, - compressToArray: _compressToArray, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: _decompressFromArray - }; + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = _compress(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = _compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return _decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return _compress(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return _compressToArray(uncompressed).join(''); + }, + compressToArray: _compressToArray, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: _decompressFromArray + }; })(); if (typeof define === 'function' && define.amd) { - define(function () { return LZStringUnsafe; }); + define(function () { return LZStringUnsafe; }); } else if (typeof module !== 'undefined' && module != null) { - module.exports = LZStringUnsafe + module.exports = LZStringUnsafe } else if (typeof angular !== 'undefined' && angular != null) { - angular.module('LZStringUnsafe', []) - .factory('LZStringUnsafe', function () { - return LZStringUnsafe; - }); + angular.module('LZStringUnsafe', []) + .factory('LZStringUnsafe', function () { + return LZStringUnsafe; + }); } diff --git a/lz-string-unsafe.min.js b/lz-string-unsafe.min.js new file mode 100644 index 0000000..02f819b --- /dev/null +++ b/lz-string-unsafe.min.js @@ -0,0 +1 @@ +var LZStringUnsafe=function(){function b(s,t){this.d=[],this.v=0,this.p=0,this.b=s,this.g=t}function d(s){return q[s]}function e(s){return r[s]}function f(s){return p(s+32)}function g(s,t,u){if(null==s)return[];var x=0,y=0,z=0,A=[3],B=[2,2,A],C=!0,D=0,E=3,F=4,G=new b(t,u);if(s.length){D=s.charCodeAt(0),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256),B[1]=D;nextchar:for(x=1;x=F&&(F<<=1),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256),B.push(D),B.push([E]),C=!0),A.push(D),A.push([++E]),E>=F&&(F<<=1),A=B[y+1]}for(C?C=!1:G.s(A[0],F),y=1;B[y]!=D&&y=F&&(F<<=1),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256)),++E>=F&&(F<<=1)}return G.s(2,F),G.f()}function h(s,t,u){for(var v=[0,1,2],x=4,y=4,z=3,A='',B=[],C='',D=0,E=2,F=0,G='',H=u(0),I=t,J=1;F!=E;)D+=(1&H>>--I)<>--I)<>--I)<D){for(E=8+8*D,D=F=0;F!=E;)D+=(1&H>>--I)<v.length)return null;A=Dn;)62>=1;u++)this.v=1&s>>u|this.v<<1,++this.p===this.b&&(this.d.push(this.g(this.v)),this.v=0,this.p=0)},b.prototype.f=function(){return this.d.push(this.g(this.v<>>8,u[2*v+1]=255&y;return u},decompressFromUint8Array:function(s){return null===s||void 0===s?m(s):0==s.length?null:h(s.length,8,function(t){return s[t]})},compressToEncodedURIComponent:function(s){return null==s?'':g(s,6,e).join('')},decompressFromEncodedURIComponent:function(s){return null==s?'':''==s?null:(s=s.replace(/ /g,'+'),h(s.length,6,function(t){return o[s.charCodeAt(t)]}))},compress:function(s){return l(s).join('')},compressToArray:l,decompress:function(s){return null==s?'':''==s?null:h(s.length,16,function(t){return s.charCodeAt(t)})},decompressFromArray:m}}();'function'==typeof define&&define.amd?define(function(){return LZStringUnsafe}):'undefined'!=typeof module&&null!=module?module.exports=LZStringUnsafe:'undefined'!=typeof angular&&null!=angular&&angular.module('LZStringUnsafe',[]).factory('LZStringUnsafe',function(){return LZStringUnsafe}); \ No newline at end of file From 3ac31a68981251c35147fdb0640b1c1feece6c23 Mon Sep 17 00:00:00 2001 From: gloryknight Date: Wed, 11 Jul 2018 00:15:14 +0200 Subject: [PATCH 38/39] Rewrite for smaller size and better speed. Replace StringStream object with global variables. Optimize arrays generation. About 5% smaller minified file. About twice faster in compressing large files. --- libs/lz-string.js | 76 ++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 58060c1..216086e 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -6,15 +6,20 @@ // For more information, the home page: // http://pieroxy.net/blog/pages/lz-string/testing.html // -// LZ-based compression algorithm, version 1.4.4 +// LZ-based compression algorithm, version 1.4.5 var LZString = (function () { // private property var i = 0, + StringStream_d, + StringStream_v, + StringStream_p, + StringStream_b, + StringStream_g, fromCharCode = String.fromCharCode, reverseDict = {}, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; + base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+", + Base64CharArray = (base+"/=").split(''), + UriSafeCharArray = (base+"-$").split(''); while (i < 65) { if (i > 62) { reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; @@ -22,36 +27,18 @@ var LZString = (function () { reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; } - function StringStream(bitsPerChar, getChar) { - // data - this.d = []; // empty stream - // davaVal - this.v = 0; - // dataPosition - this.p = 0; - this.b = bitsPerChar; - this.g = getChar; - } - - StringStream.prototype.s = function (value, numBitsMask) { //streamBits + function StringStream_s(value, numBitsMask) { //streamBits for (var i = 0; numBitsMask >>= 1; i++) { // shifting has precedence over bitmasking - this.v = value >> i & 1 | this.v << 1; - if (++this.p === this.b) { - this.p = 0; - this.d.push(this.g(this.v)); - this.v = 0; + StringStream_v = value >> i & 1 | StringStream_v << 1; + if (++StringStream_p === StringStream_b) { + StringStream_p = 0; + StringStream_d.push(StringStream_g(StringStream_v)); + StringStream_v = 0; } } } - StringStream.prototype.f = function () { // finalise - // Flush the last char - this.v <<= this.b - this.p; - this.d.push(this.g(this.v)); - return this.d; - } - function getCharFromBase64(a) { return Base64CharArray[a]; } function getCharFromURISafe(a) { return UriSafeCharArray[a]; } function getCharFromUTF16(a) { return fromCharCode(a + 32); } @@ -66,7 +53,14 @@ var LZString = (function () { nextNode, dictSize = 3, numBitsMask = 0b100, - stringStream = new StringStream(bitsPerChar, getCharFromInt); + // davaVal + StringStream_v = 0; + // dataPosition + StringStream_p = 0; + StringStream_b = bitsPerChar; + StringStream_g = getCharFromInt; + // data + StringStream_d = []; // empty stream if (uncompressed.length) { // If there is a string, the first charCode is guaranteed to @@ -84,8 +78,8 @@ var LZString = (function () { // insert "new 8/16 bit charCode" token // into bitstream (value 1) - stringStream.s(value, numBitsMask); - stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + StringStream_s(value, numBitsMask); + StringStream_s(c, value ? 0b10000000000000000 : 0b100000000); // Add charCode to the dictionary. dictionary[c] = node; @@ -112,7 +106,7 @@ var LZString = (function () { } else { // write out the current prefix token value = node.v; - stringStream.s(value, numBitsMask); + StringStream_s(value, numBitsMask); } // Is the new charCode a new character @@ -127,8 +121,8 @@ var LZString = (function () { // insert "new 8/16 bit charCode" token, // see comments above for explanation value = c < 256 ? 0 : 1 - stringStream.s(value, numBitsMask); - stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); + StringStream_s(value, numBitsMask); + StringStream_s(c, value ? 0b10000000000000000 : 0b100000000); dictionary[c] = _node(dictSize) // Note of that we already wrote @@ -153,7 +147,7 @@ var LZString = (function () { freshNode = false; } else { // write out the prefix token - stringStream.s(node.v, numBitsMask); + StringStream_s(node.v, numBitsMask); } // Is c a new character? @@ -166,8 +160,8 @@ var LZString = (function () { // insert "new 8/16 bit charCode" token, // see comments above for explanation value = c < 256 ? 0 : 1 - stringStream.s(value, numBitsMask); - stringStream.s(c, 0b100000000 << value); + StringStream_s(value, numBitsMask); + StringStream_s(c, 0b100000000 << value); } // increase token bitlength if necessary if (++dictSize >= numBitsMask) { @@ -176,9 +170,11 @@ var LZString = (function () { } // Mark the end of the stream - stringStream.s(2, numBitsMask); + StringStream_s(2, numBitsMask); // Flush the last char - return stringStream.f(); + StringStream_v <<= StringStream_b - StringStream_p; + StringStream_d.push(StringStream_g(StringStream_v)); + return StringStream_d; } function _decompress(length, resetBits, getNextValue) { @@ -385,4 +381,4 @@ if (typeof define === 'function' && define.amd) { .factory('LZString', function () { return LZString; }); -} \ No newline at end of file +} From ba8988028d78962ebadee42cc6d4b17581605e2d Mon Sep 17 00:00:00 2001 From: Job van der Zwan Date: Wed, 11 Jul 2018 15:17:39 +0200 Subject: [PATCH 39/39] Add stringStream changes to unsafe version --- libs/lz-string.js | 251 +++++++------- libs/lz-string.min.js | 2 +- lz-string-unsafe.js | 726 ++++++++++++++++++++-------------------- lz-string-unsafe.min.js | 2 +- 4 files changed, 490 insertions(+), 491 deletions(-) diff --git a/libs/lz-string.js b/libs/lz-string.js index 216086e..7697d31 100644 --- a/libs/lz-string.js +++ b/libs/lz-string.js @@ -10,16 +10,16 @@ var LZString = (function () { // private property var i = 0, - StringStream_d, - StringStream_v, - StringStream_p, - StringStream_b, - StringStream_g, fromCharCode = String.fromCharCode, + streamData, + streamDataVal, + streamDataPosition, + streamBitsPerChar, + streamGetCharFromInt, reverseDict = {}, base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+", - Base64CharArray = (base+"/=").split(''), - UriSafeCharArray = (base+"-$").split(''); + Base64CharArray = (base + "/=").split(''), + UriSafeCharArray = (base + "-$").split(''); while (i < 65) { if (i > 62) { reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; @@ -27,14 +27,14 @@ var LZString = (function () { reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; } - function StringStream_s(value, numBitsMask) { //streamBits + function streamBits(value, numBitsMask) { for (var i = 0; numBitsMask >>= 1; i++) { // shifting has precedence over bitmasking - StringStream_v = value >> i & 1 | StringStream_v << 1; - if (++StringStream_p === StringStream_b) { - StringStream_p = 0; - StringStream_d.push(StringStream_g(StringStream_v)); - StringStream_v = 0; + streamDataVal = value >> i & 1 | streamDataVal << 1; + if (++streamDataPosition === streamBitsPerChar) { + streamDataPosition = 0; + streamData.push(streamGetCharFromInt(streamDataVal)); + streamDataVal = 0; } } } @@ -44,137 +44,139 @@ var LZString = (function () { function getCharFromUTF16(a) { return fromCharCode(a + 32); } function _node(val) { return { v: val, d: {} }; } function _compress(uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, value = 0, - dictionary = {}, - freshNode = true, - c = 0, - node = _node(3), // first node will always be initialised like this. - nextNode, - dictSize = 3, - numBitsMask = 0b100, + // data - empty stream + streamData = []; + + if (uncompressed != null) { // davaVal - StringStream_v = 0; + streamDataVal = 0; // dataPosition - StringStream_p = 0; - StringStream_b = bitsPerChar; - StringStream_g = getCharFromInt; - // data - StringStream_d = []; // empty stream - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? - value = c < 256 ? 0 : 1 - - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - StringStream_s(value, numBitsMask); - StringStream_s(c, value ? 0b10000000000000000 : 0b100000000); - - // Add charCode to the dictionary. - dictionary[c] = node; - - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - nextNode = node.d[c]; - if (nextNode) { - // continue with next prefix - node = nextNode; - } else { - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. + streamDataPosition = 0; + streamBitsPerChar = bitsPerChar; + streamGetCharFromInt = getCharFromInt; + var j = 0, value = 0, + dictionary = {}, + freshNode = true, + c = 0, + node = _node(3), // first node will always be initialised like this. + nextNode, + dictSize = 3, + numBitsMask = 0b100; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + streamBits(value, numBitsMask); + streamBits(c, value ? 0b10000000000000000 : 0b100000000); + + // Add charCode to the dictionary. + dictionary[c] = node; + + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + nextNode = node.d[c]; + if (nextNode) { + // continue with next prefix + node = nextNode; } else { - // write out the current prefix token - value = node.v; - StringStream_s(value, numBitsMask); - } - // Is the new charCode a new character - // that needs to be stored at the root? - if (dictionary[c] == undefined) { + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set node to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + value = node.v; + streamBits(value, numBitsMask); + } + + // Is the new charCode a new character + // that needs to be stored at the root? + if (dictionary[c] == undefined) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + + + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + streamBits(value, numBitsMask); + streamBits(c, value ? 0b10000000000000000 : 0b100000000); + + dictionary[c] = _node(dictSize) + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.d[c] = _node(++dictSize) // increase token bitlength if necessary - if (++dictSize >= numBitsMask) { + if (dictSize >= numBitsMask) { numBitsMask <<= 1; } + // set node to first charCode of new prefix + node = dictionary[c]; + } + } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - StringStream_s(value, numBitsMask); - StringStream_s(c, value ? 0b10000000000000000 : 0b100000000); + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + streamBits(node.v, numBitsMask); + } - dictionary[c] = _node(dictSize) - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.d[c] = _node(++dictSize) + // Is c a new character? + if (dictionary[c] == undefined) { // increase token bitlength if necessary - if (dictSize >= numBitsMask) { + if (++dictSize >= numBitsMask) { numBitsMask <<= 1; } - // set node to first charCode of new prefix - node = dictionary[c]; + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + streamBits(value, numBitsMask); + streamBits(c, 0b100000000 << value); } - } - - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - StringStream_s(node.v, numBitsMask); - } - - // Is c a new character? - if (dictionary[c] == undefined) { // increase token bitlength if necessary if (++dictSize >= numBitsMask) { numBitsMask <<= 1; } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - StringStream_s(value, numBitsMask); - StringStream_s(c, 0b100000000 << value); - } - // increase token bitlength if necessary - if (++dictSize >= numBitsMask) { - numBitsMask <<= 1; } - } - // Mark the end of the stream - StringStream_s(2, numBitsMask); - // Flush the last char - StringStream_v <<= StringStream_b - StringStream_p; - StringStream_d.push(StringStream_g(StringStream_v)); - return StringStream_d; + // Mark the end of the stream + streamBits(2, numBitsMask); + // Flush the last char + streamDataVal <<= streamBitsPerChar - streamDataPosition; + streamData.push(streamGetCharFromInt(streamDataVal)); + } + return streamData; } function _decompress(length, resetBits, getNextValue) { @@ -289,9 +291,8 @@ var LZString = (function () { return { compressToBase64: function (input) { if (input == null) return ""; - var res = _compress(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; + var res = _compress(input, 6, getCharFromBase64), + i = res.length % 4; // To produce valid Base64 while (i--) { res.push("="); } diff --git a/libs/lz-string.min.js b/libs/lz-string.min.js index c44591d..c205ff6 100644 --- a/libs/lz-string.min.js +++ b/libs/lz-string.min.js @@ -1 +1 @@ -var LZString=function(){function b(s,t){this.d=[],this.v=0,this.p=0,this.b=s,this.g=t}function d(s){return q[s]}function e(s){return r[s]}function f(s){return o(s+32)}function g(s){return{v:s,d:{}}}function h(s,t,u){if(null==s)return[];var C,w=0,x=0,y={},z=!0,A=0,B=g(3),D=3,E=4,F=new b(t,u);if(s.length){for(A=s.charCodeAt(0),x=256>A?0:1,F.s(x,E),F.s(A,x?65536:256),y[A]=B,w=1;w=E&&(E<<=1),x=256>A?0:1,F.s(x,E),F.s(A,x?65536:256),y[A]=g(D),z=!0),B.d[A]=g(++D),D>=E&&(E<<=1),B=y[A]);z?z=!1:F.s(B.v,E),void 0==y[A]&&(++D>=E&&(E<<=1),x=256>A?0:1,F.s(x,E),F.s(A,256<=E&&(E<<=1)}return F.s(2,E),F.f()}function k(s,t,u){for(var v=['','',''],w=4,x=4,y=3,z='',A=[],B=0,C=2,D=0,E='',F=u(0),G=t,H=1;D!=C;)B+=(1&F>>--G)<>--G)<>--G)<B){for(C=8+8*B,B=D=0;D!=C;)B+=(1&F>>--G)<v.length)return null;z=Bn;)62>=1;u++)this.v=1&s>>u|this.v<<1,++this.p===this.b&&(this.p=0,this.d.push(this.g(this.v)),this.v=0)},b.prototype.f=function(){return this.v<<=this.b-this.p,this.d.push(this.g(this.v)),this.d},{compressToBase64:function(s){if(null==s)return'';for(var t=h(s,6,d),u=t.length%4;u--;)t.push('=');return t.join('')},decompressFromBase64:function(s){return null==s?'':''==s?null:k(s.length,6,function(t){return p[s.charCodeAt(t)]})},compressToUTF16:function(s){if(null==s)return'';var t=h(s,15,f);return t.push(' '),t.join('')},decompressFromUTF16:function(s){return null==s?'':''==s?null:k(s.length,15,function(t){return s.charCodeAt(t)-32})},compressToUint8Array:function(s){for(var x,t=l(s),u=new Uint8Array(2*t.length),v=0,w=t.length;v>>8,u[2*v+1]=255&x;return u},decompressFromUint8Array:function(s){return null===s||void 0===s?m(s):0==s.length?null:k(s.length,8,function(t){return s[t]})},compressToEncodedURIComponent:function(s){return null==s?'':h(s,6,e).join('')},decompressFromEncodedURIComponent:function(s){return null==s?'':''==s?null:(s=s.replace(/ /g,'+'),k(s.length,6,function(t){return p[s.charCodeAt(t)]}))},compress:function(s){return l(s).join('')},compressToArray:l,decompress:function(s){return null==s?'':''==s?null:k(s.length,16,function(t){return s.charCodeAt(t)})},decompressFromArray:m}}();'function'==typeof define&&define.amd?define(function(){return LZString}):'undefined'!=typeof module&&null!=module?module.exports=LZString:'undefined'!=typeof angular&&null!=angular&&angular.module('LZString',[]).factory('LZString',function(){return LZString}); \ No newline at end of file +var LZString=function(){function b(y,z){for(var A=0;z>>=1;A++)q=1&y>>A|q<<1,++r===s&&(r=0,p.push(t(q)),q=0)}function d(y){return w[y]}function e(y){return x[y]}function f(y){return o(y+32)}function g(y){return{v:y,d:{}}}function h(y,z,A){if(p=[],null!=y){q=0,r=0,s=z,t=A;var H,B=0,C=0,D={},E=!0,F=0,G=g(3),I=3,J=4;if(y.length){for(F=y.charCodeAt(0),C=256>F?0:1,b(C,J),b(F,C?65536:256),D[F]=G,B=1;B=J&&(J<<=1),C=256>F?0:1,b(C,J),b(F,C?65536:256),D[F]=g(I),E=!0),G.d[F]=g(++I),I>=J&&(J<<=1),G=D[F]);E?E=!1:b(G.v,J),void 0==D[F]&&(++I>=J&&(J<<=1),C=256>F?0:1,b(C,J),b(F,256<=J&&(J<<=1)}b(2,J),q<<=s-r,p.push(t(q))}return p}function k(y,z,A){for(var B=["","",""],C=4,D=4,E=3,F="",G=[],H=0,I=2,J=0,K="",L=A(0),M=z,N=1;J!=I;)H+=(1&L>>--M)<>--M)<>--M)<H){for(I=8+8*H,H=J=0;J!=I;)H+=(1&L>>--M)<B.length)return null;F=Hn;)62>>8,A[2*B+1]=255&D;return A},decompressFromUint8Array:function(y){if(null===y||y===void 0)return m(y);return 0==y.length?null:k(y.length,8,function(z){return y[z]})},compressToEncodedURIComponent:function(y){return null==y?"":h(y,6,e).join("")},decompressFromEncodedURIComponent:function(y){return null==y?"":""==y?null:(y=y.replace(/ /g,"+"),k(y.length,6,function(z){return u[y.charCodeAt(z)]}))},compress:function(y){return l(y).join("")},compressToArray:l,decompress:function(y){return null==y?"":""==y?null:k(y.length,16,function(z){return y.charCodeAt(z)})},decompressFromArray:m}}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString}); \ No newline at end of file diff --git a/lz-string-unsafe.js b/lz-string-unsafe.js index 36ba25e..ca5c10a 100644 --- a/lz-string-unsafe.js +++ b/lz-string-unsafe.js @@ -1,396 +1,394 @@ var LZStringUnsafe = (function () { - // private property - var i = 0, - reverseDict = {}, - fromCharCode = String.fromCharCode, - Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), - //UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), - UriSafeCharArray = Base64CharArray.concat(); UriSafeCharArray[63] = '-'; UriSafeCharArray[64] = '$'; - while (i < 65) { - if (i > 62) { - reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; - } - reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + // private property + var i = 0, + reverseDict = {}, + fromCharCode = String.fromCharCode, + streamData, + streamDataVal, + streamDataPosition, + streamBitsPerChar, + streamGetCharFromInt, + base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+", + Base64CharArray = (base + "/=").split(''), + UriSafeCharArray = (base + "-$").split(''); + while (i < 65) { + if (i > 62) { + reverseDict[UriSafeCharArray[i].charCodeAt(0)] = i; } - - function StringStream(bitsPerChar, getChar) { - // data - this.d = []; // empty stream - // davaVal - this.v = 0; - // dataPosition - this.p = 0; - this.b = bitsPerChar; - this.g = getChar; + reverseDict[Base64CharArray[i].charCodeAt(0)] = i++; + } + + function streamBits(value, numBitsMask) { + for (var i = 0; numBitsMask >>= 1; i++) { + // shifting has precedence over bitmasking + streamDataVal = value >> i & 1 | streamDataVal << 1; + if (++streamDataPosition === streamBitsPerChar) { + streamDataPosition = 0; + streamData.push(streamGetCharFromInt(streamDataVal)); + streamDataVal = 0; + } } - - StringStream.prototype.s = function (value, numBitsMask) { //streamBits - for (var i = 0; numBitsMask >>= 1; i++) { - // shifting has precedence over bitmasking - this.v = value >> i & 1 | this.v << 1; - if (++this.p === this.b) { - this.d.push(this.g(this.v)); - this.v = 0; - this.p = 0; + } + + function getCharFromBase64(a) { return Base64CharArray[a]; } + function getCharFromURISafe(a) { return UriSafeCharArray[a]; } + function getCharFromUTF16(a) { return fromCharCode(a + 32); } + function _compress(uncompressed, bitsPerChar, getCharFromInt) { + // data - empty stream + streamData = []; + + if (uncompressed != null) { + // davaVal + streamDataVal = 0; + // dataPosition + streamDataPosition = 0; + streamBitsPerChar = bitsPerChar; + streamGetCharFromInt = getCharFromInt; + var j = 0, k = 0, value = 0, + node = [3], // first node will always be initialised like this. + // we should never output the root anyway, + // so we initiate with terminating token + // Also, dictionary[1] will be overwritten + // by the first charCode + dictionary = [2, 2, node], + freshNode = true, + c = 0, + dictSize = 3, + numBitsMask = 0b100; + + if (uncompressed.length) { + // If there is a string, the first charCode is guaranteed to + // be new, so we write it to output stream, and add it to the + // dictionary. For the same reason we can initialize freshNode + // as true, and new_node, node and dictSize as if + // it was already added to the dictionary (see above). + + c = uncompressed.charCodeAt(0); + + // == Write first charCode token to output == + + // 8 or 16 bit? + value = c < 256 ? 0 : 1 + + // insert "new 8/16 bit charCode" token + // into bitstream (value 1) + streamBits(value, numBitsMask); + streamBits(c, value ? 0b10000000000000000 : 0b100000000); + + // Add charCode to the dictionary. + dictionary[1] = c; + + nextchar: + for (j = 1; j < uncompressed.length; j++) { + c = uncompressed.charCodeAt(j); + // does the new charCode match an existing prefix? + for (k = 1; k < node.length; k += 2) { + if (node[k] == c) { + node = node[k + 1]; + continue nextchar; + } + } + // we only end up here if there is no matching char + + // Prefix+charCode does not exist in trie yet. + // We write the prefix to the bitstream, and add + // the new charCode to the dictionary if it's new + // Then we set `node` to the root node matching + // the charCode. + + if (freshNode) { + // Prefix is a freshly added character token, + // which was already written to the bitstream + freshNode = false; + } else { + // write out the current prefix token + streamBits(node[0], numBitsMask); + } + + // Is the new charCode a new character + // that needs to be stored at the root? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; } - } - } - - StringStream.prototype.f = function () { // finalise - // Flush the last char - this.d.push(this.g(this.v << this.b - this.p)); - return this.d; - } - function getCharFromBase64(a) { return Base64CharArray[a]; } - function getCharFromURISafe(a) { return UriSafeCharArray[a]; } - function getCharFromUTF16(a) { return fromCharCode(a + 32); } - function _compress(uncompressed, bitsPerChar, getCharFromInt) { - if (uncompressed == null) return []; - var i = 0, j = 0, k = 0, value = 0, - node = [3], // first node will always be initialised like this. - // we should never output the root anyway, - // so we initiate with terminating token - // Also, dictionary[1] will be overwritten - // by the first charCode - dictionary = [2, 2, node], - freshNode = true, - c = 0, - dictSize = 3, - numBitsMask = 0b100, - stringStream = new StringStream(bitsPerChar, getCharFromInt); - - if (uncompressed.length) { - // If there is a string, the first charCode is guaranteed to - // be new, so we write it to output stream, and add it to the - // dictionary. For the same reason we can initialize freshNode - // as true, and new_node, node and dictSize as if - // it was already added to the dictionary (see above). - - c = uncompressed.charCodeAt(0); - - // == Write first charCode token to output == - - // 8 or 16 bit? + // insert "new 8/16 bit charCode" token, + // see comments above for explanation value = c < 256 ? 0 : 1 + streamBits(value, numBitsMask); + streamBits(c, value ? 0b10000000000000000 : 0b100000000); + + dictionary.push(c); + dictionary.push([dictSize]); + // Note of that we already wrote + // the charCode token to the bitstream + freshNode = true; + } + // add node representing prefix + new charCode to trie + node.push(c); + node.push([++dictSize]); + // increase token bitlength if necessary + if (dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // set node to first charCode of new prefix + // k is guaranteed to be at the current charCode, + // since we either broke out of the while loop + // when it matched, or just added the new charCode + node = dictionary[k + 1]; - // insert "new 8/16 bit charCode" token - // into bitstream (value 1) - stringStream.s(value, numBitsMask); - stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); - - // Add charCode to the dictionary. - dictionary[1] = c; - - nextchar: - for (j = 1; j < uncompressed.length; j++) { - c = uncompressed.charCodeAt(j); - // does the new charCode match an existing prefix? - for (k = 1; k < node.length; k += 2) { - if (node[k] == c) { - node = node[k + 1]; - continue nextchar; - } - } - // we only end up here if there is no matching char - - // Prefix+charCode does not exist in trie yet. - // We write the prefix to the bitstream, and add - // the new charCode to the dictionary if it's new - // Then we set `node` to the root node matching - // the charCode. - - if (freshNode) { - // Prefix is a freshly added character token, - // which was already written to the bitstream - freshNode = false; - } else { - // write out the current prefix token - stringStream.s(node[0], numBitsMask); - } - - // Is the new charCode a new character - // that needs to be stored at the root? - k = 1; - while (dictionary[k] != c && k < dictionary.length) { - k += 2; - } - if (k == dictionary.length) { - // increase token bitlength if necessary - if (++dictSize >= numBitsMask) { - numBitsMask <<= 1; - } - - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - stringStream.s(value, numBitsMask); - stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); - - dictionary.push(c); - dictionary.push([dictSize]); - // Note of that we already wrote - // the charCode token to the bitstream - freshNode = true; - } - // add node representing prefix + new charCode to trie - node.push(c); - node.push([++dictSize]); - // increase token bitlength if necessary - if (dictSize >= numBitsMask) { - numBitsMask <<= 1; - } - // set node to first charCode of new prefix - // k is guaranteed to be at the current charCode, - // since we either broke out of the while loop - // when it matched, or just added the new charCode - node = dictionary[k + 1]; - - } + } - // === Write last prefix to output === - if (freshNode) { - // character token already written to output - freshNode = false; - } else { - // write out the prefix token - stringStream.s(node[0], numBitsMask); + // === Write last prefix to output === + if (freshNode) { + // character token already written to output + freshNode = false; + } else { + // write out the prefix token + streamBits(node[0], numBitsMask); - } + } - // Is c a new character? - k = 1; - while (dictionary[k] != c && k < dictionary.length) { - k += 2; - } - if (k == dictionary.length) { - // increase token bitlength if necessary - if (++dictSize >= numBitsMask) { - numBitsMask <<= 1; - } - // insert "new 8/16 bit charCode" token, - // see comments above for explanation - value = c < 256 ? 0 : 1 - stringStream.s(value, numBitsMask); - stringStream.s(c, value ? 0b10000000000000000 : 0b100000000); - } - // increase token bitlength if necessary - if (++dictSize >= numBitsMask) { - numBitsMask <<= 1; - } - } + // Is c a new character? + k = 1; + while (dictionary[k] != c && k < dictionary.length) { + k += 2; + } + if (k == dictionary.length) { + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + // insert "new 8/16 bit charCode" token, + // see comments above for explanation + value = c < 256 ? 0 : 1 + streamBits(value, numBitsMask); + streamBits(c, value ? 0b10000000000000000 : 0b100000000); + } + // increase token bitlength if necessary + if (++dictSize >= numBitsMask) { + numBitsMask <<= 1; + } + } - // Mark the end of the stream - stringStream.s(2, numBitsMask); + // Mark the end of the stream + streamBits(2, numBitsMask); - // Flush the last char - return stringStream.f(); + // Flush the last char + streamDataVal <<= streamBitsPerChar - streamDataPosition; + streamData.push(streamGetCharFromInt(streamDataVal)); + } + return streamData; + } + + function _decompress(length, resetBits, getNextValue) { + var dictionary = [0, 1, 2], + enlargeIn = 4, + dictSize = 4, + numBits = 3, + entry = "", + result = [], + w = "", + bits = 0, + maxpower = 2, + power = 0, + c = "", + data_val = getNextValue(0), + data_position = resetBits, + data_index = 1; + + // Get first token, guaranteed to be either + // a new character token (8 or 16 bits) + // or end of stream token. + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } } - function _decompress(length, resetBits, getNextValue) { - var dictionary = [0, 1, 2], - enlargeIn = 4, - dictSize = 4, - numBits = 3, - entry = "", - result = [], - w = "", - bits = 0, - maxpower = 2, - power = 0, - c = "", - data_val = getNextValue(0), - data_position = resetBits, - data_index = 1; - - // Get first token, guaranteed to be either - // a new character token (8 or 16 bits) - // or end of stream token. - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } + // if end of stream token, return empty string + if (bits == 2) { + return ""; + } - // if end of stream token, return empty string - if (bits == 2) { - return ""; + // else, get character + maxpower = bits * 8 + 8; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } + } + c = fromCharCode(bits); + dictionary[3] = c; + w = c; + result.push(c); + + // read rest of string + while (data_index <= length) { + // read out next token + maxpower = numBits; + bits = power = 0; + while (power != maxpower) { + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); } + } - // else, get character - maxpower = bits * 8 + 8; + // 0 or 1 implies new character token + if (bits < 2) { + maxpower = (8 + 8 * bits); bits = power = 0; while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } + // shifting has precedence over bitmasking + bits += (data_val >> --data_position & 1) << power++; + if (data_position == 0) { + data_position = resetBits; + data_val = getNextValue(data_index++); + } } - c = fromCharCode(bits); - dictionary[3] = c; - w = c; - result.push(c); - - // read rest of string - while (data_index <= length) { - // read out next token - maxpower = numBits; - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - - // 0 or 1 implies new character token - if (bits < 2) { - maxpower = (8 + 8 * bits); - bits = power = 0; - while (power != maxpower) { - // shifting has precedence over bitmasking - bits += (data_val >> --data_position & 1) << power++; - if (data_position == 0) { - data_position = resetBits; - data_val = getNextValue(data_index++); - } - } - dictionary[dictSize] = fromCharCode(bits); - bits = dictSize++; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } - } else if (bits == 2) { - // end of stream token - return result.join(''); - } + dictionary[dictSize] = fromCharCode(bits); + bits = dictSize++; + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } + } else if (bits == 2) { + // end of stream token + return result.join(''); + } - if (bits > dictionary.length) { - return null; - } - entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); - result.push(entry); - // Add w+entry[0] to the dictionary. - dictionary[dictSize++] = w + entry.charAt(0); + if (bits > dictionary.length) { + return null; + } + entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); + result.push(entry); + // Add w+entry[0] to the dictionary. + dictionary[dictSize++] = w + entry.charAt(0); - w = entry; + w = entry; - if (--enlargeIn == 0) { - enlargeIn = 1 << numBits++; - } + if (--enlargeIn == 0) { + enlargeIn = 1 << numBits++; + } - } - return ""; - } - function _compressToArray(uncompressed) { - return _compress(uncompressed, 16, fromCharCode); - } - function _decompressFromArray(compressed) { - if (compressed == null) return ""; - if (compressed.length == 0) return null; - return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); } - - return { - compressToBase64: function (input) { - if (input == null) return ""; - var res = _compress(input, 6, getCharFromBase64); - // To produce valid Base64 - var i = res.length % 4; - while (i--) { - res.push("="); - } - - return res.join(''); - }, - - decompressFromBase64: function (input) { - if (input == null) return ""; - if (input == "") return null; - return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compressToUTF16: function (input) { - if (input == null) return ""; - var compressed = _compress(input, 15, getCharFromUTF16); - compressed.push(" "); - return compressed.join(''); - }, - - decompressFromUTF16: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); - }, - - //compress into uint8array (UCS-2 big endian format) - compressToUint8Array: function (uncompressed) { - var compressed = _compressToArray(uncompressed); - var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character - - for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { - var current_value = compressed[i].charCodeAt(0); - buf[i * 2] = current_value >>> 8; - buf[i * 2 + 1] = current_value & 0xFF; - } - return buf; - }, - - //decompress from uint8array (UCS-2 big endian format) - decompressFromUint8Array: function (compressed) { - if (compressed === null || compressed === undefined) { - return _decompressFromArray(compressed); - } else if (compressed.length == 0) { - return null; - } - return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); - }, - - - //compress into a string that is already URI encoded - compressToEncodedURIComponent: function (input) { - if (input == null) return ""; - return _compress(input, 6, getCharFromURISafe).join(''); - }, - - //decompress from an output of compressToEncodedURIComponent - decompressFromEncodedURIComponent: function (input) { - if (input == null) return ""; - if (input == "") return null; - input = input.replace(/ /g, "+"); - return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); - }, - - compress: function (uncompressed) { - return _compressToArray(uncompressed).join(''); - }, - compressToArray: _compressToArray, - - decompress: function (compressed) { - if (compressed == null) return ""; - if (compressed == "") return null; - return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); - }, - - decompressFromArray: _decompressFromArray - }; + return ""; + } + function _compressToArray(uncompressed) { + return _compress(uncompressed, 16, fromCharCode); + } + function _decompressFromArray(compressed) { + if (compressed == null) return ""; + if (compressed.length == 0) return null; + return _decompress(compressed.length, 16, function (index) { return compressed[index].charCodeAt(0); }); + } + + return { + compressToBase64: function (input) { + if (input == null) return ""; + var res = _compress(input, 6, getCharFromBase64), + i = res.length % 4; // To produce valid Base64 + while (i--) { + res.push("="); + } + + return res.join(''); + }, + + decompressFromBase64: function (input) { + if (input == null) return ""; + if (input == "") return null; + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compressToUTF16: function (input) { + if (input == null) return ""; + var compressed = _compress(input, 15, getCharFromUTF16); + compressed.push(" "); + return compressed.join(''); + }, + + decompressFromUTF16: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 15, function (index) { return compressed.charCodeAt(index) - 32; }); + }, + + //compress into uint8array (UCS-2 big endian format) + compressToUint8Array: function (uncompressed) { + var compressed = _compressToArray(uncompressed); + var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character + + for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { + var current_value = compressed[i].charCodeAt(0); + buf[i * 2] = current_value >>> 8; + buf[i * 2 + 1] = current_value & 0xFF; + } + return buf; + }, + + //decompress from uint8array (UCS-2 big endian format) + decompressFromUint8Array: function (compressed) { + if (compressed === null || compressed === undefined) { + return _decompressFromArray(compressed); + } else if (compressed.length == 0) { + return null; + } + return _decompress(compressed.length, 8, function (index) { return compressed[index]; }); + }, + + + //compress into a string that is already URI encoded + compressToEncodedURIComponent: function (input) { + if (input == null) return ""; + return _compress(input, 6, getCharFromURISafe).join(''); + }, + + //decompress from an output of compressToEncodedURIComponent + decompressFromEncodedURIComponent: function (input) { + if (input == null) return ""; + if (input == "") return null; + input = input.replace(/ /g, "+"); + return _decompress(input.length, 6, function (index) { return reverseDict[input.charCodeAt(index)]; }); + }, + + compress: function (uncompressed) { + return _compressToArray(uncompressed).join(''); + }, + compressToArray: _compressToArray, + + decompress: function (compressed) { + if (compressed == null) return ""; + if (compressed == "") return null; + return _decompress(compressed.length, 16, function (index) { return compressed.charCodeAt(index); }); + }, + + decompressFromArray: _decompressFromArray + }; })(); if (typeof define === 'function' && define.amd) { - define(function () { return LZStringUnsafe; }); + define(function () { return LZStringUnsafe; }); } else if (typeof module !== 'undefined' && module != null) { - module.exports = LZStringUnsafe + module.exports = LZStringUnsafe } else if (typeof angular !== 'undefined' && angular != null) { - angular.module('LZStringUnsafe', []) - .factory('LZStringUnsafe', function () { - return LZStringUnsafe; - }); + angular.module('LZStringUnsafe', []) + .factory('LZStringUnsafe', function () { + return LZStringUnsafe; + }); } diff --git a/lz-string-unsafe.min.js b/lz-string-unsafe.min.js index 02f819b..02d7136 100644 --- a/lz-string-unsafe.min.js +++ b/lz-string-unsafe.min.js @@ -1 +1 @@ -var LZStringUnsafe=function(){function b(s,t){this.d=[],this.v=0,this.p=0,this.b=s,this.g=t}function d(s){return q[s]}function e(s){return r[s]}function f(s){return p(s+32)}function g(s,t,u){if(null==s)return[];var x=0,y=0,z=0,A=[3],B=[2,2,A],C=!0,D=0,E=3,F=4,G=new b(t,u);if(s.length){D=s.charCodeAt(0),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256),B[1]=D;nextchar:for(x=1;x=F&&(F<<=1),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256),B.push(D),B.push([E]),C=!0),A.push(D),A.push([++E]),E>=F&&(F<<=1),A=B[y+1]}for(C?C=!1:G.s(A[0],F),y=1;B[y]!=D&&y=F&&(F<<=1),z=256>D?0:1,G.s(z,F),G.s(D,z?65536:256)),++E>=F&&(F<<=1)}return G.s(2,F),G.f()}function h(s,t,u){for(var v=[0,1,2],x=4,y=4,z=3,A='',B=[],C='',D=0,E=2,F=0,G='',H=u(0),I=t,J=1;F!=E;)D+=(1&H>>--I)<>--I)<>--I)<D){for(E=8+8*D,D=F=0;F!=E;)D+=(1&H>>--I)<v.length)return null;A=Dn;)62>=1;u++)this.v=1&s>>u|this.v<<1,++this.p===this.b&&(this.d.push(this.g(this.v)),this.v=0,this.p=0)},b.prototype.f=function(){return this.d.push(this.g(this.v<>>8,u[2*v+1]=255&y;return u},decompressFromUint8Array:function(s){return null===s||void 0===s?m(s):0==s.length?null:h(s.length,8,function(t){return s[t]})},compressToEncodedURIComponent:function(s){return null==s?'':g(s,6,e).join('')},decompressFromEncodedURIComponent:function(s){return null==s?'':''==s?null:(s=s.replace(/ /g,'+'),h(s.length,6,function(t){return o[s.charCodeAt(t)]}))},compress:function(s){return l(s).join('')},compressToArray:l,decompress:function(s){return null==s?'':''==s?null:h(s.length,16,function(t){return s.charCodeAt(t)})},decompressFromArray:m}}();'function'==typeof define&&define.amd?define(function(){return LZStringUnsafe}):'undefined'!=typeof module&&null!=module?module.exports=LZStringUnsafe:'undefined'!=typeof angular&&null!=angular&&angular.module('LZStringUnsafe',[]).factory('LZStringUnsafe',function(){return LZStringUnsafe}); \ No newline at end of file +var LZStringUnsafe=function(){function b(z,A){for(var B=0;A>>=1;B++)r=1&z>>B|r<<1,++s===t&&(s=0,q.push(u(r)),r=0)}function d(z){return x[z]}function e(z){return y[z]}function f(z){return p(z+32)}function g(z,A,B){if(q=[],null!=z){r=0,s=0,t=A,u=B;var C=0,D=0,E=0,F=[3],G=[2,2,F],H=!0,I=0,J=3,K=4;if(z.length){I=z.charCodeAt(0),E=256>I?0:1,b(E,K),b(I,E?65536:256),G[1]=I;nextchar:for(C=1;C=K&&(K<<=1),E=256>I?0:1,b(E,K),b(I,E?65536:256),G.push(I),G.push([J]),H=!0),F.push(I),F.push([++J]),J>=K&&(K<<=1),F=G[D+1]}for(H?H=!1:b(F[0],K),D=1;G[D]!=I&&D=K&&(K<<=1),E=256>I?0:1,b(E,K),b(I,E?65536:256)),++J>=K&&(K<<=1)}b(2,K),r<<=t-s,q.push(u(r))}return q}function h(z,A,B){for(var C=[0,1,2],D=4,E=4,F=3,G="",H=[],I="",J=0,K=2,L=0,M="",N=B(0),O=A,P=1;L!=K;)J+=(1&N>>--O)<>--O)<>--O)<J){for(K=8+8*J,J=L=0;L!=K;)J+=(1&N>>--O)<C.length)return null;G=Jn;)62>>8,B[2*C+1]=255&E;return B},decompressFromUint8Array:function(z){if(null===z||z===void 0)return m(z);return 0==z.length?null:h(z.length,8,function(A){return z[A]})},compressToEncodedURIComponent:function(z){return null==z?"":g(z,6,e).join("")},decompressFromEncodedURIComponent:function(z){return null==z?"":""==z?null:(z=z.replace(/ /g,"+"),h(z.length,6,function(A){return o[z.charCodeAt(A)]}))},compress:function(z){return l(z).join("")},compressToArray:l,decompress:function(z){return null==z?"":""==z?null:h(z.length,16,function(A){return z.charCodeAt(A)})},decompressFromArray:m}}();"function"==typeof define&&define.amd?define(function(){return LZStringUnsafe}):"undefined"!=typeof module&&null!=module?module.exports=LZStringUnsafe:"undefined"!=typeof angular&&null!=angular&&angular.module("LZStringUnsafe",[]).factory("LZStringUnsafe",function(){return LZStringUnsafe}); \ No newline at end of file