cleanup; rename vars; fix deflate

This commit is contained in:
rolux 2012-05-25 10:37:48 +02:00
parent a393591741
commit ad3b50cb82

View file

@ -1,91 +1,50 @@
'use strict'; 'use strict';
(function() { /*@
Ox.encodeBase26 <b> Encode a number as bijective base26
function cap(width, height) {
// returns maximum encoding capacity of an image
return parseInt(width * height * 3/8) - 4;
}
function seek(data, px) {
// returns this, or the next, opaque pixel
while (data[px * 4 + 3] < 255) {
if (++px * 4 == data.length) {
throwPNGError('de');
}
}
return px;
}
function xor(byte) {
// returns "1"-bits-in-byte % 2
// use: num.toString(2).replace(/0/g, '').length % 2
var xor = 0;
Ox.range(8).forEach(function(i) {
xor ^= byte >> i & 1;
});
return xor;
}
function throwPNGError(str) {
throw new RangeError(
'PNG codec can\'t ' +
(str == 'en' ? 'encode data' : 'decode image')
);
}
function throwUTF8Error(byte, pos) {
throw new RangeError(
'UTF-8 codec can\'t decode byte 0x' +
byte.toString(16).toUpperCase() + ' at position ' + pos
);
}
/*@
Ox.encodeBase26 <b> Encode a number as bijective base26
See <a href="http://en.wikipedia.org/wiki/Bijective_numeration"> See <a href="http://en.wikipedia.org/wiki/Bijective_numeration">
Bijective numeration</a>. Bijective numeration</a>.
> Ox.encodeBase26(4461) > Ox.encodeBase26(4461)
'FOO' 'FOO'
@*/ @*/
Ox.encodeBase26 = function(num) { Ox.encodeBase26 = function(number) {
var ret = ''; var string = '';
while (num) { while (number) {
ret = String.fromCharCode(64 + num % 26) + ret; string = String.fromCharCode(64 + number % 26) + string;
num = parseInt(num / 26); number = parseInt(number / 26);
} }
return ret; return string;
}; };
/*@ /*@
Ox.decodeBase26 <f> Decodes a bijective base26-encoded number Ox.decodeBase26 <f> Decodes a bijective base26-encoded number
See <a href="http://en.wikipedia.org/wiki/Bijective_numeration"> See <a href="http://en.wikipedia.org/wiki/Bijective_numeration">
Bijective numeration</a>. Bijective numeration</a>.
> Ox.decodeBase26('foo') > Ox.decodeBase26('foo')
4461 4461
@*/ @*/
Ox.decodeBase26 = function(str) { Ox.decodeBase26 = function(string) {
return str.toUpperCase().split('').reverse().reduce(function(p, v, i) { return string.toUpperCase().split('').reverse().reduce(function(p, c, i) {
return p + (v.charCodeAt(0) - 64) * Math.pow(26, i); return p + (c.charCodeAt(0) - 64) * Math.pow(26, i);
}, 0); }, 0);
}; };
/*@ /*@
Ox.encodeBase32 <b> Encode a number as base32 Ox.encodeBase32 <b> Encode a number as base32
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>. See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
> Ox.encodeBase32(15360) > Ox.encodeBase32(15360)
'F00' 'F00'
> Ox.encodeBase32(33819) > Ox.encodeBase32(33819)
'110V' '110V'
@*/ @*/
Ox.encodeBase32 = function(num) { Ox.encodeBase32 = function(number) {
return Ox.map(num.toString(32), function(char) { return Ox.map(number.toString(32), function(character) {
return Ox.BASE_32_DIGITS[parseInt(char, 32)]; return Ox.BASE_32_DIGITS[parseInt(character, 32)];
}); });
}; };
/*@ /*@
Ox.decodeBase32 <f> Decodes a base32-encoded number Ox.decodeBase32 <f> Decodes a base32-encoded number
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>. See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
> Ox.decodeBase32('foo') > Ox.decodeBase32('foo')
15360 15360
@ -93,86 +52,86 @@
33819 33819
> Ox.decodeBase32('?').toString() > Ox.decodeBase32('?').toString()
'NaN' 'NaN'
@*/ @*/
Ox.decodeBase32 = function(str) { Ox.decodeBase32 = function(string) {
return parseInt(Ox.map(str.toUpperCase(), function(char) { return parseInt(Ox.map(string.toUpperCase(), function(character) {
var index = Ox.BASE_32_DIGITS.indexOf( var index = Ox.BASE_32_DIGITS.indexOf(
Ox.BASE_32_ALIASES[char] || char Ox.BASE_32_ALIASES[character] || character
); );
return (index == -1 ? ' ' : index).toString(32); return (index == -1 ? ' ' : index).toString(32);
}), 32); }), 32);
}; };
/*@ /*@
Ox.encodeBase64 <f> Encode a number as base64 Ox.encodeBase64 <f> Encode a number as base64
> Ox.encodeBase64(32394) > Ox.encodeBase64(32394)
'foo' 'foo'
@*/ @*/
Ox.encodeBase64 = function(num) { Ox.encodeBase64 = function(number) {
return btoa(Ox.encodeBase256(num)).replace(/=/g, ''); return btoa(Ox.encodeBase256(number)).replace(/=/g, '');
}; };
/*@ /*@
Ox.decodeBase64 <f> Decodes a base64-encoded number Ox.decodeBase64 <f> Decodes a base64-encoded number
> Ox.decodeBase64('foo') > Ox.decodeBase64('foo')
32394 32394
@*/ @*/
Ox.decodeBase64 = function(str) { Ox.decodeBase64 = function(string) {
return Ox.decodeBase256(atob(str)); return Ox.decodeBase256(atob(string));
}; };
/*@ /*@
Ox.encodeBase128 <f> Encode a number as base128 Ox.encodeBase128 <f> Encode a number as base128
> Ox.encodeBase128(1685487) > Ox.encodeBase128(1685487)
'foo' 'foo'
@*/ @*/
Ox.encodeBase128 = function(num) { Ox.encodeBase128 = function(number) {
var str = ''; var string = '';
while (num) { while (number) {
str = Ox.char(num & 127) + str; string = Ox.char(number & 127) + string;
num >>= 7; number >>= 7;
} }
return str; return string;
}; };
/*@ /*@
Ox.decodeBase128 <f> Decode a base128-encoded number Ox.decodeBase128 <f> Decode a base128-encoded number
> Ox.decodeBase128('foo') > Ox.decodeBase128('foo')
1685487 1685487
@*/ @*/
Ox.decodeBase128 = function(str) { Ox.decodeBase128 = function(string) {
return str.split('').reverse().reduce(function(p, v, i) { return string.split('').reverse().reduce(function(p, c, i) {
return p + (v.charCodeAt(0) << i * 7); return p + (c.charCodeAt(0) << i * 7);
}, 0); }, 0);
}; };
/*@ /*@
Ox.encodeBase256 <f> Encode a number as base256 Ox.encodeBase256 <f> Encode a number as base256
> Ox.encodeBase256(6713199) > Ox.encodeBase256(6713199)
'foo' 'foo'
@*/ @*/
Ox.encodeBase256 = function(num) { Ox.encodeBase256 = function(number) {
var str = ''; var string = '';
while (num) { while (number) {
str = Ox.char(num & 255) + str; string = Ox.char(number & 255) + string;
num >>= 8; number >>= 8;
} }
return str; return string;
}; };
/*@ /*@
Ox.decodeBase256 <f> Decode a base256-encoded number Ox.decodeBase256 <f> Decode a base256-encoded number
> Ox.decodeBase256('foo') > Ox.decodeBase256('foo')
6713199 6713199
@*/ @*/
Ox.decodeBase256 = function(str) { Ox.decodeBase256 = function(string) {
return str.split('').reverse().reduce(function(p, v, i) { return string.split('').reverse().reduce(function(p, c, i) {
return p + (v.charCodeAt(0) << i * 8); return p + (c.charCodeAt(0) << i * 8);
}, 0); }, 0);
}; };
/*@ /*@
Ox.encodeDeflate <f> Encodes a string, using deflate Ox.encodeDeflate <f> Encodes a string, using deflate
Since PNGs are deflate-encoded, the <code>canvas</code> object's Since PNGs are deflate-encoded, the <code>canvas</code> object's
<code>toDataURL</code> method provides an efficient implementation. <code>toDataURL</code> method provides an efficient implementation.
The string is encoded as UTF-8 and written to the RGB channels of a The string is encoded as UTF-8 and written to the RGB channels of a
@ -181,44 +140,47 @@
(str) -> <s> The encoded string (str) -> <s> The encoded string
str <s> The string to be encoded str <s> The string to be encoded
# Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert) # Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert)
@*/ @*/
Ox.encodeDeflate = function(str, callback) { Ox.encodeDeflate = function(string, callback) {
// Make sure we can encode the full unicode range of characters. // Make sure we can encode the full unicode range of characters.
str = Ox.encodeUTF8(str); string = Ox.encodeUTF8(string);
// We can only safely write to RGB, so we need 1 pixel for 3 bytes. // We can only safely write to RGB, so we need 1 pixel for 3 bytes.
// The string length may not be a multiple of 3, so we need to encode // The string length may not be a multiple of 3, so we need to encode
// the number of padding bytes (1 byte), the string, and non-0-bytes // the number of padding bytes (1 byte), the string, and non-0-bytes
// as padding, so that the combined length becomes a multiple of 3. // as padding, so that the combined length becomes a multiple of 3.
var len = 1 + str.length, c = Ox.canvas(Math.ceil(len / 3), 1), var length = 1 + string.length, c = Ox.canvas(Math.ceil(length / 3), 1),
data, idat, pad = (3 - len % 3) % 3; data, idat, pad = (3 - length % 3) % 3;
str = Ox.char(pad) + str + Ox.repeat('\u00FF', pad); string = Ox.char(pad) + string + Ox.repeat('\u00FF', pad);
Ox.loop(c.data.length, function(i) { Ox.loop(c.data.length, function(i) {
// Write character codes into RGB, and 255 into ALPHA // Write character codes into RGB, and 255 into ALPHA
c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; c.data[i] = i % 4 < 3 ? string.charCodeAt(i - parseInt(i / 4)) : 255;
}); });
c.context.putImageData(c.imageData, 0, 0); c.context.putImageData(c.imageData, 0, 0);
// Get the PNG data from the data URL and decode it from base64. // Get the PNG data from the data URL and decode it from base64.
str = atob(c.canvas.toDataURL().split(',')[1]); string = atob(c.canvas.toDataURL().split(',')[1]);
// Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4 // Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4
// bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29 // bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29
// (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum), // (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum),
// keep the rest (IDAT chunks), discard the last 12 bytes (IEND chunk). // keep the rest (IDAT chunks), discard the last 12 bytes (IEND chunk).
data = str.slice(16, 20) + str.slice(29, 33); data = string.slice(16, 20) + string.slice(29, 33);
idat = str.slice(33, -45); idat = string.slice(33, -12);
while (idat) { while (idat) {
// Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes // Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes
// data and 4 bytes checksum. We can discard the name parts. // data and 4 bytes checksum. We can discard the name parts.
len = idat.slice(0, 4); length = idat.slice(0, 4);
data += len + idat.slice(8, 12 + (len = Ox.decodeBase256(len))); data += length + idat.slice(8, 12 + (
idat = idat.slice(12 + len); length = Ox.decodeBase256(length)
));
idat = idat.slice(12 + length);
} }
// Allow for async use, symmetrical to Ox.decodeDeflate
callback && callback(data); callback && callback(data);
return data; return data;
}; };
/*@ /*@
Ox.decodeDeflate <f> Decodes an deflate-encoded string Ox.decodeDeflate <f> Decodes an deflate-encoded string
Since PNGs are deflate-encoded, the <code>canvas</code> object's Since PNGs are deflate-encoded, the <code>canvas</code> object's
<code>drawImage</code> method provides an efficient implementation. The <code>drawImage</code> method provides an efficient implementation. The
string will be wrapped as a PNG dataURL, encoded as base64, and drawn string will be wrapped as a PNG dataURL, encoded as base64, and drawn
@ -228,65 +190,67 @@
str <s> The string to be decoded str <s> The string to be decoded
callback <f> Callback function callback <f> Callback function
str <s> The decoded string str <s> The decoded string
@*/ @*/
Ox.decodeDeflate = function(str, callback) { Ox.decodeDeflate = function(string, callback) {
var image = new Image(), var image = new Image(),
// PNG file signature and IHDR chunk // PNG file signature and IHDR chunk
data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR' data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR'
+ str.slice(0, 4) + '\u0000\u0000\u0000\u0001' + string.slice(0, 4) + '\u0000\u0000\u0000\u0001'
+ '\u0008\u0006\u0000\u0000\u0000' + str.slice(4, 8), + '\u0008\u0006\u0000\u0000\u0000' + string.slice(4, 8),
// IDAT chunks // IDAT chunks
idat = str.slice(8), len; idat = string.slice(8), length;
function error() { function error() {
throw new RangeError('Deflate codec can\'t decode data.'); throw new RangeError('Deflate codec can\'t decode data.');
} }
while (idat) { while (idat) {
// Reinsert the IDAT chunk names // Reinsert the IDAT chunk names
len = idat.slice(0, 4); length = idat.slice(0, 4);
data += len + 'IDAT' + idat.slice(4, 8 + (len = Ox.decodeBase256(len))); data += length + 'IDAT' + idat.slice(4, 8 + (
idat = idat.slice(8 + len); length = Ox.decodeBase256(length)
));
idat = idat.slice(8 + length);
} }
// IEND chunk // IEND chunk
data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082'; data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082';
// Unfortunately, we can't synchronously set the source of an image, // Unfortunately, we can't synchronously set the source of an image,
// draw it onto a canvas, and read its data. // draw it onto a canvas, and read its data.
image.onload = function() { image.onload = function() {
str = Ox.toArray(Ox.canvas(image).data).map(function(v, i) { string = Ox.toArray(Ox.canvas(image).data).map(function(value, index) {
// Read one character per RGB byte, ignore ALPHA. // Read one character per RGB byte, ignore ALPHA.
return i % 4 < 3 ? Ox.char(v) : ''; return index % 4 < 3 ? Ox.char(value) : '';
}).join(''); }).join('');
try { try {
// Parse the first byte as number of bytes to chop at the end, // Parse the first byte as number of bytes to chop at the end,
// and the rest, without these bytes, as an UTF8-encoded string. // and the rest, without these bytes, as an UTF8-encoded string.
str = Ox.decodeUTF8(str.slice(1, -str.CharCodeAt(0))); string = Ox.decodeUTF8(string.slice(1, -string.charCodeAt(0)));
} catch (e) { } catch (e) {
error(); error();
} }
callback(str); callback(string);
} }
image.onerror = error; image.onerror = error;
image.src = 'data:image/png;base64,' + btoa(data); image.src = 'data:image/png;base64,' + btoa(data);
}; };
/*@ /*@
Ox.encodeHTML <f> HTML-encodes a string Ox.encodeHTML <f> HTML-encodes a string
> Ox.encodeHTML('\'<"&">\'') > Ox.encodeHTML('\'<"&">\'')
'&apos;&lt;&quot;&amp;&quot;&gt;&apos;' '&apos;&lt;&quot;&amp;&quot;&gt;&apos;'
> Ox.encodeHTML('äbçdê') > Ox.encodeHTML('äbçdê')
'&#x00E4;b&#x00E7;d&#x00EA;' '&#x00E4;b&#x00E7;d&#x00EA;'
@*/ @*/
Ox.encodeHTML = function(str) { Ox.encodeHTML = function(str) {
return Ox.map(str.toString(), function(v) { return Ox.map(str.toString(), function(v) {
var code = v.charCodeAt(0); var code = v.charCodeAt(0);
return code < 128 return code < 128
? (v in Ox.HTML_ENTITIES ? Ox.HTML_ENTITIES[v] : v) ? (v in Ox.HTML_ENTITIES ? Ox.HTML_ENTITIES[v] : v)
: '&#x' + Ox.pad(code.toString(16).toUpperCase(), 4) + ';'; : '&#x' + Ox.pad(code.toString(16).toUpperCase(), 4) + ';';
}); });
}; };
/*@ /*@
Ox.decodeHTML <f> Decodes an HTML-encoded string Ox.decodeHTML <f> Decodes an HTML-encoded string
> Ox.decodeHTML('&apos;&lt;&quot;&amp;&quot;&gt;&apos;') > Ox.decodeHTML('&apos;&lt;&quot;&amp;&quot;&gt;&apos;')
'\'<"&">\'' '\'<"&">\''
> Ox.decodeHTML('&#x0027;&#x003C;&#x0022;&#x0026;&#x0022;&#x003E;&#x0027;') > Ox.decodeHTML('&#x0027;&#x003C;&#x0022;&#x0026;&#x0022;&#x003E;&#x0027;')
@ -297,33 +261,33 @@
'äbçdê' 'äbçdê'
> Ox.decodeHTML('<b>bold</b>') > Ox.decodeHTML('<b>bold</b>')
'<b>bold</b>' '<b>bold</b>'
@*/ @*/
Ox.decodeHTML = function(str) { Ox.decodeHTML = function(str) {
// relies on dom, but shorter than using this: // relies on dom, but shorter than using this:
// http://www.w3.org/TR/html5/named-character-references.html // http://www.w3.org/TR/html5/named-character-references.html
return Ox.decodeHTMLEntities(Ox.element('<div>').html(str).html()); return Ox.decodeHTMLEntities(Ox.element('<div>').html(str).html());
}; };
Ox.encodeHTMLEntities = function(str) { Ox.encodeHTMLEntities = function(str) {
return str.replace( return str.replace(
new RegExp('(' + Object.keys(Ox.HTML_ENTITIES).join('|') + ')', 'g'), new RegExp('(' + Object.keys(Ox.HTML_ENTITIES).join('|') + ')', 'g'),
function(match) { function(match) {
return Ox.HTML_ENTITIES[match]; return Ox.HTML_ENTITIES[match];
} }
); );
}; };
Ox.decodeHTMLEntities = function(str) { Ox.decodeHTMLEntities = function(str) {
return str.replace( return str.replace(
new RegExp('(' + Ox.values(Ox.HTML_ENTITIES).join('|') + ')', 'g'), new RegExp('(' + Ox.values(Ox.HTML_ENTITIES).join('|') + ')', 'g'),
function(match) { function(match) {
return Ox.keyOf(Ox.HTML_ENTITIES, match); return Ox.keyOf(Ox.HTML_ENTITIES, match);
} }
); );
}; };
/*@ /*@
Ox.encodeUTF8 <f> Encodes a string as UTF-8 Ox.encodeUTF8 <f> Encodes a string as UTF-8
see http://en.wikipedia.org/wiki/UTF-8 see http://en.wikipedia.org/wiki/UTF-8
(string) -> <s> UTF-8 encoded string (string) -> <s> UTF-8 encoded string
string <s> Any string string <s> Any string
@ -331,8 +295,8 @@
"YES" "YES"
> Ox.encodeUTF8("¥€$") > Ox.encodeUTF8("¥€$")
"\u00C2\u00A5\u00E2\u0082\u00AC\u0024" "\u00C2\u00A5\u00E2\u0082\u00AC\u0024"
@*/ @*/
Ox.encodeUTF8 = function(str) { Ox.encodeUTF8 = function(str) {
return Ox.map(str, function(chr) { return Ox.map(str, function(chr) {
var code = chr.charCodeAt(0), var code = chr.charCodeAt(0),
str = ''; str = '';
@ -348,10 +312,10 @@
} }
return str; return str;
}); });
}; };
/*@ /*@
Ox.decodeUTF8 <f> Decodes an UTF-8-encoded string Ox.decodeUTF8 <f> Decodes an UTF-8-encoded string
see http://en.wikipedia.org/wiki/UTF-8 see http://en.wikipedia.org/wiki/UTF-8
(utf8) -> <s> string (utf8) -> <s> string
utf8 <s> Any UTF-8-encoded string utf8 <s> Any UTF-8-encoded string
@ -359,22 +323,27 @@
'YES' 'YES'
> Ox.decodeUTF8('\u00C2\u00A5\u00E2\u0082\u00AC\u0024') > Ox.decodeUTF8('\u00C2\u00A5\u00E2\u0082\u00AC\u0024')
'¥€$' '¥€$'
@*/ @*/
Ox.decodeUTF8 = function(str) { Ox.decodeUTF8 = function(string) {
var code, var code, i = 0, length = string.length, ret = '';
i = 0, function error(byte, position) {
len = str.length, throw new RangeError(
ret = ''; 'UTF-8 codec can\'t decode byte 0x' +
while (i < len) { byte.toString(16).toUpperCase() + ' at position ' + position
code = Ox.range(3).map(function(o) { );
return str.charCodeAt(i + o); }
}); while (i < length) {
code = [
string.charCodeAt(i),
string.charCodeAt(i + 1),
string.charCodeAt(i + 2)
];
if (code[0] <= 128) { if (code[0] <= 128) {
ret += str[i]; ret += string[i];
i++; i++;
} else if ( } else if (
code[0] >= 192 && code[0] < 240 code[0] >= 192 && code[0] < 240
&& i < len - (code[0] < 224 ? 1 : 2) && i < length - (code[0] < 224 ? 1 : 2)
) { ) {
if (code[1] >= 128 && code[1] < 192) { if (code[1] >= 128 && code[1] < 192) {
if (code[0] < 224) { if (code[0] < 224) {
@ -389,16 +358,14 @@
); );
i += 3; i += 3;
} else { } else {
throwUTF8Error(code[2], i + 2); error(code[2], i + 2);
} }
} else { } else {
throwUTF8Error(code[1], i + 1); error(code[1], i + 1);
} }
} else { } else {
throwUTF8Error(code[0], i); error(code[0], i);
} }
} }
return ret; return ret;
}; };
})();