This is my first attempt at a jQuery plugin. Any tips or comments in regards the the plugin setup would be appreciated. I'm also looking for a general review over the code itself. I'm wondering if there is a better way to encode the font. The way I'm doing it now makes it easy to see, read and create the characters, but it's bulky.
One item to note it the font is not yet complete, but most screens/animations have what they need. I should have the font finished out in the next few days.
You can see a working ANSI animated screen here. There is a known issue with IE that prevents this from working. I know the fix, but just haven't gotten doing it yet.
The full font is not listed here as it's too large to post. A full listing is here. Only the majority of the font has been removed from the listing below.
(function ($) {
var methods = {
init: function (options) {
return this.each(function () {
var $this = $(this);
$this.addClass('ansiScreen');
var data = $this.data('ansi');
if (!data) {
data = new Object();
data.target = $this;
data.fontheight = 16;
data.fontwidth = 8;
data.canvas = $('<canvas width="' + (data.fontwidth * 80) + 'px" height="' + (data.fontheight * 25) + 'px">');
$this.append(data.canvas);
data.ctx = data.canvas[0].getContext('2d');
data.processInterval = null;
data.ansi = null;
data.ansiCode = null;
data.ansiPos = 0;
data.fgcolor = 'rgb(255, 255, 255)';
data.bgcolor = 'rgb(0, 0, 0)';
data.bold = false;
data.blink = false;
data.pos = [0, 0];
data.savepos = [0, 0];
data.screen = Array();
data.last = 0;
for (var i = 0; i < 25; i++) {
data.screen.push(Array());
for (var j = 0; j < 80; j++) {
data.screen[i].push([data.bgcolor, data.fgcolor, ' ', false, true]);
}
}
$this.data('ansi', data);
}
});
},
destroy: function () {
return this.each(function () {
var $this = $(this);
var data = $this.data('ansi');
// Clean up
$(window).unbind('.ansi');
data.tooltip.remove();
$this.removeData('ansi');
});
},
load: function (ansiUrl) {
return this.each(function () {
var $this = $(this);
var me = this;
$.ajax({
'url': ansiUrl,
'data': 'text',
beforeSend: function (jqXHR, settings) {
jqXHR.overrideMimeType('text/plain; charset=x-user-defined');
},
success: function(ansiData) {
var data = $this.data('ansi');
if (data.processInterval != null) {
clearInterval(data.processInterval);
clearInterval(data.screenInterval);
}
data.ansi = ansiData;
data.ansiPos = 0;
data.fgcolor = 'rgb(255, 255, 255)';
data.bgcolor = 'rgb(0, 0, 0)';
data.bold = false;
data.blink = false;
data.pos = [0, 0];
data.savepos = [0, 0];
var interval = setInterval(function () {
processAnsi.call(me);
}, 5);
data.processInterval = interval;
$this.data('ansi', data);
updateDisplay.call(me);
}
});
});
}
};
$.fn.ansi = function (method) {
// Method calling logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.ansi');
}
};
// Process a byte from teh ansi data
function processAnsi() {
var $this = $(this);
var data = $this.data('ansi');
if (data.ansiPos > data.ansi.length)
{
clearInterval(data.processInterval);
return;
}
var code = data.ansi.charCodeAt(data.ansiPos) & 0xff;
data.ansiPos += 1;
$this.data('ansi', data);
var display = true;
if (code < 33 || code > 126)
{
switch (code)
{
case 0: display = false; break;
case 10: display = false; cursorStartOfLine.call(this); break;
case 13: display = false; cursorDown.call(this, 1); break;
case 27: display = false; ansiCode.call(this); break;
default: display = true; break;
}
}
if (display) {
putCharacter.call(this, code);
}
updateDisplay.call(this);
}
function ansiCode() {
var $this = $(this);
var data = $this.data('ansi');
if (data.ansiPos > data.ansi.length)
return '-';
var valid = /^[0-9;HABCDRsuJKmh]$/;
var end = /^[HABCDRsuJKmh]$/;
var char = data.ansi[data.ansiPos];
var escape = '';
if (char == '[')
{
var stop = false;
do {
data.ansiPos += 1;
var char = data.ansi[data.ansiPos];
escape += char;
stop = end.test(char);
} while (valid.test(char) && !stop)
data.ansiPos += 1;
}
switch(escape[escape.length - 1])
{
case 'J': // Clear screen
if (escape == '2J') {
clearDisplay.call(this);
}
break;
case 'A': // Move up
var lines = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(lines)) { lines = 1; }
data.pos[1] -= lines;
if (data.pos[1] < 0) { data.pos[1] = 0; }
break;
case 'B': // Move down
var lines = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(lines)) { lines = 1; }
cursorDown.call(this, lines);
break;
case 'C': // Move Forward
var spaces = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(spaces)) { spaces = 1; }
cursorForward.call(this, spaces);
break;
case 'D': // Move back
var spaces = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(spaces)) { lines = 1; }
data.pos[0] -= spaces;
if (data.pos[0] < 0) { data.pos[0] = 0; }
break;
case 'H': // Move To
var codes = escape.substring(0, escape.length - 1).split(';');
if (isNaN(codes[0])) { codes[0] = 1; }
if (isNaN(codes[1])) { codes[1] = 1; }
data.pos[0] = codes[1] - 1;
data.pos[1] = codes[0] - 1;
break;
case 'K': // Clear
var mode = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(mode)) { mode = 0; }
switch (mode)
{
case 2: // clear current line
for (var i=0; i< data.screen[0].length; i++)
{
data.screen[data.pos[1]][i] = [data.bgcolor, data.fgcolor, 32, false, true];
}
break;
case 1: // clear start of line
for (var i=0; i< data.pos[1]; i++)
{
data.screen[data.pos[1]][i] = [data.bgcolor, data.fgcolor, 32, false, true];
}
break;
case 0: // clear end of line
default:
for (var i=data.pos[1]; i< data.screen[0].length; i++)
{
data.screen[data.pos[1]][i] = [data.bgcolor, data.fgcolor, 32, false, true];
}
break;
}
break;
case 's': // Save position
data.savepos[0] = data.pos[0];
data.savepos[1] = data.pos[1];
break;
case 'u': // Restore position
data.pos[0] = data.savepos[0];
data.pos[1] = data.savepos[1];
break;
case 'm': // Change attrinutes
var codes = escape.substring(0, escape.length - 1).split(';');
for (var i=0; i < codes.length; i++) {
var code = codes[i];
switch (code) {
case '0':
data.bold = false;
data.blink = false;
data.fgcolor = 'rgb(255, 255, 255)';
data.bgcolor = 'rgb(0, 0, 0)';
break;
case '1':
data.bold = true;
break;
case '5':
data.blink = true;
break;
case '30':
data.fgcolor = !data.bold ? 'rgb(0, 0, 0)' : 'rgb(85, 85, 85)';
break;
case '31':
data.fgcolor = !data.bold ? 'rgb(170, 0, 0)' : 'rgb(255, 85, 85)';
break;
case '32':
data.fgcolor = !data.bold ? 'rgb(0, 170, 0)' : 'rgb(85, 255, 85)';
break;
case '33':
data.fgcolor = !data.bold ? 'rgb(170, 85, 0)' : 'rgb(255, 255, 0)';
break;
case '34':
data.fgcolor = !data.bold ? 'rgb(0, 0, 170)' : 'rgb(85, 85, 255)';
break;
case '35':
data.fgcolor = !data.bold ? 'rgb(170, 0, 170)' : 'rgb(255, 85, 255)';
break;
case '36':
data.fgcolor = !data.bold ? 'rgb(0, 170, 170)' : 'rgb(85, 255, 255)';
break;
case '37':
data.fgcolor = !data.bold ? 'rgb(170, 170, 170)' : 'rgb(255, 255, 255)';
break;
case '40':
data.bgcolor = 'rgb(0, 0, 0)';
break;
case '41':
data.bgcolor = 'rgb(170, 0, 0)';
break;
case '42':
data.bgcolor = 'rgb(0, 170, 0)';
break;
case '43':
data.bgcolor = 'rgb(170, 85, 0)';
break;
case '44':
data.bgcolor = 'rgb(0, 0, 170)';
break;
case '45':
data.bgcolor = 'rgb(170, 0, 170)';
break;
case '46':
data.bgcolor = 'rgb(0, 170, 170)';
break;
case '47':
data.bgcolor = 'rgb(170, 170, 170)';
break;
default:
$('#debug').html($('#debug').html() + '<br>Unknown Attribute: ' + code);
break;
}
}
break;
default:
$('#debug').html($('#debug').html() + '<br>esc[' + escape);
break;
}
$this.data('ansi', data);
return '';
}
// Move the cursor position up a number of lines
function cursorStartOfLine(lines) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[0] = 0;
$this.data('ansi', data);
}
// Move the cursor position down a number of lines
// scroll the screen if needed
function cursorDown(lines) {
var $this = $(this);
var data = $this.data('ansi');
var rows = data.screen.length;
var cols = data.screen[0].length;
data.pos[1] += lines;
if (data.pos[1] > data.screen.length - 1) {
data.pos[1] = rows - 1;
for (var i = 0; i < rows - 1; i++) {
for (var j = 0; j < cols; j++) {
data.screen[i][j] = data.screen[i+1][j];
data.screen[i][j][4] = true;
}
}
for (var j = 0; j < cols; ++j) {
data.screen[rows-1][j] = [data.bgcolor, data.fgcolor, 32, false, true];
}
}
$this.data('ansi', data);
}
// Move the cursor position forward a number of columns
function cursorForward(cols) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[0] += cols;
if (data.pos[0] > data.screen[0].length - 1) {
data.pos[0] = 0;
cursorDown.call(this,1);
}
$this.data('ansi', data);
}
// Puts a character on screen
function putCharacter(charCode) {
var $this = $(this);
var data = $this.data('ansi');
data.screen[data.pos[1]][data.pos[0]] = [data.bgcolor, data.fgcolor, charCode, data.blink, true];
$this.data('ansi', data);
// Move forward 1 character
cursorForward.call(this, 1);
}
// Clear the screen
function clearDisplay() {
var $this = $(this);
var data = $this.data('ansi');
for (var i = 0; i < data.screen.length; i++) {
for (var j = 0; j < data.screen[i].length; j++) {
data.screen[i][j] = [data.bgcolor, data.fgcolor, ' ', data.blink, true];
}
}
data.pos = [0, 0];
$this.data('ansi', data);
}
// Update the container with the current screen
function updateDisplay() {
var $this = $(this);
var data = $this.data('ansi');
for (var i = 0, length1 = data.screen.length; i < length1; ++i) {
var a = data.screen[i]; // cache object
for (var j = 0, length2 = a.length; j < length2; ++j) {
if (a[j][4])
{
data.ctx.save();
data.ctx.beginPath();
data.ctx.rect (data.fontwidth * j, data.fontheight * i, data.fontwidth, data.fontheight);
data.ctx.clip();
data.ctx.fillStyle = a[j][0];
data.ctx.fillRect (data.fontwidth * j, data.fontheight * i, data.fontwidth, data.fontheight);
data.ctx.fillStyle = a[j][1];
drawCharater.call(this, a[j][2], [data.fontwidth * j, data.fontheight * i], 1);
data.ctx.restore();
a[j][4] = false;
}
}
}
$this.data('ansi', data);
}
// Draw a single character at a given location
function drawCharater(code, location, size) {
var $this = $(this);
var data = $this.data('ansi');
var template = new Array();
switch(code)
{
case 32:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 48:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,1,1,1,1,0,0]);
template.push([0,1,1,0,0,1,1,0]);
template.push([1,1,0,0,0,0,1,1]);
template.push([1,1,0,0,0,0,1,1]);
template.push([1,1,0,1,1,0,1,1]);
template.push([1,1,0,1,1,0,1,1]);
template.push([1,1,0,0,0,0,1,1]);
template.push([1,1,0,0,0,0,1,1]);
template.push([0,1,1,0,0,1,1,0]);
template.push([0,0,1,1,1,1,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 49:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,1,1,1,0,0,0]);
template.push([0,1,1,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,1,1,1,1,1,1,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 50:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,1,1,0,0,0,0]);
template.push([0,1,1,0,0,0,0,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,1,1,1,1,1,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 51:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,1,1,1,1,0,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 52:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,1,1,1,0,0]);
template.push([0,0,1,1,1,1,0,0]);
template.push([0,1,1,0,1,1,0,0]);
template.push([1,1,0,0,1,1,0,0]);
template.push([1,1,1,1,1,1,1,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,1,1,1,1,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 53:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([1,1,1,1,1,1,1,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,1,1,1,1,0,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 54:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,1,1,1,1,0,0]);
template.push([0,1,1,0,0,0,0,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,0,0,0,0,0,0]);
template.push([1,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 55:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([1,1,1,1,1,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,0,0,1,1,0,0,0]);
template.push([0,0,1,1,0,0,0,0]);
template.push([0,0,1,1,0,0,0,0]);
template.push([0,0,1,1,0,0,0,0]);
template.push([0,0,1,1,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 56:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
case 57:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,1,1,1,1,1,0,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([1,1,0,0,0,1,1,0]);
template.push([0,1,1,1,1,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,0,1,1,0]);
template.push([0,0,0,0,1,1,0,0]);
template.push([0,1,1,1,1,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
default:
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
template.push([0,0,0,0,0,0,0,0]);
break;
}
for(var y=0; y<16; y++)
{
for(var x=0; x<8; x++)
{
if (template[y][x] == 1)
data.ctx.fillRect(location[0] + x, location[1] + y, 1, 1);
}
}
}
})(jQuery);