source: trunk/termtv/js/font.js@ 17407

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