this.xpmlib = new function() { /* Basic x-pixmap support for JavaScript Pixmaps can either be read from XPM files or from JavaScript arrays. */ var xpmlib = this; function PixMap(data) { this.data = data; this.cache = null; }; this.PixMap = PixMap; PixMap.prototype.render = function(renderer) { if (!renderer) { renderer = new xpmlib.TableRenderer(document); }; return renderer.render(this.parse()); }; PixMap.prototype.rerender = function(renderer) { this.cache = null; return this.render(renderer); }; PixMap.prototype.parse = function() { var data = this.data; if (!this.cache) { if (typeof(data) == 'string') { // string, assume it's unparsed XPM data, if not we assume it's // an array var data = this._parse_string(data); } else if (data.nodeName && data.nodeName.toLowerCase() == 'table') { var data = this._parse_table(data); } else { testing.assert(data instanceof Array, 'wrong data type ' + typeof(data)); }; this.cache = data; } else { data = this.cache; }; return data; }; PixMap.prototype._parse_string = function(data) { // create nice array of lines var parsed = []; var lines = data.split('\n'); var first = lines.shift(); var lineno = 1; var infolineno = 0; testing.assert(string.strip(first) == '/* XPM */', 'missing XPM header'); // remove empty lines, get to the first line of the data // XXX should we remove comments too? var current = ''; while (true) { var current = string.strip(lines.shift()); lineno++; if (current) { break; }; }; var firstlinereg = /static char ?\* ?(.*) ?\[\] ?= ?\{/; var match = firstlinereg.exec(current); testing.assert((match && match.length > 1), 'first line of data not recognized: ' + current + ' (line ' + lineno + ')'); // walk through the data while (true) { var current = string.strip(lines.shift()); lineno++; if (!infolineno) { infolineno = lineno; }; if (current === undefined) { break; }; testing.assert(current.charAt(0) == '"', 'unrecognized data line: ' + current + ' (line ' + lineno + ')'); if (current.charAt(current.length - 1) == ';') { testing.assert(current.substr(current.length - 3) == '"};', 'unrecognized closing line: ' + current + ' (line ' + lineno + ')'); parsed.push(current.substring(1, (current.length - 3))); break; } else { testing.assert( current.substring(current.length - 2) == '",', 'unrecognized data line: ' + current + ' (line ' + lineno + ')'); parsed.push(current.substring(1, (current.length - 2))); }; }; this._parse_info(parsed, infolineno); var colors = this._parse_colors(parsed, (infolineno + 1)); var pixels = this._parse_pixels(colors, parsed, (infolineno + this.numcolors)); return pixels; }; PixMap.prototype._parse_info = function(lines, lineno) { var infoline = lines.shift(); var info = infoline.split(' '); testing.assert(info.length == 4, 'wrong number of info items (line ' + lineno + ')'); this.width = parseInt(info[0]); this.height = parseInt(info[1]); this.numcolors = parseInt(info[2]); this.coloridsize = parseInt(info[3]); }; PixMap.prototype._parse_colors = function(lines, startlineno) { var colors = {}; for (var i=0; i < this.numcolors; i++) { var line = lines.shift(); var id = line.substr(0, this.coloridsize); var chunks = line.split(' '); var color = chunks[chunks.length - 1]; if (color == 'None') { color = 'transparent'; } else if (color.substr(0, 1) == '#') { testing.assert(color.length == 7, 'color should be 6 digits of size ' + '(line ' + (startlineno + i + 1) + ')'); }; colors[id] = color; }; return colors; }; PixMap.prototype._parse_pixels = function(colors, lines, startlineno) { var grid = []; var coloridsize = this.coloridsize; for (var i=0; i < lines.length; i++) { var pixels = []; var line = lines[i]; var length = this.width * this.coloridsize; testing.assert(line.length == length, 'wrong line length: ' + length + ' (line ' + (startlineno + i + 1) + ')'); while (true) { if (!line) { break; }; pixels.push(colors[line.substr(0, coloridsize)]); line = line.substr(coloridsize); }; grid.push(pixels); }; testing.assert(grid.length == this.height, 'wrong height: ' + grid.length); return grid; }; PixMap.prototype._parse_table = function(table) { var grid = []; var colors = {}; var idgen = new xpmlib._ColorIDGenerator(); var trs = table.getElementsByTagName('tr'); for (var i=0; i < trs.length; i++) { var line = []; grid.push(line); var tds = trs[i].getElementsByTagName('td'); for (var j=0; j < tds.length; j++) { var color = this._css_color_to_hex( tds[j].style.backgroundColor); line.push(color); colors[color] = true; }; }; // set some attributes this.width = trs[0].getElementsByTagName('td').length; this.height = trs.length; this.numcolors = colors.length; return grid; }; PixMap.prototype._css_color_to_hex = function(cssstring) { if (cssstring.charAt(0) == '#' || cssstring == 'transparent') { return cssstring; } else if (cssstring.substr(0, 4) == 'rgb(') { var ret = '#'; var value = cssstring.substring(4, cssstring.length - 1); value = value.split(','); for (var i=0; i < value.length; i++) { var hex = number.toBase(value[i], 16); if (hex.length == 1) { hex = '0' + hex; }; ret += hex; }; return ret; } else { throw( new exception.Exception( 'CSS color string format not supported: ' + cssstring)); }; }; function TableRenderer(document) { this.doc = document; }; this.TableRenderer = TableRenderer; TableRenderer.prototype.render = function(grid) { var table = this.doc.createElement('table'); table.className = 'pixmap'; table.cellPadding = '0'; table.cellSpacing = '0'; var tbody = this.doc.createElement('tbody'); table.appendChild(tbody); for (var i=0; i < grid.length; i++) { var tr = this.doc.createElement('tr'); tbody.appendChild(tr); for (var j=0; j < grid[i].length; j++) { var td = this.doc.createElement('td'); tr.appendChild(td); //td.appendChild(this.doc.createTextNode('\xa0')); td.style.backgroundColor = grid[i][j]; }; }; return table; }; function StringTableRenderer(document) { this.doc = document; }; this.StringTableRenderer = StringTableRenderer; StringTableRenderer.prototype.render = function(grid) { var table = this.doc.createElement('table'); table.className = 'pixmap'; table.cellPadding = '0'; table.cellSpacing = '0'; var tbody = ''; for (var i=0; i < grid.length; i++) { tbody += ''; for (var j=0; j < grid[i].length; j++) { tbody += ' '; }; tbody += ''; }; tbody += ''; table.innerHTML = tbody; return table; }; this._ColorIDGenerator = function() { // chars stolen from an XPM from the Gazpacho project (very cool!) var chars = ' .+@#$%&*=-;>,\')!~{]^/(_:<[}|' + '0123456789' + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var i = 0; this.next = function() { i++; // we abuse number.toBase() a bit here to return a unique string... var idrev = number.toBase(i, chars.length, 0, chars); var id = ''; for (var i=idrev.length-1; i >= 0; i--) { id += idrev.charAt(i); }; return id; }; }; }();