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

Last change on this file since 20115 was 17409, checked in by tbretz, 11 years ago
Enable strict mode.
File size: 13.0 KB
Line 
1'use strict';
2
3// TODO: look into using drawRect for backgrounds, to only need a colorMap for every foreground color
4var TTVFontData = { };
5TTVFontData.missingCode = "?".charCodeAt(0);
6TTVFontData.fonts = { };
7TTVFontData.fonts_loading = { };
8TTVFontData.base = "./fonts/";
9
10
11/**
12 * @constructor
13 */
14function 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/*
242var 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
Note: See TracBrowser for help on using the repository browser.