| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | // TODO: look into using drawRect for backgrounds, to only need a colorMap for every foreground color
|
|---|
| 4 | var TTVFontData = { };
|
|---|
| 5 | TTVFontData.missingCode = "?".charCodeAt(0);
|
|---|
| 6 | TTVFontData.fonts = { };
|
|---|
| 7 | TTVFontData.fonts_loading = { };
|
|---|
| 8 | TTVFontData.base = "./fonts/";
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 | /**
|
|---|
| 12 | * @constructor
|
|---|
| 13 | */
|
|---|
| 14 | function TTVFont(name, cb, dbg)
|
|---|
| 15 | {
|
|---|
| 16 | var vt = this;
|
|---|
| 17 |
|
|---|
| 18 | if (TTVFontData.fonts[name])
|
|---|
| 19 | {
|
|---|
| 20 | cb(TTVFontData.fonts[name], null);
|
|---|
| 21 | return;
|
|---|
| 22 | }
|
|---|
| 23 |
|
|---|
| 24 | if (TTVFontData.fonts_loading[name])
|
|---|
| 25 | {
|
|---|
| 26 | TTVFontData.fonts_loading[name].callbacks.push(cb);
|
|---|
| 27 | return;
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | vt.debug = function(txt)
|
|---|
| 31 | {
|
|---|
| 32 | dbg("font.js: "+txt);
|
|---|
| 33 | }
|
|---|
| 34 |
|
|---|
| 35 | // ===============================================================
|
|---|
| 36 |
|
|---|
| 37 | var f = TTVFontData.fonts_loading[name] =
|
|---|
| 38 | {
|
|---|
| 39 | image: new Image(),
|
|---|
| 40 | callbacks: [cb]
|
|---|
| 41 | };
|
|---|
| 42 |
|
|---|
| 43 | // Called when the image was successfully loaded
|
|---|
| 44 | f.image.onload = function ()
|
|---|
| 45 | {
|
|---|
| 46 | f.imageComplete = true;
|
|---|
| 47 | if (f.loadedTxt)
|
|---|
| 48 | fontReady(name);
|
|---|
| 49 | };
|
|---|
| 50 |
|
|---|
| 51 | var url = TTVFontData.base+name;
|
|---|
| 52 |
|
|---|
| 53 | // Called when loading the image failed.
|
|---|
| 54 | f.image.onerror = function()
|
|---|
| 55 | {
|
|---|
| 56 | alert("ERROR[1] - Loading image '"+url+".png' failed.");
|
|---|
| 57 |
|
|---|
| 58 | // inform callbacks
|
|---|
| 59 | f.callbacks.forEach(function(cb){ cb(null, "Couldn't load stats file"); });
|
|---|
| 60 |
|
|---|
| 61 | // remove entry from list
|
|---|
| 62 | delete TTVFontData.fonts_loading[name];
|
|---|
| 63 | };
|
|---|
| 64 |
|
|---|
| 65 | f.image.src = url+'.png';
|
|---|
| 66 |
|
|---|
| 67 | // ===============================================================
|
|---|
| 68 |
|
|---|
| 69 | var r = new XMLHttpRequest();
|
|---|
| 70 | r.open('GET', url+'.txt', true);
|
|---|
| 71 | r.onload = function ()
|
|---|
| 72 | {
|
|---|
| 73 | // error occured during load
|
|---|
| 74 | if (r.status!=200)
|
|---|
| 75 | {
|
|---|
| 76 | alert("ERROR[0] - HTTP request '"+url+".txt': "+r.statusText+" ["+r.status+"]");
|
|---|
| 77 |
|
|---|
| 78 | // inform callbacks
|
|---|
| 79 | f.callbacks.forEach(function(cb){ cb(null, "Couldn't load stats file"); });
|
|---|
| 80 |
|
|---|
| 81 | // remove entry from list
|
|---|
| 82 | delete TTVFontData.fonts_loading[name];
|
|---|
| 83 | return;
|
|---|
| 84 | }
|
|---|
| 85 |
|
|---|
| 86 | f.loadedTxt = r.responseText;
|
|---|
| 87 | if ( f.imageComplete /*f.image.complete*/ )
|
|---|
| 88 | fontReady(name);
|
|---|
| 89 | };
|
|---|
| 90 | r.send(null);
|
|---|
| 91 |
|
|---|
| 92 | // ===============================================================
|
|---|
| 93 |
|
|---|
| 94 | var fontReady = function (name)
|
|---|
| 95 | {
|
|---|
| 96 | var fl = TTVFontData.fonts_loading[name];
|
|---|
| 97 | var font = new Font(name, fl.image, fl.loadedTxt);
|
|---|
| 98 | TTVFontData.fonts[name] = font;
|
|---|
| 99 | delete TTVFontData.fonts_loading[name];
|
|---|
| 100 |
|
|---|
| 101 | vt.debug(name+" ["+font.charWidth+"x"+font.charHeight+"] loaded.");
|
|---|
| 102 |
|
|---|
| 103 | fl.callbacks.forEach(function(cb) { cb(font, null); });
|
|---|
| 104 | };
|
|---|
| 105 |
|
|---|
| 106 | /**
|
|---|
| 107 | * @constructor
|
|---|
| 108 | */
|
|---|
| 109 | var Font = function (name, image, stats)
|
|---|
| 110 | {
|
|---|
| 111 | TTVFontData.fonts[name] = this;
|
|---|
| 112 |
|
|---|
| 113 | this.image = image;
|
|---|
| 114 |
|
|---|
| 115 | var chars = this.chars = { };
|
|---|
| 116 | this.colorMaps = { };
|
|---|
| 117 |
|
|---|
| 118 | var x = 0;
|
|---|
| 119 | var y = 0;
|
|---|
| 120 | var count = 0;
|
|---|
| 121 | var charsPerRow = 0;
|
|---|
| 122 | var last_cp = 0;
|
|---|
| 123 |
|
|---|
| 124 | function proc(v)
|
|---|
| 125 | {
|
|---|
| 126 | if ( !v.length )
|
|---|
| 127 | return;
|
|---|
| 128 |
|
|---|
| 129 | if ( /^\d+$/.exec(v) )
|
|---|
| 130 | {
|
|---|
| 131 | chars[v] = [x++, y];
|
|---|
| 132 | last_cp = parseInt(v, 10);
|
|---|
| 133 | count++;
|
|---|
| 134 | return;
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | if ( /^y$/.exec(v) )
|
|---|
| 138 | {
|
|---|
| 139 | if ( x > charsPerRow )
|
|---|
| 140 | charsPerRow = x;
|
|---|
| 141 | x = 0;
|
|---|
| 142 | y++;
|
|---|
| 143 | return;
|
|---|
| 144 | }
|
|---|
| 145 |
|
|---|
| 146 | var res = /^r(\d+)$/.exec(v);
|
|---|
| 147 | if ( res )
|
|---|
| 148 | {
|
|---|
| 149 | var ct = parseInt(res[1], 10);
|
|---|
| 150 | for (var v2 = last_cp+1; v2 <= last_cp+ct; v2++)
|
|---|
| 151 | chars[v2] = [x++, y];
|
|---|
| 152 |
|
|---|
| 153 | count += ct;
|
|---|
| 154 | last_cp += ct;
|
|---|
| 155 | return;
|
|---|
| 156 | }
|
|---|
| 157 |
|
|---|
| 158 | vt.debug("Stats file is corrupt, line=\""+v+"\"");
|
|---|
| 159 | }
|
|---|
| 160 |
|
|---|
| 161 | stats.split("\n").forEach(proc);
|
|---|
| 162 |
|
|---|
| 163 | if ( x > charsPerRow )
|
|---|
| 164 | charsPerRow = x;
|
|---|
| 165 |
|
|---|
| 166 | this.charCount = count;
|
|---|
| 167 |
|
|---|
| 168 | this.charHeight = this.image.naturalHeight / (y+1);
|
|---|
| 169 | this.charWidth = this.image.naturalWidth / charsPerRow;
|
|---|
| 170 |
|
|---|
| 171 | if (this.charWidth != Math.floor(this.charWidth))
|
|---|
| 172 | vt.debug("font loading of "+name+" failed: image width is not a multiple of the character count (image width = " + this.image.naturalWidth + ", character count = " + this.charCount + ")");
|
|---|
| 173 | };
|
|---|
| 174 |
|
|---|
| 175 | Font.prototype.drawChar = function (ctx, ch, x, y, fg, bg)
|
|---|
| 176 | {
|
|---|
| 177 | var idx = this.chars[ch.charCodeAt(0)];
|
|---|
| 178 | if (idx === undefined)
|
|---|
| 179 | {
|
|---|
| 180 | idx = this.chars[0];
|
|---|
| 181 | if (idx === undefined)
|
|---|
| 182 | {
|
|---|
| 183 | idx = this.chars[0x3f]; // question mark
|
|---|
| 184 | if (idx === undefined)
|
|---|
| 185 | {
|
|---|
| 186 | vt.debug("Can't draw '"+ch+"', it is not mapped and neither is the missing character");
|
|---|
| 187 | return;
|
|---|
| 188 | }
|
|---|
| 189 | }
|
|---|
| 190 | }
|
|---|
| 191 |
|
|---|
| 192 | var mapstr = fg.substr(1)+bg.substr(1)+idx[1];
|
|---|
| 193 |
|
|---|
| 194 | if (!this.colorMaps[mapstr])
|
|---|
| 195 | this.colorMaps[mapstr] = this.getFontColorMap(fg, bg, idx[1]);
|
|---|
| 196 |
|
|---|
| 197 | // ctx.drawImage(source, src_x, src_y, src_w, src_h, dest_x, dest_y, dest_w, dest_h);
|
|---|
| 198 | ctx.drawImage(this.colorMaps[mapstr], idx[0]*this.charWidth, 0, this.charWidth, this.charHeight, x, y, this.charWidth, this.charHeight);
|
|---|
| 199 | }
|
|---|
| 200 |
|
|---|
| 201 | ////////////////////////////////////////////////////////////////////////////////
|
|---|
| 202 | // Private
|
|---|
| 203 |
|
|---|
| 204 | Font.prototype.getFontColorMap = function(fg, bg, chunk)
|
|---|
| 205 | {
|
|---|
| 206 | var w = this.image.naturalWidth;
|
|---|
| 207 | var h = this.charHeight;
|
|---|
| 208 |
|
|---|
| 209 | var yoff = chunk * this.charHeight;
|
|---|
| 210 |
|
|---|
| 211 | var cv = document.createElement('canvas');
|
|---|
| 212 | cv.width = w;
|
|---|
| 213 | cv.height = h;
|
|---|
| 214 |
|
|---|
| 215 | var ctx = cv.getContext('2d');
|
|---|
| 216 |
|
|---|
| 217 | // ctx.drawImage(source, src_x, src_y, src_w, src_h, dest_x, dest_y, dest_w, dest_h);
|
|---|
| 218 | ctx.drawImage(this.image, 0, yoff, w, h, 0, 0, w, h);
|
|---|
| 219 |
|
|---|
| 220 | var input = ctx.getImageData(0, 0, w, h);
|
|---|
| 221 | var output = ctx.createImageData(w, h);
|
|---|
| 222 |
|
|---|
| 223 | var N = w*h*4;
|
|---|
| 224 | for (var i=0; i<N; i+=4)
|
|---|
| 225 | {
|
|---|
| 226 | var col = input.data[i] > 127 ? bg : fg;
|
|---|
| 227 |
|
|---|
| 228 | output.data[i ] = parseInt(col.substring(1, 3), 16);
|
|---|
| 229 | output.data[i+1] = parseInt(col.substring(3, 5), 16);
|
|---|
| 230 | output.data[i+2] = parseInt(col.substring(5, 7), 16);
|
|---|
| 231 | output.data[i+3] = 255;
|
|---|
| 232 | }
|
|---|
| 233 |
|
|---|
| 234 | ctx.putImageData(output, 0, 0);
|
|---|
| 235 |
|
|---|
| 236 | return cv;
|
|---|
| 237 | }
|
|---|
| 238 | };
|
|---|
| 239 |
|
|---|
| 240 |
|
|---|
| 241 | /*
|
|---|
| 242 | var TTVFont = (function()
|
|---|
| 243 | {
|
|---|
| 244 | // var missingCode = "?".charCodeAt(0);
|
|---|
| 245 |
|
|---|
| 246 | ////////////////////////////////////////////////////////////////////////////////
|
|---|
| 247 | // Font loader
|
|---|
| 248 |
|
|---|
| 249 | // var fonts = { };
|
|---|
| 250 | //var fonts_loading = { };
|
|---|
| 251 |
|
|---|
| 252 | //var base = "./fonts/";
|
|---|
| 253 | //var setBase = function (baseurl) {
|
|---|
| 254 | // base = baseurl;
|
|---|
| 255 | //};
|
|---|
| 256 |
|
|---|
| 257 | //var debug = function(txt)
|
|---|
| 258 | //{
|
|---|
| 259 | // alert("font.js: "+txt);
|
|---|
| 260 | //}
|
|---|
| 261 |
|
|---|
| 262 | var load = function(name, cb, dbg)
|
|---|
| 263 | {
|
|---|
| 264 | if ( fonts[name] )
|
|---|
| 265 | {
|
|---|
| 266 | cb(fonts[name], null);
|
|---|
| 267 | return;
|
|---|
| 268 | }
|
|---|
| 269 |
|
|---|
| 270 | if ( fonts_loading[name] )
|
|---|
| 271 | {
|
|---|
| 272 | fonts_loading[name].callbacks.push(cb);
|
|---|
| 273 | return;
|
|---|
| 274 | }
|
|---|
| 275 |
|
|---|
| 276 | debug = function(txt)
|
|---|
| 277 | {
|
|---|
| 278 | dbg("font.js: "+txt);
|
|---|
| 279 | }
|
|---|
| 280 |
|
|---|
| 281 | var f = fonts_loading[name] = {
|
|---|
| 282 | image: new Image(),
|
|---|
| 283 | callbacks: [cb]
|
|---|
| 284 | };
|
|---|
| 285 |
|
|---|
| 286 | // ===============================================================
|
|---|
| 287 |
|
|---|
| 288 | // Called when the image was successfully loaded
|
|---|
| 289 | f.image.onload = function ()
|
|---|
| 290 | {
|
|---|
| 291 | f.imageComplete = true;
|
|---|
| 292 | if ( f.loadedTxt )
|
|---|
| 293 | fontReady(name);
|
|---|
| 294 | };
|
|---|
| 295 |
|
|---|
| 296 | // Called when loading the image failed.
|
|---|
| 297 | f.image.onerror= function ()
|
|---|
| 298 | {
|
|---|
| 299 | alert("ERROR[1] - Loading image '"+base+name+".png' failed.");
|
|---|
| 300 |
|
|---|
| 301 | // inform callbacks
|
|---|
| 302 | f.callbacks.forEach(function(cb){ cb(null, "Couldn't load stats file"); });
|
|---|
| 303 |
|
|---|
| 304 | // remove entry from list
|
|---|
| 305 | delete fonts_loading[name];
|
|---|
| 306 | };
|
|---|
| 307 |
|
|---|
| 308 | f.image.src = base + name + '.png';
|
|---|
| 309 |
|
|---|
| 310 | // ===============================================================
|
|---|
| 311 |
|
|---|
| 312 | var r = new XMLHttpRequest();
|
|---|
| 313 | r.open('GET', base + name + '.txt', true);
|
|---|
| 314 | r.onload = function ()
|
|---|
| 315 | {
|
|---|
| 316 | // error occured during load
|
|---|
| 317 | if (r.status!=200)
|
|---|
| 318 | {
|
|---|
| 319 | alert("ERROR[0] - HTTP request '"+base+name+".txt': "+r.statusText+" ["+r.status+"]");
|
|---|
| 320 |
|
|---|
| 321 | // inform callbacks
|
|---|
| 322 | f.callbacks.forEach(function(cb){ cb(null, "Couldn't load stats file"); });
|
|---|
| 323 |
|
|---|
| 324 | // remove entry from list
|
|---|
| 325 | delete fonts_loading[name];
|
|---|
| 326 | return;
|
|---|
| 327 | }
|
|---|
| 328 |
|
|---|
| 329 | f.loadedTxt = r.responseText;
|
|---|
| 330 | if ( f.imageComplete ) // f.image.complete
|
|---|
| 331 | fontReady(name);
|
|---|
| 332 | };
|
|---|
| 333 | r.send(null);
|
|---|
| 334 |
|
|---|
| 335 | // ===============================================================
|
|---|
| 336 | };
|
|---|
| 337 |
|
|---|
| 338 | var fontReady = function (name)
|
|---|
| 339 | {
|
|---|
| 340 | var fl = fonts_loading[name];
|
|---|
| 341 | fonts[name] = new Font(name, fl.image, fl.loadedTxt);
|
|---|
| 342 | delete fonts_loading[name];
|
|---|
| 343 |
|
|---|
| 344 | fl.callbacks.forEach(function(cb) { cb(fonts[name], null); });
|
|---|
| 345 | };
|
|---|
| 346 |
|
|---|
| 347 | ////////////////////////////////////////////////////////////////////////////////
|
|---|
| 348 | // Font drawer
|
|---|
| 349 |
|
|---|
| 350 | var Font = function (name, image, stats)
|
|---|
| 351 | {
|
|---|
| 352 | fonts[name] = this;
|
|---|
| 353 |
|
|---|
| 354 | this.image = image;
|
|---|
| 355 |
|
|---|
| 356 | var chars = this.chars = { };
|
|---|
| 357 | this.colorMaps = { };
|
|---|
| 358 |
|
|---|
| 359 | var x = 0;
|
|---|
| 360 | var y = 0;
|
|---|
| 361 | var count = 0;
|
|---|
| 362 | var charsPerRow = 0;
|
|---|
| 363 | var last_cp = 0;
|
|---|
| 364 |
|
|---|
| 365 | function proc(v)
|
|---|
| 366 | {
|
|---|
| 367 | if ( !v.length )
|
|---|
| 368 | return;
|
|---|
| 369 |
|
|---|
| 370 | if ( /^\d+$/.exec(v) )
|
|---|
| 371 | {
|
|---|
| 372 | chars[v] = [x++, y];
|
|---|
| 373 | last_cp = parseInt(v, 10);
|
|---|
| 374 | count++;
|
|---|
| 375 | return;
|
|---|
| 376 | }
|
|---|
| 377 |
|
|---|
| 378 | if ( /^y$/.exec(v) )
|
|---|
| 379 | {
|
|---|
| 380 | if ( x > charsPerRow )
|
|---|
| 381 | charsPerRow = x;
|
|---|
| 382 | x = 0;
|
|---|
| 383 | y++;
|
|---|
| 384 | return;
|
|---|
| 385 | }
|
|---|
| 386 |
|
|---|
| 387 | var res = /^r(\d+)$/.exec(v);
|
|---|
| 388 | if ( res )
|
|---|
| 389 | {
|
|---|
| 390 | var ct = parseInt(res[1], 10);
|
|---|
| 391 | for (var v2 = last_cp+1; v2 <= last_cp+ct; v2++)
|
|---|
| 392 | chars[v2] = [x++, y];
|
|---|
| 393 |
|
|---|
| 394 | count += ct;
|
|---|
| 395 | last_cp += ct;
|
|---|
| 396 | return;
|
|---|
| 397 | }
|
|---|
| 398 |
|
|---|
| 399 | debug("Stats file is corrupt, line=\""+v+"\"");
|
|---|
| 400 | }
|
|---|
| 401 |
|
|---|
| 402 | stats.split("\n").forEach(proc);
|
|---|
| 403 |
|
|---|
| 404 | if ( x > charsPerRow )
|
|---|
| 405 | charsPerRow = x;
|
|---|
| 406 |
|
|---|
| 407 | this.charCount = count;
|
|---|
| 408 |
|
|---|
| 409 | this.charHeight = this.image.naturalHeight / (y+1);
|
|---|
| 410 | this.charWidth = this.image.naturalWidth / charsPerRow;
|
|---|
| 411 |
|
|---|
| 412 | if ( this.charWidth != Math.floor(this.charWidth) )
|
|---|
| 413 | debug("font loading of \""+name+"\" failed: image width is not a multiple of the character count (image width = " + this.image.naturalWidth + ", character count = " + this.charCount + ")");
|
|---|
| 414 | };
|
|---|
| 415 |
|
|---|
| 416 | Font.prototype.drawChar = function (ctx, ch, x, y, fg, bg)
|
|---|
| 417 | {
|
|---|
| 418 | var codepoint = ch.charCodeAt(0);
|
|---|
| 419 |
|
|---|
| 420 | var idx;
|
|---|
| 421 | if ( typeof(this.chars[codepoint]) != 'undefined' )
|
|---|
| 422 | {
|
|---|
| 423 | idx = this.chars[codepoint];
|
|---|
| 424 | }
|
|---|
| 425 |
|
|---|
| 426 | if ( typeof idx == 'undefined' )
|
|---|
| 427 | {
|
|---|
| 428 | if ( typeof(this.chars[missingCode]) == 'undefined' )
|
|---|
| 429 | {
|
|---|
| 430 | debug("Can't draw \""+ch+"\", it is not mapped and neither is the missing character");
|
|---|
| 431 | return;
|
|---|
| 432 | }
|
|---|
| 433 |
|
|---|
| 434 | idx = this.chars[missingCode];
|
|---|
| 435 | }
|
|---|
| 436 |
|
|---|
| 437 | // ctx.drawImage(source, src_x, src_y, src_w, src_h, dest_x, dest_y, dest_w, dest_h);
|
|---|
| 438 | var cm = this.getFontColorMap(fg, bg, idx[1]);
|
|---|
| 439 | ctx.drawImage(cm, idx[0]*this.charWidth, 0, this.charWidth, this.charHeight, x, y, this.charWidth, this.charHeight);
|
|---|
| 440 | }
|
|---|
| 441 |
|
|---|
| 442 | ////////////////////////////////////////////////////////////////////////////////
|
|---|
| 443 | // Private
|
|---|
| 444 |
|
|---|
| 445 | Font.prototype.getFontColorMap = function(fg, bg, chunk)
|
|---|
| 446 | {
|
|---|
| 447 | // create a look up table
|
|---|
| 448 | var mapstr = fg + "/" + bg + "/" + chunk;
|
|---|
| 449 | if ( this.colorMaps[mapstr] )
|
|---|
| 450 | return this.colorMaps[mapstr];
|
|---|
| 451 |
|
|---|
| 452 | var w = this.image.naturalWidth;
|
|---|
| 453 | var h = this.charHeight;
|
|---|
| 454 |
|
|---|
| 455 | var yoff = chunk * this.charHeight;
|
|---|
| 456 |
|
|---|
| 457 | var cv = document.createElement('canvas');
|
|---|
| 458 | cv.width = w;
|
|---|
| 459 | cv.height = h;
|
|---|
| 460 |
|
|---|
| 461 | var ctx = cv.getContext('2d');
|
|---|
| 462 |
|
|---|
| 463 | // ctx.drawImage(source, src_x, src_y, src_w, src_h, dest_x, dest_y, dest_w, dest_h);
|
|---|
| 464 | ctx.drawImage(this.image, 0, yoff, w, h, 0, 0, w, h);
|
|---|
| 465 |
|
|---|
| 466 | var input = ctx.getImageData(0, 0, w, h);
|
|---|
| 467 | var output = ctx.createImageData(w, h);
|
|---|
| 468 |
|
|---|
| 469 | var N = w*h*4;
|
|---|
| 470 | for (var i=0; i<N; i+=4)
|
|---|
| 471 | {
|
|---|
| 472 | var col = input.data[i] > 127 ? bg : fg;
|
|---|
| 473 |
|
|---|
| 474 | output.data[i ] = col[0];
|
|---|
| 475 | output.data[i+1] = col[1];
|
|---|
| 476 | output.data[i+2] = col[2];
|
|---|
| 477 | output.data[i+3] = 255;
|
|---|
| 478 | }
|
|---|
| 479 |
|
|---|
| 480 | ctx.putImageData(output, 0, 0);
|
|---|
| 481 |
|
|---|
| 482 | this.colorMaps[mapstr] = cv;
|
|---|
| 483 |
|
|---|
| 484 | return cv;
|
|---|
| 485 | }
|
|---|
| 486 |
|
|---|
| 487 | return {
|
|---|
| 488 | load: load,
|
|---|
| 489 | setBase: setBase,
|
|---|
| 490 | Font: Font
|
|---|
| 491 | };
|
|---|
| 492 | })();*/
|
|---|
| 493 |
|
|---|