oxjs/build/js/ox.js
2010-07-14 00:38:53 +02:00

1728 lines
No EOL
47 KiB
JavaScript

// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
Ox = {
//jQuery: jQuery || {}, // fixme: make this private
version: "0.1.2"
};
/*
================================================================================
Core functions
================================================================================
*/
Ox.getset = function(obj, args, callback, context) {
/*
generic getter and setter function
Ox.getset(obj) returns obj
Ox.getset(obj, str) returns obj.str
Ox.getset(obj, {key: val, ...}, callback, context) sets obj.key to val,
calls callback(key, val),
returns context
*/
var obj_ = obj,
args = args || {},
callback = callback || function() {},
context = context || {},
length = args.length,
ret;
if (length == 0) {
// getset()
ret = obj;
} else if (length == 1 && typeof arguments[0] == "string") {
// getset(str)
ret = obj[args[0]]
} else {
// getset(str, val) or getset({str: val, ...})
// translate (str, val) to ({str: val})
args = Ox.makeObject(args);
obj = $.extend(obj, args);
$.each(args, function(key, value) {
if (!obj_ || !obj_[key] || obj_[key] !== value) {
callback(key, value);
}
});
ret = context;
}
return ret;
}
Ox.print = function() {
/*
*/
if (typeof console != "undefined") {
var args = $.makeArray(arguments),
date = new Date;
args.unshift(Ox.formatDate(date, "%H:%M:%S") + "." +
(Ox.pad(+date % 1000, 3)));
console.log.apply(console, args);
}
}
Ox.uid = function() {
/*
>>> Ox.uid() == Ox.uid()
false
*/
var uid = 0;
return function() {
return uid++;
};
}();
Ox.user = function() {
$.get("http://www.maxmind.com/app/locate_my_ip", function(data) {
var arr = data.split("tblProduct1"),
re = />(.+?)<\/td>\n<td class=output align="center">\n(.*?)\n/,
results = {};
arr.shift();
$.each(arr, function(i, v) {
var result = re(v);
results[result[1].replace(/Your |\*/, "")] = result[2];
});
Ox.print(results)
});
return {
document: {
referrer: document.referrer
},
history: {
length: history.length
},
navigator: navigator,
innerHeight: innerHeight,
innerWidth: innerWidth,
screen: screen,
outerHeight: outerHeight,
outerWidth: outerWidth
}
};
/*
================================================================================
Array and Object functions
================================================================================
*/
Ox.avg = function(obj) {
/*
>>> Ox.avg([-1, 0, 1])
0
>>> Ox.avg({"a": 1, "b": 2, "c": 3})
2
*/
return Ox.sum(obj) / Ox.length(obj);
};
Ox.each = function(obj, fn) {
/*
Ox.each() works for arrays, objects and strings,
like $.each(), unlike [].forEach()
>>> Ox.each([0, 1, 2], function(i, v) {})
[0, 1, 2]
>>> Ox.each({a: 1, b: 2, c: 3}, function(k, v) {}).a
1
>>> Ox.each("foo", function(i, v) {})
"foo"
*/
var i;
for (i in obj) {
if (fn(i, obj[i]) === false) {
break;
}
}
return obj;
};
Ox.equals = function() { // unused
/*
... core? type?
*/
var ret = false;
if (obj0.length == obj1.length) {
Ox.each(obj0, function(k, v) {
if (Ox.isObject(v)) {
} else {
}
return ret;
});
}
return ret;
}
Ox.every = function(obj, fn) {
/*
Ox.every() forks for arrays, objects and strings, unlike [].every()
>>> Ox.every([0, 1, 2], function(v, i) { return i == v; })
true
>>> Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
false
>>> Ox.every("foo", function(v) { return v == "f"; })
false
*/
return Ox.filter(Ox.values(obj), fn).length == Ox.length(obj);
};
Ox.filter = function(arr, fn) {
/*
Ox.filter works for arrays and strings, like $.grep(), unlike [].filter()
>>> Ox.filter([2, 1, 0], function(v, i) { return v == i; })
[1]
>>> Ox.filter("210", function(v, i) { return v == i; })
[1]
*/
var i, len = arr.length, ret = [];
for (i = 0; i < len; i++) {
if (fn(arr[i], i)) {
ret.push(arr[i]);
}
}
return ret;
};
Ox.getObjectById = function(arr, id) {
var ret = null;
Ox.each(arr, function(i, v) {
if (v.id == id) {
ret = v;
return false;
}
});
return ret;
};
Ox.getPositionById = function(arr, id) {
var ret = -1;
Ox.each(arr, function(i, v) {
if (v.id == id) {
ret = i;
return false;
}
});
return ret;
};
Ox.keys = function(obj) {
/*
>>> Ox.keys({"a": 1, "b": 2, "c": 3})
["a", "b", "c"]
*/
var keys = [];
Ox.each(obj, function(k) {
keys.push(k);
});
return keys;
};
Ox.length = function(obj) {
/*
>>> Ox.length({"a": 1, "b": 2, "c": 3})
3
*/
var length = 0;
Ox.each(obj, function() {
length++;
});
return length;
};
Ox.makeArray = function(arr) {
/*
like $.makeArray()
>>> Ox.makeArray("foo")
["foo"]
>>> Ox.makeArray(["foo"])
["foo"]
>>> (function() { return Ox.makeArray(arguments); })("foo")
["foo"]
>>> (function() { return Ox.makeArray(arguments); })(["foo"])
["foo"]
*/
var ret = [], i = 0, len = arr.length;
if (Ox.isString(arr)) {
ret = [arr];
} else {
for (i = 0; i < len; i++) {
ret.push(arr[i]);
}
}
return ret;
};
Ox.makeObject = function(arr) {
/*
>>> Ox.makeObject("foo", "bar").foo
"bar"
>>> Ox.makeObject({foo: "bar"}).foo
"bar"
>/>> (function() { return Ox.makeObject(arguments); })("foo", "bar").foo // fixme
"bar"
>/>> (function() { return Ox.makeObject(arguments); })({foo: "bar"}).foo
"bar"
*/
var obj = {};
if (arguments.length == 1) {
obj = arguments[0];
} else {
obj[arguments[0]] = arguments[1];
}
return obj;
};
Ox.map = function(arr, fn) {
/*
Ox.map() works for arrays and strings, like $.map(), unlike [].map()
>>> Ox.map([1, 1, 1], function(v, i) { return v == i; })
[false, true, false]
>>> Ox.map("111", function(v, i) { return v == i; })
[false, true, false]
>>> Ox.map(new Array(3), function(v, i) { return i; })
[0, 1, 2]
*/
var i, len = arr.length, val, ret = [];
for (i = 0; i < len; i++) {
if ((val = fn(arr[i], i)) !== null) {
ret.push(val);
}
}
return ret;
};
Ox.max = function(obj) {
/*
>>> Ox.max([-1, 0, 1])
1
>>> Ox.max({"a": 1, "b": 2, "c": 3})
3
*/
return Math.max.apply(Math, Ox.values(obj));
};
Ox.min = function(obj) {
/*
>>> Ox.min([-1, 0, 1])
-1
>>> Ox.min({"a": 1, "b": 2, "c": 3})
1
*/
return Math.min.apply(Math, Ox.values(obj));
};
Ox.range = function(start, stop, step) {
/*
>>> Ox.range(3)
[0, 1, 2]
>>> Ox.range(3, 0)
[3, 2, 1]
>>> Ox.range(1, 2, 0.5)
[1, 1.5]
*/
stop = arguments.length > 1 ? stop : arguments[0];
start = arguments.length > 1 ? start : 0;
step = step || (start <= stop ? 1 : -1);
var range = [],
i;
for (i = start; step > 0 ? i < stop : i > stop; i += step) {
range.push(i);
}
return range;
};
Ox.serialize = function(obj) {
var arr = [];
Ox.each(obj, function(k, v) {
arr.push(k + "=" + v);
});
return arr.join("&");
};
Ox.shuffle = function(arr) {
/*
>>> Ox.shuffle([1, 2, 3]).length
3
*/
var shuffle = arr;
return shuffle.sort(function() {
return Math.random() - 0.5;
});
};
Ox.some = function(obj, fn) {
/*
Ox.some() works for arrays, objects and strings, unlike [].some()
>>> Ox.some([2, 1, 0], function(i, v) { return i == v; })
true
>>> Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; })
true
>>> Ox.some("foo", function(v) { return v == "f"; })
true
*/
return Ox.filter(Ox.values(obj), fn).length > 0;
};
Ox.sum = function(obj) {
/*
>>> Ox.sum([-1, 0, 1])
0
>>> Ox.sum({"a": 1, "b": 2, "c": 3})
6
*/
var sum = 0;
Ox.each(obj, function(k, v) {
sum += v;
});
return sum;
};
Ox.unserialize = function(str) {
var arr, obj = {};
Ox.each(str.split("&"), function(i, v) {
arr = v.split("=");
obj[arr[0]] = arr[1];
});
return obj;
};
Ox.values = function(obj) {
/*
>>> Ox.values({"a": 1, "b": 2, "c": 3}).join(",")
[1, 2, 3]
>>> Ox.values([1, 2, 3]).join(",")
[1, 2, 3]
*/
var values = [];
Ox.each(obj, function(k, v) {
values.push(v);
});
return values;
};
Ox.zip = function() {
/*
// fixme: Ox.each doesn't work here
>>> Ox.zip([[0, 1], [2, 3], [4, 5]])
[[0, 2, 4], [1, 3, 5]]
>>> Ox.zip([0, 1, 2], [3, 4, 5])
[[0, 3], [1, 4], [2, 5]]
*/
var args = arguments.length == 1 ? arguments[0] : Ox.makeArray(arguments),
arr = [];
args[0].forEach(function(v, i) {
arr[i] = [];
args.forEach(function(v_, i_) {
arr[i].push(v_[i]);
});
});
return arr;
};
/*
================================================================================
Color functions
================================================================================
*/
Ox.hsl = function(rgb) {
/*
>>> Ox.hsl([0, 0, 0])
[0, 0, 0]
>>> Ox.hsl([255, 255, 255])
[0, 0, 1]
>>> Ox.hsl([0, 255, 0])
[120, 1, 0.5]
*/
rgb = rgb.map(function(v) {
return v / 255;
});
var max = Ox.max(rgb),
min = Ox.min(rgb),
hsl = [0, 0, 0];
hsl[2] = 0.5 * (max + min);
if (max == min) {
hsl[0] = 0;
hsl[1] = 0;
} else {
if (max == rgb[0]) {
hsl[0] = (60 * (rgb[1] - rgb[2]) / (max - min) + 360) % 360;
} else if (max == rgb[1]) {
hsl[0] = 60 * (rgb[2] - rgb[0]) / (max - min) + 120;
} else if (max == rgb[2]) {
hsl[0] = 60 * (rgb[0] - rgb[1]) / (max - min) + 240;
}
if (hsl[2] <= 0.5) {
hsl[1] = (max - min) / (2 * hsl[2]);
} else {
hsl[1] = (max - min) / (2 - 2 * hsl[2]);
}
}
return hsl;
};
Ox.rgb = function(hsl) {
/*
>>> Ox.rgb([0, 0, 0])
[0, 0, 0]
>>> Ox.rgb([0, 0, 1])
[255, 255, 255]
>>> Ox.rgb([120, 1, 0.5])
[0, 255, 0]
*/
hsl[0] /= 360;
var rgb = [0, 0, 0],
v1, v2, v3;
if (hsl[1] == 0) {
rgb = [hsl[2], hsl[2], hsl[2]];
} else {
if (hsl[2] < 0.5) {
v2 = hsl[2] * (1 + hsl[1]);
} else {
v2 = hsl[1] + hsl[2] - (hsl[1] * hsl[2]);
}
v1 = 2 * hsl[2] - v2;
rgb.forEach(function(v, i) {
v3 = hsl[0] + (1 - i) * 1/3;
if (v3 < 0) {
v3++;
} else if (v3 > 1) {
v3--;
}
if (v3 < 1/6) {
rgb[i] = v1 + ((v2 - v1) * 6 * v3);
} else if (v3 < 0.5) {
rgb[i] = v2;
} else if (v3 < 2/3) {
rgb[i] = v1 + ((v2 - v1) * 6 * (2/3 - v3));
} else {
rgb[i] = v1;
}
});
}
return rgb.map(function(v) {
return v * 255;
});
};
/*
================================================================================
Date functions
================================================================================
*/
Ox.getFirstDayOfTheYear = function(date) {
/*
Decimal weekday of January 1 (0-6, Sunday as first day)
>>> Ox.getFirstDayOfTheYear(new Date("01/01/00"))
6
*/
var date_ = date ? new Date(date.valueOf()) : new Date();
date_.setMonth(0);
date_.setDate(1);
return date_.getDay();
};
Ox.getDayOfTheYear = function() {
/*
>>> Ox.getDayOfTheYear(new Date("12/31/2004"))
366
*/
var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return function(date) {
date = date || new Date();
var day = date.getDate(),
month = date.getMonth(),
i;
for (i = 0; i < month; i++) {
day += days[i];
}
if (month >= 2 && Ox.isLeapYear(date.getFullYear())) {
day++;
}
return day;
};
}();
Ox.getISODate = function(date) {
/*
>>> Ox.getISODate(new Date("01/01/2000"))
"2000-01-01T00:00:00Z"
*/
return Ox.formatDate(date || new Date(), '%FT%TZ');
};
Ox.getISODay = function(date) {
/*
Decimal weekday (1-7, Monday as first day)
>>> Ox.getISODay(new Date("01/01/2000"))
6
>>> Ox.getISODay(new Date("01/02/2000"))
7
>>> Ox.getISODay(new Date("01/03/2000"))
1
*/
return (date || new Date()).getDay() || 7;
};
Ox.getISOWeek = function(date) {
/*
see http://en.wikipedia.org/wiki/ISO_8601
>>> Ox.getISOWeek(new Date("01/01/2000"))
52
>>> Ox.getISOWeek(new Date("01/02/2000"))
52
>>> Ox.getISOWeek(new Date("01/03/2000"))
1
*/
date = date || new Date();
var date_ = new Date(date.valueOf());
// set date to Thursday of the same week
date_.setDate(date.getDate() - Ox.getISODay(date) + 4);
return Math.floor((Ox.getDayOfTheYear(date_) - 1) / 7) + 1;
};
Ox.getISOYear = function(date) {
/*
see http://en.wikipedia.org/wiki/ISO_8601
>>> Ox.getISOYear(new Date("01/01/2000"))
1999
>>> Ox.getISOYear(new Date("01/02/2000"))
1999
>>> Ox.getISOYear(new Date("01/03/2000"))
2000
*/
date = date || new Date();
var date_ = new Date(date.valueOf());
// set date to Thursday of the same week
date_.setDate(date.getDate() - Ox.getISODay(date) + 4);
return date_.getFullYear();
};
Ox.getTime = function() {
return +new Date();
}
Ox.getTimezoneOffsetString = function(date) {
/*
Time zone offset string (-1200 - +1200)
>>> Ox.getTimezoneOffsetString(new Date("01/01/2000"))
"+0100"
*/
var offset = (date || new Date()).getTimezoneOffset();
return (offset < 0 ? '+' : '-') +
Ox.pad(Math.floor(Math.abs(offset) / 60), 2) +
Ox.pad(Math.abs(offset) % 60, 2);
};
Ox.getWeek = function(date) {
/*
Week of the year (0-53, Sunday as first day)
>>> Ox.getWeek(new Date("01/01/2000"))
0
>>> Ox.getWeek(new Date("01/02/2000"))
1
>>> Ox.getWeek(new Date("01/03/2000"))
1
*/
date = date || new Date();
return Math.floor((Ox.getDayOfTheYear(date) +
Ox.getFirstDayOfTheYear(date) - 1) / 7);
};
Ox.isLeapYear = function(year) {
/*
>>> Ox.isLeapYear(2000)
false
*/
return year % 4 == 0 && year % 400 != 0;
};
/*
================================================================================
DOM functions
================================================================================
*/
Ox.canvas = function() {
// Ox.canvas(img) or Ox.canvas(width, height)
var c = {}, isImage = arguments.length == 1,
image = isImage ? arguments[0] : {
width: arguments[0], height: arguments[1]
};
c.context = (c.canvas = Ox.element("canvas").attr({
width: image.width, height: image.height
})[0]).getContext("2d");
if (isImage) {
c.context.drawImage(image, 0, 0);
}
c.data = (c.imageData = c.context.getImageData(0, 0,
image.width, image.height)).data;
return c;
};
Ox.element = function(str) {
/*
>>> Ox.element("div").attr({id: "foo"}).attr("id")
"foo"
>>> Ox.element("div").html("foo").html()
"foo"
*/
return {
0: str[0] == "#" ? document.getElementById(str.substr(1)) :
document.createElement(str),
attr: function() {
var args, ret, that = this;
if (arguments.length == 1 && Ox.isString(arguments[0])) {
ret = this[0].getAttribute(arguments[0]);
} else {
Ox.each(Ox.makeObject.apply(this, arguments), function(k, v) {
that[0].setAttribute(k, v);
});
ret = this;
}
return ret;
},
html: function(str) {
var ret;
if (Ox.isUndefined(str)) {
ret = this[0].innerHTML;
} else {
this[0].innerHTML = str;
ret = this;
}
return ret;
}
}
};
/*
================================================================================
Encoding functions
================================================================================
*/
(function() {
var aliases = {"I": "1", "L": "1", "O": "0", "U": "V"},
digits = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
function max(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
var xor = 0;
$.each(new Array(8), function(i) { // fixme: Ox.each doesn't work
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.encodeBase32 = function(num) {
// see http://www.crockford.com/wrmg/base32.html
/*
>>> Ox.encodeBase32(15360)
"F00"
>>> Ox.encodeBase32(33819)
"110V"
*/
return Ox.map(num.toString(32), function(char) {
return digits[parseInt(char, 32)];
}).join("");
}
Ox.decodeBase32 = function(str) {
/*
>>> Ox.decodeBase32("foo")
15360
>>> Ox.decodeBase32("ilou")
33819
>>> Ox.decodeBase32("?").toString() // fixme: toString shouldn't be necessary here
"NaN"
*/
return parseInt($.map(str.toUpperCase(), function(char) {
var index = digits.indexOf(aliases[char] || char);
return (index == -1 ? " " : index).toString(32);
}).join(""), 32);
}
Ox.encodeBase64 = function(num) {
/*
>>> Ox.encodeBase64(32394)
"foo"
*/
return btoa(Ox.encodeBase256(num)).replace(/=/g, "");
}
Ox.decodeBase64 = function(str) {
/*
>>> Ox.decodeBase64("foo")
32394
*/
return Ox.decodeBase256(atob(str));
}
Ox.encodeBase128 = function(num) {
/*
>>> Ox.encodeBase128(1685487)
"foo"
*/
var str = "";
while (num) {
str = Ox.char(num & 127) + str;
num >>= 7;
}
return str;
}
Ox.decodeBase128 = function(str) {
/*
>>> Ox.decodeBase128("foo")
1685487
*/
var num = 0, len = str.length;
Ox.each(str, function(i, char) {
num += char.charCodeAt(0) << (len - i - 1) * 7;
});
return num;
}
Ox.encodeBase256 = function(num) {
/*
>>> Ox.encodeBase256(6713199)
"foo"
*/
var str = "";
while (num) {
str = Ox.char(num & 255) + str;
num >>= 8;
}
return str;
}
Ox.decodeBase256 = function(str) {
/*
>>> Ox.decodeBase256("foo")
6713199
*/
var num = 0, len = str.length;
Ox.each(str, function(i, char) {
num += char.charCodeAt(0) << (len - i - 1) * 8;
});
return num;
}
Ox.encodeDeflate = function(str) {
// encodes string, using deflate
/*
in fact, the string is written to the rgb channels of a canvas element,
then the dataURL is decoded from base64, and some head and tail cut off
*/
str = Ox.encodeUTF8(str);
var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), data;
str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str +
Ox.repeat("\u00FF", (4 - len % 4) % 4); // simpler? Ox.pad()?
/* fixme: why does map not work here?
c.data = $.map(c.data, function(v, i) {
return i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
});
*/
for (i = 0; i < c.data.length; i += 1) {
c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
}
c.context.putImageData(c.imageData, 0, 0);
data = atob(c.canvas.toDataURL().split(",")[1]);
//Ox.print("deflate", len, "->", data.length - 20);
return data.substr(8, data.length - 20);
}
Ox.decodeDeflate = function(str) {
var image = new Image();
image.src = "data:image/png;base64," + btoa("\u0089PNG\r\n\u001A\n" +
str + Ox.repeat("\u0000", 4) + "IEND\u00AEB`\u0082");
while (!image.width) {} // wait for image data
str = Ox.map(Ox.canvas(image).data, function(v, i) {
return i % 4 < 3 ? Ox.char(v) : "";
}).join("");
//Ox.print(str.length, "len", Ox.decodeBase256(str.substr(0, 4)), str)
return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))));
}
Ox.encodeHTML = function() {
/*
>>> Ox.encodeHTML("'<\"&\">'")
"&apos;&lt;&quot;&amp;&quot;&gt;&apos;"
>>> Ox.encodeHTML("äbçdê")
"&#x00E4;b&#x00E7;d&#x00EA;"
*/
var entities = {
'"': "&quot;", "&": "&amp;", "'": "&apos;", "<": "&lt;", ">": "&gt;"
};
return function(str) {
return $.map(Array.prototype.slice.call(str), function(v) {
var code = v.charCodeAt(0);
return code < 128 ? (v in entities ? entities[v] : v) :
"&#x" + Ox.pad(code.toString(16).toUpperCase(), 4) + ";";
}).join("");
};
}();
Ox.decodeHTML = function(str) {
/*
>>> Ox.decodeHTML("&apos;&lt;&quot;&amp;&quot;&gt;&apos;")
"'<\"&\">'"
>>> Ox.decodeHTML("&#x0027;&#x003C;&#x0022;&#x0026;&#x0022;&#x003E;&#x0027;")
"'<\"&\">'"
>>> Ox.decodeHTML("&auml;b&ccedil;d&ecirc;")
"äbçdê"
>>> Ox.decodeHTML("&#x00E4;b&#x00E7;d&#x00EA;")
"äbçdê"
*/
// relies on dom, but shorter than using this:
// http://www.w3.org/TR/html5/named-character-references.html
return $("<div/>").html(str)[0].childNodes[0].nodeValue;
};
Ox.encodePNG = function(img, str) {
// encodes string into image, returns new image url
/*
the message is compressed with deflate (by proxy of canvas),
then the string (four bytes length) + (length bytes message)
is encoded bitwise into the r/g/b bytes of all opaque pixels
by flipping, if necessary, the least significant bit, so that
(number of "1"-bits of the byte) % 2 is the bit of the string
wishlist:
- only use deflate if it actually shortens the message
- in deflate, strip and later re-insert the chunk types
- encode a decoy message into the least significant bit
(and flip the second least significant bit, if at all)
- write an extra png chunk containing some key
*/
str = Ox.encodeDeflate(str);
var c = Ox.canvas(img), len = str.length, px = 0;
if (len == 0 || len > max(img.width, img.height)) {
throwPNGError("en")
}
len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0));
Ox.each(Ox.map(len + str, function(byte) {
return Ox.map(new Array(8), function(v, i) {
return byte.charCodeAt(0) >> 7 - i & 1;
}).join("");
}).join(""), function(i, bit) {
var index = parseInt((px = seek(c.data, px)) * 4 + i % 3),
byte = c.data[index];
c.data[index] = bit == xor(byte) ? byte :
byte & 254 | !(byte & 1);
px += i % 3 == 2;
});
c.context.putImageData(c.imageData, 0, 0);
return c.canvas.toDataURL();
}
Ox.decodePNG = function(img) {
// decodes image, returns string
var data = Ox.canvas(img).data, bits = "", str = "",
px = 0, i = 0; len = 4, flag = false;
do {
bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]);
px += i % 3 == 2;
if (++i % 8 == 0) {
str += Ox.char(parseInt(bits, 2));
bits = "";
len--;
if (len == 0 && !flag) {
len = Ox.decodeBase256(str);
if (len <= 0 || len > max(img.width, img.height)) {
Ox.print(len);
throwPNGError("de");
}
str = "";
flag = true;
}
}
} while (len);
try {
return Ox.decodeDeflate(str);
} catch(e) {
Ox.print(e.toString());
throwPNGError("de");
}
}
Ox.encodeUTF8 = function(str) {
/*
see http://en.wikipedia.org/wiki/UTF-8
>>> Ox.encodeUTF8("foo")
"foo"
>>> Ox.encodeUTF8("¥€$")
"\u00C2\u00A5\u00E2\u0082\u00AC\u0024"
*/
return $.map(Array.prototype.slice.call(str), function(char) { // fixme: why not str?
var code = char.charCodeAt(0),
str = "";
if (code < 128) {
str = char;
} else if (code < 2048) {
str = String.fromCharCode(code >> 6 | 192) +
String.fromCharCode(code & 63 | 128);
} else {
str = String.fromCharCode(code >> 12 | 224) +
String.fromCharCode(code >> 6 & 63 | 128) +
String.fromCharCode(code & 63 | 128);
}
return str;
}).join("");
}
Ox.decodeUTF8 = function(str) {
/*
>>> Ox.decodeUTF8("foo")
"foo"
>>> Ox.decodeUTF8("\u00C2\u00A5\u00E2\u0082\u00AC\u0024")
"¥€$"
*/
var bytes = $.map(str, function(v) {
return v.charCodeAt(0);
}),
i = 0,
len = str.length,
str = "";
while (i < len) {
if (bytes[i] <= 128) {
str += String.fromCharCode(bytes[i]);
i++;
} else if (bytes[i] >= 192 && bytes[i] < 240 &&
i < len - (bytes[i] < 224 ? 1 : 2)) {
if (bytes[i + 1] >= 128 && bytes[i + 1] < 192) {
if (bytes[i] < 224) {
str += String.fromCharCode((bytes[i] & 31) << 6 |
bytes[i + 1] & 63);
i += 2;
} else if (bytes[i + 2] >= 128 && bytes[i + 2] < 192) {
str += String.fromCharCode((bytes[i] & 15) << 12 |
(bytes[i + 1] & 63) << 6 | bytes[i + 2] & 63);
i += 3;
} else {
throwUTF8Error(bytes[i + 2], i + 2);
}
} else {
throwUTF8Error(bytes[i + 1], i + 1);
}
} else {
throwUTF8Error(bytes[i], i);
}
}
return str;
};
})();
/*
================================================================================
Format functions
================================================================================
*/
Ox.formatDate = function() {
/*
See http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/strftime.3.html
and http://en.wikipedia.org/wiki/ISO_8601
>>> _date = new Date("01/02/05 00:03:04")
"Sun Jan 02 2005 00:03:04 GMT+0100 (CET)"
>>> Ox.formatDate(_date, "%A") // Full weekday
"Sunday"
>>> Ox.formatDate(_date, "%a") // Abbreviated weekday
"Sun"
>>> Ox.formatDate(_date, "%B") // Full month
"January"
>>> Ox.formatDate(_date, "%b") // Abbreviated month
"Jan"
>>> Ox.formatDate(_date, "%C") // Century
"20"
>>> Ox.formatDate(_date, "%c") // US time and date
"01/02/05 12:03:04 AM"
>>> Ox.formatDate(_date, "%D") // US date
"01/02/05"
>>> Ox.formatDate(_date, "%d") // Zero-padded day of the month
"02"
>>> Ox.formatDate(_date, "%e") // Space-padded day of the month
" 2"
>>> Ox.formatDate(_date, "%F") // Date
"2005-01-02"
>>> Ox.formatDate(_date, "%G") // Full ISO-8601 year
"2004"
>>> Ox.formatDate(_date, "%g") // Abbreviated ISO-8601 year
"04"
>>> Ox.formatDate(_date, "%H") // Zero-padded hour (24-hour clock)
"00"
>>> Ox.formatDate(_date, "%h") // Abbreviated month
"Jan"
>>> Ox.formatDate(_date, "%I") // Zero-padded hour (12-hour clock)
"12"
>>> Ox.formatDate(_date, "%j") // Zero-padded day of the year
"002"
>>> Ox.formatDate(_date, "%k") // Space-padded hour (24-hour clock)
" 0"
>>> Ox.formatDate(_date, "%l") // Space-padded hour (12-hour clock)
"12"
>>> Ox.formatDate(_date, "%M") // Zero-padded minute
"03"
>>> Ox.formatDate(_date, "%m") // Zero-padded month
"01"
>>> Ox.formatDate(_date, "%n") // Newline
"\n"
>>> Ox.formatDate(_date, "%p") // AM or PM
"AM"
>>> Ox.formatDate(_date, "%Q") // Quarter of the year
"1"
>>> Ox.formatDate(_date, "%R") // Zero-padded hour and minute
"00:03"
>>> Ox.formatDate(_date, "%r") // US time
"12:03:04 AM"
>>> Ox.formatDate(_date, "%S") // Zero-padded second
"04"
>>> Ox.formatDate(_date, "%s") // Number of seconds since the Epoch
"1104620584"
>>> Ox.formatDate(_date, "%T") // Time
"00:03:04"
>>> Ox.formatDate(_date, "%t") // Tab
"\t"
>>> Ox.formatDate(_date, "%U") // Zero-padded week of the year (00-53, Sunday as first day)
"01"
>>> Ox.formatDate(_date, "%u") // Decimal weekday (1-7, Monday as first day)
"7"
>>> Ox.formatDate(_date, "%V") // Zero-padded ISO-8601 week of the year
"53"
>>> Ox.formatDate(_date, "%v") // Formatted date
" 2-Jan-2005"
>>> Ox.formatDate(_date, "%W") // Zero-padded week of the year (00-53, Monday as first day)
"00"
>>> Ox.formatDate(_date, "%w") // Decimal weekday (0-6, Sunday as first day)
"0"
>>> Ox.formatDate(_date, "%X") // US time
"12:03:04 AM"
>>> Ox.formatDate(_date, "%x") // US date
"01/02/05"
>>> Ox.formatDate(_date, "%Y") // Full year
"2005"
>>> Ox.formatDate(_date, "%y") // Abbreviated year
"05"
>>> Ox.formatDate(_date, "%Z") // Time zone name
"CET"
>>> Ox.formatDate(_date, "%z") // Time zone offset
"+0100"
>>> Ox.formatDate(_date, "%+") // Formatted date and time
"Sun Jan 2 00:03:04 CET 2005"
>>> Ox.formatDate(_date, "%%")
"%"
>>> delete _date
true
>>> Ox.formatDate(new Date("01/01/2000"), "%W")
"00"
>>> Ox.formatDate(new Date("01/02/2000"), "%W")
"00"
>>> Ox.formatDate(new Date("01/03/2000"), "%W")
"01"
*/
var ampm = ["AM", "PM"],
days = ["Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"],
months = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"],
format = [
["%", function() {return "%{%}";}],
["c", function() {return "%x %X";}],
["X", function() {return "%r";}],
["x", function() {return "%D";}],
["D", function() {return "%m/%d/%y";}],
["F", function() {return "%Y-%m-%d";}],
["h", function() {return "%b";}],
["R", function() {return "%H:%M";}],
["r", function() {return "%I:%M:%S %p";}],
["T", function() {return "%H:%M:%S";}],
["v", function() {return "%e-%b-%Y";}],
["\\+", function() {return "%a %b %e %H:%M:%S %Z %Y";}],
["A", function(d) {return days[(d.getDay() + 6) % 7];}],
["a", function(d) {return days[(d.getDay() + 6) % 7].toString().substr(0, 3);}],
["B", function(d) {return months[d.getMonth()];}],
["b", function(d) {return months[d.getMonth()].toString().substr(0, 3);}],
["C", function(d) {return d.getFullYear().toString().substr(0, 2);}],
["d", function(d) {return Ox.pad(d.getDate(), 2);}],
["e", function(d) {return Ox.pad(d.getDate(), 2, " ");}],
["G", function(d) {return Ox.getISOYear(d);}],
["g", function(d) {return Ox.getISOYear(d).toString().substr(-2);}],
["H", function(d) {return Ox.pad(d.getHours(), 2);}],
["I", function(d) {return Ox.pad((d.getHours() + 11) % 12 + 1, 2);}],
["j", function(d) {return Ox.pad(Ox.getDayOfTheYear(d), 3);}],
["k", function(d) {return Ox.pad(d.getHours(), 2, " ");}],
["l", function(d) {return Ox.pad(((d.getHours() + 11) % 12 + 1), 2, " ");}],
["M", function(d) {return Ox.pad(d.getMinutes(), 2);}],
["m", function(d) {return Ox.pad((d.getMonth() + 1), 2);}],
["p", function(d) {return ampm[Math.floor(d.getHours() / 12)];}],
["Q", function(d) {return Math.floor(d.getMonth() / 4) + 1;}],
["S", function(d) {return Ox.pad(d.getSeconds(), 2);}],
["s", function(d) {return Math.floor(d.getTime() / 1000);}],
["U", function(d) {return Ox.pad(Ox.getWeek(d), 2);}],
["u", function(d) {return Ox.getISODay(d);}],
["V", function(d) {return Ox.pad(Ox.getISOWeek(d), 2);}],
["W", function(d) {return Ox.pad(Math.floor((Ox.getDayOfTheYear(d) +
(Ox.getFirstDayOfTheYear(d) || 7) - 2) / 7), 2);}],
["w", function(d) {return d.getDay();}],
["Y", function(d) {return d.getFullYear();}],
["y", function(d) {return d.getFullYear().toString().substr(-2);}],
["Z", function(d) {return d.toString().split("(")[1].replace(")", "");}],
["z", function(d) {return Ox.getTimezoneOffsetString(d);}],
["n", function() {return "\n";}],
["t", function() {return "\t";}],
["\\{%\\}", function() {return "%";}]
];
$.each(format, function(i, v) {
format[i][0] = new RegExp("%" + v[0] + "", "g");
});
return function(date, str) {
str = str || date;
date = arguments.length == 2 ? date : new Date();
$.each(format, function(i, v) {
str = str.replace(v[0], v[1](date));
});
return str;
};
}();
Ox.formatDuration = function(sec, dec, format) {
/*
>>> Ox.formatDuration(123456.789, 3)
1:10:17:36.789
>>> Ox.formatDuration(12345.6789)
03:25:46
>>> Ox.formatDuration(12345.6789, true)
0:03:25:46
>>> Ox.formatDuration(3599.999, 3)
00:59:59.999
>>> Ox.formatDuration(3599.999)
01:00:00
*/
var format = arguments.length == 3 ? format : (Ox.isString(dec) ? dec : "short"),
dec = (arguments.length == 3 || Ox.isNumber(dec)) ? dec : 0,
sec = dec ? sec : Math.round(sec),
val = [
Math.floor(sec / 31536000),
Math.floor(sec % 31536000 / 86400),
Math.floor(sec % 86400 / 3600),
Math.floor(sec % 3600 / 60),
format == "short" ? Ox.formatNumber(sec % 60, dec) : sec % 60
],
str = {
medium: ["y", "d", "h", "m", "s"],
long: ["year", "day", "hour", "minute", "second"]
},
pad = [0, 3, 2, 2, dec ? dec + 3 : 2];
while (!val[0] && val.length > (format == "short" ? 3 : 1)) {
val.shift();
str.medium.shift();
str.long.shift();
pad.shift();
}
while (format != "short" && !val[val.length - 1] && val.length > 1) {
val.pop();
str.medium.pop();
str.long.pop();
}
return $.map(val, function(v, i) {
return format == "short" ? Ox.pad(v, pad[i]) :
v + (format == "long" ? " " : "") + str[format][i] +
(format == "long" && v != 1 ? "s" : "");
}).join(format == "short" ? ":" : " ");
};
Ox.formatNumber = function(num, dec) {
/*
>>> Ox.formatNumber(123456789, 3)
"123,456,789.000"
>>> Ox.formatNumber(-2000000 / 3, 3)
"-666,666.667"
>>> Ox.formatNumber(666666.666)
"666,667"
*/
var str = Math.abs(num).toFixed(dec || 0),
spl = str.split("."),
arr = [];
while (spl[0]) {
arr.unshift(spl[0].substr(-3));
spl[0] = spl[0].substr(0, spl[0].length - 3);
}
spl[0] = arr.join(",");
return (num < 0 ? "-" : "") + spl.join(".");
};
Ox.formatValue = function(num, str) {
/*
>>> Ox.formatValue(0, "B")
???
>>> Ox.formatValue(123456789, "B")
???
*/
var arr = ["K", "M", "G", "T", "P"],
len = arr.length,
val = "";
$.each(arr, function(i, v) {
if (num < Math.pow(1024, i + 2) || i == len - 1) {
val = Ox.formatNumber(num / Math.pow(1024, i + 1), i) + " " + v + str;
return false;
}
});
return val;
};
/*
================================================================================
Math functions
================================================================================
*/
Ox.asinh = function(x) {
/*
fixme: no test
*/
return Math.log(x + Math.sqrt(x * x + 1));
};
Ox.deg = function(rad) {
/*
>>> Ox.deg(2 * Math.PI)
360
*/
return rad * 180 / Math.PI;
};
Ox.limit = function(num, min, max) {
/*
>>> Ox.limit(1, 2, 3)
2
>>> Ox.limit(2, 1)
1
*/
Ox.print(num, min, max)
var len = arguments.length;
max = arguments[len - 1];
min = len == 3 ? min : 0;
return Math.min(Math.max(num, min), max);
};
Ox.log = function(x, base) {
/*
>>> Ox.log(100, 10)
2
>>> Ox.log(Math.E)
1
*/
return Math.log(x) / Math.log(base || Math.E);
};
Ox.rad = function(deg) {
/*
>>> Ox.rad(360)
2 * Math.PI
*/
return deg * Math.PI / 180;
};
Ox.random = function() {
/*
>>> Ox.random(3) in {0: 0, 1: 0, 2: 0, 3: 0}
true
>>> Ox.random(1, 2) in {1: 0, 2: 0}
true
*/
var len = arguments.length,
min = len == 1 ? 0 : arguments[0],
max = arguments[len - 1];
return min + parseInt(Math.random() * (max - min + 1));
};
Ox.round = function(num, dec) {
/*
>>> Ox.round(2 / 3, 6)
0.666667
>>> Ox.round(1 / 2, 3)
0.5
*/
var pow = Math.pow(10, dec || 0);
return Math.round(num * pow) / pow;
};
Ox.sinh = function(x) {
/*
fixme: no test
*/
return (Math.exp(x) - Math.exp(-x)) / 2;
};
/*
================================================================================
String functions
================================================================================
*/
Ox.basename = function(str) {
/*
fixme: this should go into Path functions
>>> Ox.basename("foo/bar/foo.bar")
"foo.bar"
>>> Ox.basename("foo.bar")
"foo.bar"
*/
return str.replace(/^.*[\/\\]/g, "");
};
Ox.char = String.fromCharCode;
Ox.clean = function(str) {
/*
>>> Ox.clean("foo bar")
"foo bar"
>>> Ox.clean(" foo bar ")
"foo bar"
*/
return Ox.trim(str.replace(/\s+/g, " "));
};
Ox.endsWith = function(str, sub) {
/*
>>> Ox.endsWith("foobar", "bar")
true
*/
return str.substr(-sub.length) === sub;
};
Ox.pad = function(str, len, pad, pos) {
/*
>>> Ox.pad(1, 2)
"01"
>>> Ox.pad("abc", 6, ".", "right")
"abc..."
>>> Ox.pad("foobar", 3, ".", "right")
"foo"
>>> Ox.pad("abc", 6, "123456", "right")
"abc123"
>>> Ox.pad("abc", 6, "123456", "left")
"456abc"
*/
str = str.toString().substr(0, len);
pad = Ox.repeat(pad || "0", len - str.length);
pos = pos || "left";
str = pos == "left" ? pad + str : str + pad;
str = pos == "left" ? str.substr(str.length - len, str.length) :
str.substr(0, len);
return str;
};
Ox.repeat = function(str, num) {
/*
>>> Ox.repeat(1, 3)
"111"
>>> Ox.repeat("foo", 3)
"foofoofoo"
*/
return num >= 1 ? new Array(num + 1).join(str.toString()) : "";
};
Ox.reverse = function(str) {
/*
Ox.reverse("foo")
oof
*/
return str.split("").reverse().join("");
};
Ox.startsWith = function(str, sub) {
/*
>>> Ox.startsWith("foobar", "foo")
true
*/
return str.substr(0, sub.length) === sub;
};
Ox.stripTags = function(str) {
/*
>>> Ox.stripTags("f<span>o</span>o")
foo
*/
return str.replace(/(<.*?>)/gi, "");
};
Ox.toCamelCase = function(str) {
/*
>>> Ox.toCamelCase("foo-bar-baz")
"fooBarBaz"
>>> Ox.toCamelCase("foo/bar/baz")
"fooBarBaz"
>>> Ox.toCamelCase("foo_bar_baz")
"fooBarBaz"
*/
return str.replace(/[\-_\/][a-z]/g, function(str) {
return str[1].toUpperCase();
});
};
Ox.toDashes = function(str) {
/*
>>> Ox.toDashes("fooBarBaz")
"foo-bar-baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '-' + str.toLowerCase();
});
};
Ox.toSlashes = function(str) {
/*
>>> Ox.toSlashes("fooBarBaz")
"foo/bar/baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '/' + str.toLowerCase();
});
};
Ox.toTitleCase = function(str) {
/*
>>> Ox.toTitleCase("foo")
"Foo"
>>> Ox.toTitleCase("Apple releases iPhone, IBM stock plummets")
"Apple Releases iPhone, IBM Stock Plummets"
*/
return $.map(str.split(" "), function(v) {
var sub = v.substr(1),
low = sub.toLowerCase();
if (sub == low) {
v = v.substr(0, 1).toUpperCase() + low;
}
return v;
}).join(" ");
};
Ox.toUnderscores = function(str) {
/*
>>> Ox.toUnderscores("fooBarBaz")
"foo_bar_baz"
*/
return str.replace(/[A-Z]/g, function(str) {
return '_' + str.toLowerCase();
});
};
Ox.trim = function(str) { // is in jQuery
/*
Ox.trim(" foo ")
"foo"
*/
return str.replace(/^\s+|\s+$/g, "");
};
Ox.truncate = function(str, len, pad, pos) {
/*
>>> Ox.truncate("anticonstitutionellement", 16, "...", "center")
anticon...lement
*/
var pad = pad || {},
pos = pos || "right",
strlen = str.length,
padlen = pad.length,
left, right;
if (strlen > len) {
if (pos == "left") {
str = pad + str.substr(padlen + strlen - len);
} else if (pos == "center") {
left = Math.ceil((len - padlen) / 2);
right = Math.floor((len - padlen) / 2);
str = str.substr(0, left) + pad + str.substr(-right);
} else if (pos == "right") {
str = str.substr(0, len - padlen) + pad;
}
}
return str;
};
Ox.wordwrap = function(str, len, sep, bal, spa) {
/*
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, "<br/>"")
Anticonstitutionellement, <br/>Paris s'eveille
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, "<br/>")
Anticonstitution<br />ellement, Paris <br/>s'eveille
>>> Ox.wordwrap("These are short words", 16, "<br/>", true)
These are <br/>short words
*/
var len = len || 80,
sep = sep || "<br/>",
bal = bal || false,
spa = spa || true,
words = str.split(" "),
lines;
if (bal) {
// balance lines: test if same number of lines
// can be achieved with a shorter line length
lines = Ox.wordwrap(str, len, sep, false).split(sep);
if (lines.length > 1) {
// test shorter line, unless
// that means cutting a word
var max = Ox.max($.map(words, function(word) {
return word.length;
}));
while (len > max) {
len--;
if (Ox.wordwrap(str, len, sep, false).split(sep).length > lines.length) {
len++;
break;
}
}
}
}
lines = [""];
$.each(words, function(i, word) {
if ((lines[lines.length - 1] + word + " ").length <= len + 1) {
// word fits in current line
lines[lines.length - 1] += word + " ";
} else {
if (word.length <= len) {
// word fits in next line
lines.push(word + " ");
} else {
// word is longer than line
var chr = len - lines[lines.length - 1].length;
lines[lines.length - 1] += word.substr(0, chr);
for (var pos = chr; pos < word.length; pos += len) {
lines.push(word.substr(pos, len));
}
lines[lines.length - 1] += " ";
}
}
});
if (!spa) {
lines = $.map(lines, function(line) {
return Ox.trim(line);
});
}
return Ox.trim(lines.join(sep));
};
/*
================================================================================
Type functions
================================================================================
*/
Ox.isArray = function(val) { // is in jQuery
/*
>>> Ox.isArray([])
true
*/
return val instanceof Array;
}
Ox.isBoolean = function(val) {
/*
>>> Ox.isBoolean(false)
true
*/
return typeof val == "boolean";
};
Ox.isDate = function(val) {
/*
>>> Ox.isDate(new Date())
true
*/
return val instanceof Date;
};
Ox.isFunction = function(val) { // is in jQuery
/*
>>> Ox.isFunction(function() {})
true
>>> Ox.isFunction(/ /)
false
*/
return typeof val == "function" && !Ox.isRegExp(val);
};
Ox.isNull = function(val) {
/*
>>> Ox.isNull(null)
true
*/
return val === null;
};
Ox.isNumber = function(val) {
/*
>>> Ox.isNumber(0)
true
>>> Ox.isNumber(Infinity)
false
>>> Ox.isNumber(NaN)
false
*/
return typeof val == "number" && isFinite(val);
};
Ox.isObject = function(val) {
/*
>>> Ox.isObject({})
true
>>> Ox.isObject([])
false
>>> Ox.isObject(new Date())
false
>>> Ox.isObject(null)
false
*/
return typeof val == "object" && !$.isArray(val) &&
!Ox.isDate(val) && !Ox.isNull(val);
};
Ox.isRegExp = function(val) {
/*
>>> Ox.isRegExp(/ /)
true
*/
return val instanceof RegExp;
};
Ox.isString = function(val) {
/*
>>> Ox.isString("")
true
*/
return typeof val == "string";
};
Ox.isUndefined = function(val) {
/*
>>> Ox.isUndefined()
true
*/
return typeof val == "undefined";
};