fixing encoding functions (deflate, png)

This commit is contained in:
rlx 2011-09-05 23:34:29 +00:00
parent 318e2e95b2
commit 816993e1b9
10 changed files with 235 additions and 143 deletions

View file

@ -65,12 +65,11 @@ Ox.Resizebar = function(options, self) {
function dragstart(event, e) {
if (self.options.resizable && !self.options.collapsed) {
Ox.print('DRAGSTART')
self.drag = {
startPos: e[self.clientXY],
startSize: self.options.size
}
} else { Ox.print('NO DRAGSTART r !c', self.options.resizable, !self.options.collapsed) }
}
}
function drag(event, e) {

View file

@ -40,7 +40,7 @@ Ox.DocPage = function(options, self) {
.html(
'<code><b>' + (name || item.name) + '</b> ' +
'&lt;' + item.types.join('&gt;</code> or <code>&lt;') + '&gt; </code>' +
(item['default'] ? 'default: <code>' + item['default'] + ' </code>' : '') +
(item['default'] ? '(default: <code>' + item['default'] + '</code>) ' : '') +
Ox.parseHTML(item.summary)
)
];

View file

@ -106,9 +106,9 @@ Ox.DocPanel = function(options, self) {
}
}
(
docItem.section ?
treeItems[moduleIndex].items[sectionIndex] :
treeItems[moduleIndex]
docItem.section
? treeItems[moduleIndex].items[sectionIndex]
: treeItems[moduleIndex]
).items.push({
id: docItem.name,
title: docItem.name
@ -119,7 +119,7 @@ Ox.DocPanel = function(options, self) {
item.items.sort(sortByTitle);
item.items.forEach(function(subitem) {
subitem.items.sort(sortByTitle);
})
});
});
self.$list = Ox.TreeList({
items: treeItems,
@ -154,7 +154,6 @@ Ox.DocPanel = function(options, self) {
var selected;
if (data.ids.length) {
selected = data.ids[0];
Ox.print('selected', data.ids)
if (selected[0] != '_') {
self.$page = Ox.DocPage({
item: getItemByName(selected)

View file

@ -3,9 +3,9 @@
Ox.Request <o> Basic request handler object
FIXME: options is not a property, just documenting defaults
options <o> Options object
timeout <n|60000> request timeout
type <s|POST> request type, possible values POST, GET, PUT, DELETE
url <s> request url
timeout <n|60000> request timeout
type <s|"POST"> request type, possible values POST, GET, PUT, DELETE
url <s> request url
@*/
Ox.Request = function(options) {
@ -26,8 +26,8 @@ Ox.Request = function(options) {
/*@
cancel <f> cancel pending requests
() -> <u> cancel all requests
(f) -> <u> cancel all requests where function returns true
(n) -> <u> cancel request by id
(fn) -> <u> cancel all requests where function returns true
(id) -> <u> cancel request by id
@*/
cancel: function() {
if (arguments.length == 0) {
@ -47,7 +47,7 @@ Ox.Request = function(options) {
},
/*@
clearCache <f> clear cached results
() -> <u>
() -> <u> ...
@*/
clearCache: function() {
cache = {};

View file

@ -16,7 +16,7 @@ Ox.Range <f:Ox.Element> Range Object
thumbSize <n> minimum width or height of thumb, in px
thumbValue <b> if true, display value on thumb
trackGradient <a> colors
trackImages <s> or <a> one or multiple track background image URLs
trackImages <s|[s]> one or multiple track background image URLs
trackStep <n> 0 (scroll here) or step when clicking track
value <n> initial value
valueNames <a> value names to display on thumb

View file

@ -447,10 +447,10 @@ Ox.List = function(options, self) {
function findCell(e) {
var $element = $(e.target);
while (!$element.hasClass('OxCell') && !$element.hasClass('OxPage') && !$element.is('body')) {
while (!$element.is('.OxCell') && !$element.is('.OxPage') && !$element.is('body')) {
$element = $element.parent();
}
return $element.hasClass('OxCell') ? $element : null;
return $element.is('.OxCell') ? $element : null;
}
function findItemPosition(e) {
@ -458,19 +458,19 @@ Ox.List = function(options, self) {
$parent,
position = -1;
while (
!$element.hasClass('OxTarget') && !$element.hasClass('OxPage')
!$element.is('.OxTarget') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length
) {
$element = $parent;
}
if ($element.hasClass('OxTarget')) {
if ($element.is('.OxTarget')) {
while (
!$element.hasClass('OxItem') && !$element.hasClass('OxPage')
!$element.is('.OxItem') && !$element.is('.OxPage')
&& ($parent = $element.parent()).length
) {
$element = $parent;
}
if ($element.hasClass('OxItem')) {
if ($element.is('.OxItem')) {
position = $element.data('position');
}
}
@ -780,7 +780,7 @@ Ox.List = function(options, self) {
// click on unselected item
select(pos);
}
} else if (self.options.min == 0) {
} else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
// click on empty area
selectNone();
}
@ -850,8 +850,8 @@ Ox.List = function(options, self) {
} else if (self.options.type == 'text' && self.hadFocus) {
$cell = findCell(e);
if ($cell) {
clickable = $cell.hasClass('OxClickable');
editable = $cell.hasClass('OxEditable') && !$cell.hasClass('OxEdit');
clickable = $cell.is('.OxClickable');
editable = $cell.is('.OxEditable') && !$cell.is('.OxEdit');
if (clickable || editable) {
// click on a clickable or editable cell
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
@ -904,8 +904,8 @@ Ox.List = function(options, self) {
} else if (self.options.type == 'text' && hadFocus) {
var $cell = findCell(e),
$element = $cell || self.$items[pos];
clickable = $element.hasClass('OxClickable');
editable = $element.hasClass('OxEditable') && !$element.hasClass('OxEdit');
clickable = $element.is('.OxClickable');
editable = $element.is('.OxEditable') && !$element.is('.OxEdit');
if (clickable || editable) {
if (self.options.sortable && self.listLength > 1) {
clickTimeout = true;
@ -944,7 +944,7 @@ Ox.List = function(options, self) {
self.clickTimeout = 0;
open();
}
} else if (!$(e.target).hasClass('OxToggle') && self.options.min == 0) {
} else if (!$(e.target).is('.OxToggle') && self.options.min == 0) {
selectNone();
}
}

View file

@ -70,7 +70,7 @@ Ox.TreeList = function(options, self) {
}
function constructItem(data) {
var $item = $('<div>'),
var $item = $('<div>'), //.css({width: self.options.width + 'px'}),
padding = (data.level + !data.items) * 16 - 8;
if (data.level || !data.items) {
$('<div>')
@ -161,14 +161,14 @@ Ox.TreeList = function(options, self) {
items = items || self.options.items;
level = level || 0;
items.forEach(function(item, i) {
var item_ = $.extend({
var item_ = Ox.extend({
level: level
}, item, item.items ? {
items: !!item.expanded ?
parseItems(item.items, level + 1) : []
} : {});
ret.push(item_);
item.items && $.merge(ret, item_.items);
item.items && Ox.merge(ret, item_.items);
});
return ret;
}

View file

@ -6,11 +6,11 @@ Ox.ListMap <f:Ox.Element> ListMap Object
(options) -> <f> ListMap Object
(options, self) -> <f> ListMap Object
options <o> Options object
height <n|256>
labels <b|false>
places <f|null>
selected <a|[]>
width <n|256>
height <n|256> height
labels <b|false> labels
places <f|null> places
selected <a|[]> selected
width <n|256> width
self <o> shared private variable
@*/

View file

@ -560,9 +560,9 @@ Ox.Map = function(options, self) {
callback = point;
point = self.map.getCenter();
}
Ox.print('CALLING ZOOM SERVICE', point.lat(), point.lng())
//Ox.print('CALLING ZOOM SERVICE', point.lat(), point.lng())
self.maxZoomService.getMaxZoomAtLatLng(point, function(data) {
Ox.print('ZOOM SERVICE', data.status, data.zoom)
//Ox.print('ZOOM SERVICE', data.status, data.zoom)
callback(data.status == 'OK' ? data.zoom : null);
});
}

View file

@ -40,6 +40,8 @@ Some conventions:
// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
// also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) {
Array.prototype.filter = function(fn, that) {
@ -182,13 +184,14 @@ Ox.load <f> Loads a module
success <b> If true, the module has been loaded successfully
@*/
Ox.load = function(module, options, callback) {
Ox.load = function(/*module[[, options], callback]*/) {
// fixme: no way to load multiple modules
// problem: where do multiple options go?
// [{name: "", options: {}}, {name: "", options: {}}] isn't pretty
var len = arguments.length;
callback = arguments[len - 1];
options = len == 3 ? arguments[1] : {};
var len = arguments.length,
module = arguments[0],
options = len == 3 ? arguments[1] : {},
callback = arguments[len - 1];
Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() {
Ox.load[module](options, callback);
});
@ -209,8 +212,8 @@ Ox.print = function() {
date = new Date();
args.unshift(
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3),
(arguments.callee.caller && arguments.callee.caller.name) ||
'(anonymous)'
(arguments.callee.caller && arguments.callee.caller.name)
|| '(anonymous)'
);
window.console && window.console.log.apply(window.console, args);
return args.join(' ');
@ -401,6 +404,8 @@ Ox.avg <f> Returns the average of an array's values, or an object's properties
0
> Ox.avg({a: 1, b: 2, c: 3})
2
> Ox.avg('avg is 0.1')
0.1
@*/
Ox.avg = function(obj) {
@ -717,8 +722,9 @@ Ox.keys <f> Returns the keys of a collection
[0, 1, 2]
> Ox.keys([1,,3])
[0, 2]
> Ox.keys([,])
[0]
# fixme?
# > Ox.keys([,])
# [0]
> Ox.keys({a: 1, b: 2, c: 3})
['a', 'b', 'c']
> Ox.keys('abc')
@ -887,8 +893,9 @@ Ox.map <f> Transforms the values of an array, object or string
[true, false, false]
> Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; })
[true, true, false]
> Ox.map([,], function(v, i) { return i; })
[0]
# fixme?
# > Ox.map([,], function(v, i) { return i; })
# [0]
@*/
Ox.map = function(obj, fn) {
@ -1731,6 +1738,7 @@ Ox.canvas <function> Generic canvas object
@*/
Ox.canvas = function() {
// Ox.print("CANVAS", arguments)
var c = {}, isImage = arguments.length == 1,
image = isImage ? arguments[0] : {
width: arguments[0], height: arguments[1]
@ -1775,6 +1783,10 @@ Ox.documentReady = (function() {
Ox.element <f> Generic HTML element, mimics jQuery
(str) -> <o> Element object
str <s> Tagname ('<tagname>') or selector ('tagname', '.classname', '#id')
> Ox.element("<div>").addClass("red").hasClass("red")
true
> Ox.element("<div>").addClass("red").removeClass("red").hasClass("red")
false
> Ox.element("<div>").addClass("red").addClass("red")[0].className
"red"
> Ox.element("<div>").attr({id: "red"}).attr("id")
@ -1936,6 +1948,7 @@ Ox.element = function(str) {
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;
@ -2060,43 +2073,99 @@ Ox.element = function(str) {
return num;
}
//@ Ox.encodeDeflate <f> (undocumented)
/*@
Ox.encodeDeflate <f> Encodes a string, using deflate
Since PNGs are deflate-encoded, the <code>canvas</code> object's
<code>toDataURL</code> method provides an efficient implementation.
The string is encoded as UTF-8 and written to the RGB channels of a
canvas element, then the PNG dataURL is decoded from base64, and some
head, tail and chunk names are removed.
(str) -> <s> The encoded string
str <s> The string to be encoded
@*/
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
*/
// Make sure we can encode the full unicode range of characters.
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) {
// We can only safely write to RGB, so we need 1 pixel for 3 bytes.
var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1),
data = '', idat, ihdr;
// Prefix the string with its length, left-padded with 0-bytes to
// length of 4 bytes, and right-pad the result with non-0-bytes to a
// length that is a multiple of 3.
str = Ox.pad(Ox.pad(Ox.encodeBase256(len), 4, '\u0000') + str,
-Math.ceil((4 + len) / 3) * 3, '\u00FF');
Ox.loop(c.data.length, function(i) {
// Write character codes into RGB, and 255 into ALPHA
c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
}
});
c.context.putImageData(c.imageData, 0, 0);
Ox.print(c.canvas.toDataURL())
data = atob(c.canvas.toDataURL().split(',')[1]);
Ox.print('data', data);
return data.substr(8, data.length - 20);
// Get the PNG data from the data URL and decode it from base64.
str = atob(c.canvas.toDataURL().split(',')[1]);
// The first 16 and the last 12 bytes of a PNG are always the same and
// can be discarded, the first 17 remaining bytes are part of the IHDR
// chunk, and the rest are IDAT chunks.
ihdr = Ox.sub(str, 16, 33); idat = Ox.sub(str, 33, -12);
while (idat) {
// Each IDAT chunk consists of 4 bytes length, 4 bytes "IDAT" and
// length bytes data, so we can optimize by removing each "IDAT".
len = idat.substr(0, 4);
data += len + idat.substr(8, 12 + (len = Ox.decodeBase256(len)));
idat = idat.substr(12 + len);
}
return ihdr + data;
}
//@ Ox.decodeDeflate <f> (undocumented)
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');
Ox.print(image.src);
while (!image.width) {} // block until image data is available
str = Ox.map(Ox.canvas(image).data, function(v, i) {
return i % 4 < 3 ? Ox.char(v) : '';
}).join('');
return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))));
/*@
Ox.decodeDeflate <f> Decodes an deflate-encoded string
Since PNGs are deflate-encoded, the <code>canvas</code> object's
<code>drawImage</code> method provides an efficient implementation. The
string will be wrapped as a PNG dataURL, encoded as base64, and drawn
onto a canvas element, then the RGB channels will be read, and the
result will be decoded from UTF8.
(str) -> <u> undefined
str <s> The string to be decoded
callback <f> Callback function
str <s> The decoded string
# Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert)
@*/
Ox.decodeDeflate = function(str, callback) {
function decodeHex(str) {
return str.split(' ').map(function(v) {
return Ox.char(parseInt(v, 16));
}).join('');
}
var image = new Image(),
// The first 16 and the last 12 bytes of a PNG are always the same,
// the first 17 remaining bytes are part of the IHDR chunk, and the
// rest are IDAT chunks.
head = decodeHex('89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52'),
tail = decodeHex('00 00 00 00 49 45 4E 44 AE 42 60 82'),
data = '', ihdr = str.substr(0, 17), idat = str.substr(17), len;
while (idat) {
// Reinsert the IDAT chunk names
len = idat.substr(0, 4);
data += len + 'IDAT' + idat.substr(4, 8 + (len = Ox.decodeBase256(len)));
idat = idat.substr(8 + len);
}
// Unfortunately, we can't synchronously set the source of an image,
// draw it onto a canvas, and read its data.
image.onload = function() {
str = Ox.makeArray(Ox.canvas(image).data).map(function(v, i) {
// Read one character per RGB byte, ignore ALPHA.
return i % 4 < 3 ? Ox.char(v) : '';
}).join('');
callback(
// Parse the first 4 bytes as length and the next length bytes
// as an UTF8-encoded string.
Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))))
);
}
image.onerror = function() {
throw new RangeError('Deflate codec can\'t decode data.')
}
image.src = 'data:image/png;base64,' + btoa(head + ihdr + data + tail);
}
/*@
@ -2131,71 +2200,98 @@ Ox.element = function(str) {
return Ox.element('<div>').html(str)[0].childNodes[0].nodeValue;
};
//@ Ox.encodePNG <f> Encodes a string into an image, returns a new image URL
/*@
Ox.encodePNG <f> Encodes a string into an image, returns a new image
The string is compressed with deflate (by proxy of canvas), prefixed
with its length (four bytes), and encoded bitwise into the red, green
and blue bytes of all fully opaque pixels of the image, by flipping, if
necessary, the least significant bit, so that for every byte, the total
number of bits set to to 1, modulo 2, is the bit that we are encoding.
(img, src) -> <e> An image into which the string has been encoded
img <e> Any JavaScript PNG image object
str <s> The string to be encoded
@*/
Ox.encodePNG = function(img, str) {
var c = Ox.canvas(img), i = 0;
// Compress the string
str = Ox.encodeDeflate(str);
// Prefix the string with its length, as a four-byte value
str = Ox.pad(Ox.encodeBase256(str.length), 4, Ox.char(0)) + str;
// Create an array of bit values
Ox.forEach(Ox.flatten(Ox.map(str, function(chr) {
return Ox.map(Ox.range(8), function(i) {
return chr.charCodeAt(0) >> 7 - i & 1;
});
})), function(bit) {
// Skip all pixels that are not fully opaque
while (i < c.data.length && i % 4 == 0 && c.data[i + 3] < 255) {
i += 4;
}
if (i == c.data.length) {
throw new RangeError('PNG codec can\'t encode data');
}
// If the number of bits set to one, modulo 2 is equal to the bit,
// do nothing, otherwise, flip the least significant bit.
c.data[i] += c.data[i].toString(2).replace(/0/g, '').length % 2
== bit ? 0 : c.data[i] % 2 ? -1 : 1;
i++;
});
c.context.putImageData(c.imageData, 0, 0);
img = new Image();
img.src = c.canvas.toDataURL();
return img;
/*
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); currently broken
str = Ox.encodeUTF8(str);
var c = Ox.canvas(img), len = str.length, px = 0;
if (len == 0 || len > cap(img.width, img.height)) {
throwPNGError('en')
}
len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0));
Ox.forEach(Ox.map(len + str, function(byte) {
return Ox.map(Ox.range(8), function(i) {
return byte.charCodeAt(0) >> 7 - i & 1;
}).join('');
}).join(''), function(bit, i) {
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 <f> Decodes an image, returns a string
Ox.decodePNG = function(img) {
/*@
Ox.decodePNG <f> Decodes an image, returns a string
For every red, green and blue byte of every fully opaque pixel of the
image, one bit, namely the number of bits of the byte set to one, modulo
2, is being read, the result being the string, prefixed with its length
(four bytes), which is decompressed with deflate (by proxy of canvas).
(img, callback) -> <u> undefined
img <e> The image into which the string has been encoded
callback <f> Callback function
str <s> The decoded string
@*/
Ox.decodePNG = function(img, callback) {
var bits = '', data = Ox.canvas(img).data, flag = false, i = 0,
len = 4, max = cap(img.width, img.height), px = 0, str = '';
do {
bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]);
px += i % 3 == 2;
len = 4, str = '';
while (len) {
// Skip all pixels that are not fully opaque
while (i < data.length && i % 4 == 0 && data[i + 3] < 255) {
i += 4;
}
if (i == data.length) {
break;
}
// Read the number of bits set to one, modulo 2
bits += data[i].toString(2).replace(/0/g, '').length % 2;
if (++i % 8 == 0) {
// Every 8 bits, add one byte
str += Ox.char(parseInt(bits, 2));
bits = '';
len--;
if (len == 0 && !flag) {
len = Ox.decodeBase256(str);
if (len <= 0 || len > max) {
Ox.print(len);
throwPNGError('de');
}
str = '';
// When length reaches 0 for the first time,
// decode the string and treat it as the new length
if (--len == 0 && !flag) {
flag = true;
len = Ox.decodeBase256(str);
str = '';
}
}
} while (len);
}
try {
//return Ox.decodeDeflate(str); currently broken
return Ox.decodeUTF8(str);
} catch(e) {
Ox.print(e.toString());
throwPNGError('de');
Ox.decodeDeflate(str, callback);
} catch (e) {
throw new RangeError('PNG codec can\'t decode image');
}
}
@ -3354,7 +3450,7 @@ Ox.doc = (function() {
return indent;
}
function parseItem(str) {
var matches = re.item(str);
var matches = re.item.exec(str);
// to tell a variable with default value, like
// name <string|'<a href="...">foo</a>'> summary
// from a line of description with tags, like
@ -3370,7 +3466,7 @@ Ox.doc = (function() {
}, parseType(matches[2])) : null;
}
function parseName(str) {
var matches = re.usage(str);
var matches = re.usage.exec(str);
return matches ? matches[0] : str;
}
function parseNode(node) {
@ -3515,7 +3611,7 @@ Ox.doc = (function() {
var match;
token.source = source.substr(token.offset, token.length);
if (token.type == 'comment' && (match =
re.multiline(token.source) || re.singleline(token.source)
re.multiline.exec(token.source)|| re.singleline.exec(token.source)
)) {
blocks.push(match[1]);
tokens.push([]);
@ -4016,11 +4112,11 @@ Ox.limit <f> Limits a number by a given mininum and maximum
> Ox.limit(2, 1)
1
@*/
Ox.limit = function(num, min, max) {
// fixme: rewrite this
var len = arguments.length;
max = arguments[len - 1];
min = len == 3 ? min : 0;
Ox.limit = function(/*num[[, min], max]*/) {
var len = arguments.length,
num = arguments[0],
min = len == 3 ? arguments[1] : 0, // fixme: should be -Infinity
max = arguments[len - 1];
return Math.min(Math.max(num, min), max);
};
@ -4373,25 +4469,23 @@ Ox.isValidEmail = function(str) {
Ox.pad <f> Pad a string to a given length
> Ox.pad(1, 2)
"01"
> Ox.pad("abc", 6, ".", "right")
> Ox.pad("abc", -6, ".")
"abc..."
> Ox.pad("foobar", 3, ".", "right")
> Ox.pad("foobar", -3, ".")
"foo"
> Ox.pad("abc", 6, "123456", "right")
> Ox.pad("abc", -6, "123456")
"abc123"
> Ox.pad("abc", 6, "123456", "left")
> Ox.pad("abc", 6, "123456")
"456abc"
@*/
Ox.pad = function(str, len, pad, pos) {
Ox.pad = function(str, len, pad) {
// fixme: slighly obscure signature
// fixme: weird for negative numbers
/*
*/
var pos = len / (len = Math.abs(len));
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 = pos == 1 ? pad + str : str + pad;
str = pos == 1 ?
str.substr(str.length - len, str.length) :
str.substr(0, len);
return str;