source: trunk/termtv/js/canvas.js@ 19662

Last change on this file since 19662 was 17409, checked in by tbretz, 11 years ago
Enable strict mode.
File size: 16.6 KB
Line 
1'use strict';
2
3TTVCanvas.prototype.getFontSize = function(ctx, font)
4{
5 var rc = [];
6
7 ctx.font = font;
8 rc[0] = ctx.measureText('WWWWI').width;
9
10 ctx.font = "bold "+font;
11 rc[1] = ctx.measureText('WWWWI').width;
12
13 ctx.font = "lighter "+font;
14 rc[2] = ctx.measureText('WWWWI').width;
15
16 return rc;
17}
18
19TTVCanvas.prototype.waitForFonts = function()
20{
21 var s = this;
22
23 if (s.fontsize)
24 {
25 var ctx = s.canvas.getContext('2d');
26
27 var f1 = "300px sans";
28 var f2 = "300px serif";
29 var f3 = "300px "+s.fontFamily+", sans";
30 var f4 = "300px "+s.fontFamily+", serif";
31
32 var rc1 = s.getFontSize(ctx, f1);
33 var rc2 = s.getFontSize(ctx, f2);
34 var rc3 = s.getFontSize(ctx, f3);
35 var rc4 = s.getFontSize(ctx, f4);
36
37 var test0 = rc3[0]!=rc4[0] || rc3[0]==rc1[0] || rc4[0]==rc2[0];
38 var test1 = rc3[1]!=rc4[1] || rc3[1]==rc1[1] || rc4[1]==rc2[1];
39 var test2 = rc3[2]!=rc4[2] || rc3[2]==rc1[2] || rc4[2]==rc2[2];
40 if (test0 || test1 || test2)
41 {
42 setTimeout(function() { s.waitForFonts(); }, 20);
43 return;
44 }
45
46 s.normalFontName = "normal " +s.fontsize+"px "+s.fontFamily;
47 s.boldFontName = "bold " +s.fontsize+"px "+s.fontFamily;
48 s.lightFontName = "lighter "+s.fontsize+"px "+s.fontFamily;
49
50 ctx.font = s.normalFontName;
51 var fw = ctx.measureText('W').width;
52
53 ctx.font = s.boldFontName;
54 var bw = ctx.measureText('W').width;
55
56 ctx.font = s.lightFontName;
57 var lw = ctx.measureText('W').width;
58
59 var mx = Math.max(fw, bw, lw);
60
61 s.normalFontOffset = parseInt((mx-fw)/2, 10);
62 s.boldFontOffset = parseInt((mx-bw)/2, 10);
63 s.lightFontOffset = parseInt((mx-lw)/2, 10);
64
65 s.font = { };
66 s.font.charHeight = s.fontsize;
67 s.font.charWidth = mx;
68
69 s.debug("Using font-family '"+s.fontFamily+"' ["+mx+" x "+s.fontsize+"]");
70
71 s.readyCheck();
72 return;
73 }
74
75 // this callback may be called immediately, so we must make sure
76 // everything is set up for it beforehand
77 new TTVFont(s.fontName, function (f) {
78 s.font = f;
79 s.readyCheck();
80 }, s.options.debug);
81
82 if (s.boldFontName)
83 {
84 new TTVFont(s.boldFontName, function (f) {
85 s.boldfont = f;
86 s.readyCheck();
87 }, s.options.debug);
88 }
89
90}
91
92/**
93 * @class TTVCanvas
94 * @constructor
95 * @public
96 *
97 * This objects handles the canvas update and interacts with the
98 * emulator.
99 *
100 * @param {Node} canvas
101 * A canvas element
102 *
103 * @param {Object} options
104 * <ul>
105 * <li> font: the name or url of the font to be loaded (without extension)
106 * <li> boldfont: the name or url of the boldfont to be loaded (without extension)
107 * <li> autoResize: allow the canvas to be resized when the theoretical size is known
108 * <li> callback: a callback called in case of special events
109 * <li> onReady: a callback called when everything is readily setup
110 * <li> width: the width of the terminal in characters
111 * <li> height: the height of the terminal in characters
112 * <li> fontsize: if fontsize is given the cnavas-element text rendering
113 * engine is used for text rendering. This might not work on all
114 * browsers.
115 * <li> fontfamily: The font-family, if manual canvas-rendering is
116 * switched on with fontsize>0. Do not choose variable size fonts.
117 * Note that the code is waiting for the font to be loaded. So if
118 * the font cannot be loaded, it will wait forever.
119 * </ul>
120 */
121function TTVCanvas(canvas, options)
122{
123 // -0- -1- -2- -3- -4- -5- -6- -7-
124 // black red green yellow blue magenta cyan white
125 var stdRGB = ['#000000', '#b21818', '#18b218', '#b26818', '#1818b2', '#b218b2', '#18b2b2', '#b2b2b2'];
126 var hiRGB = ['#686868', '#ff5454', '#54ff54', '#ffff54', '#5454ff', '#ff54ff', '#54ffff', '#ffffff'];
127
128 var s = this;
129
130 if ( !(canvas instanceof HTMLCanvasElement) )
131 throw new Error("First argument to TTVCanvas constructor must be an HTMLCanvasElement (was "+canvas+")");
132
133 //s.stdColors = clone(stdColors);
134 //s.hiColors = clone(hiColors);
135 s.stdRGB = clone(stdRGB);
136 s.hiRGB = clone(hiRGB);
137 s.fontName = 'qemu-vgafont';
138 s.fontFamily = 'monospace';
139 s.onReady = [];
140 s.canvas = canvas;
141
142 s.debug = function(txt)
143 {
144 options.debug ? options.debug("canvasview.js: "+txt) : alert("canvasview.js: "+txt);
145 }
146
147 s.options = options;
148
149 if (options.fontsize) s.fontsize = options.fontsize;
150 if (options.autoResize) s.autoResize = options.autoResize;
151 if (options.font) s.fontName = options.font;
152 if (options.fontfamily) s.fontFamily = options.fontfamily;
153 if (options.boldfont) s.boldFontName = options.boldfont;
154 if (options.autoResize) s.autoResize = options.autoResize;
155 if (options.callback) s.callback = options.callback;
156 if (options.onReady) s.onReady.push(options.onReady);
157
158 s.cursor = { };
159 s.cursor.x = 0;
160 s.cursor.y = 0;
161 s.cursor.visible = true;
162
163 s.emu = new TTVEmulator({
164 debug: s.options.debug,
165 debugLevel: s.options.debugLevel,
166 width: s.options.width,
167 height: s.options.height,
168 change: function(y, minx, maxx)
169 {
170 s.makeSpanDirty(y, minx, maxx);
171 },
172 cursor: function(x, y, vis)
173 {
174 if (x >= s.emu.width)
175 x = s.emu.width - 1;
176
177 // signal that the old and new position
178 // must be redrawn with the next update
179 s.makeSpanDirty(y, x, x);
180 s.makeSpanDirty(s.cursor.y, s.cursor.x, s.cursor.x);
181 //s.makeSpanDirty(s.cursor.cur.y, s.cursor.cur.x, s.cursor.cur.x);
182
183 s.cursor.x = x;
184 s.cursor.y = y;
185 s.cursor.visible = vis;
186
187 },
188 special: function(obj)
189 {
190 if (!s.callback)
191 return;
192
193 if (obj.title || obj.icon)
194 s.callback(obj);
195
196 if (obj.border)
197 {
198 var col = obj.border[0];
199
200 // Because of 'fontWidth' this must not be
201 // called before the fonts are ready!
202 if (col>=0 && col<8)
203 {
204 s.callback({ 'border': s.stdRGB[col], 'width': obj.border[1]*s.font.charWidth });
205 return;
206 }
207 if (col>=10 && col<18)
208 {
209 s.callback({ 'border': s.hiRGB[col%10], 'width': obj.border[1]*s.font.charWidth });
210 return;
211 }
212 s.callback({ 'border': null });
213 }
214
215 /*
216 if (obj.bell)
217 {
218 }
219 */
220 }
221 });
222
223 s.parser = new TTVParser(s.emu, s.options.debug);
224
225 s.dirtySpans = [];
226 for (var y = 0; y < s.emu.height; y++)
227 s.dirtySpans[y] = { min: 0, max: s.emu.width-1 };
228
229 // Now wait until the fonts are ready
230 setTimeout(function() { s.waitForFonts(); }, 1);
231
232 return this;
233};
234
235/**
236 * Callback which is called when the fonts are loaded. When
237 * the fonts are ready, and a boldfont was set, the consistency
238 * of their size is checked. If autoSize was set, the canvas is
239 * resized automatically according to the font size. Then a
240 * canvas update is forced and the ready-callbacks are invoked.
241 *
242 * @private
243 *
244 */
245TTVCanvas.prototype.readyCheck = function ()
246{
247 if (!this.fontsize)
248 {
249 if (!this.font)
250 return;
251
252 if (this.boldFontName)
253 {
254 if (!this.boldfont)
255 return;
256
257 if (this.font.charWidth != this.boldfont.charWidth ||
258 this.font.charHeight != this.boldfont.charHeight)
259 {
260 this.debug("Normal font size ["+this.font.charWidth+"x"+this.font.charHeight+"] and "+
261 "bold font size ["+this.boldfont.charWidth+"x"+this.font.charHeight+"] mismatch");
262 this.boldfont = undefined;
263 }
264 }
265 }
266
267 if (this.autoResize)
268 {
269 this.canvas.setAttribute('width', this.emu.width * this.font.charWidth);
270 this.canvas.setAttribute('height', this.emu.height * this.font.charHeight);
271 }
272
273 this.updateCanvas();
274 this.onReady.forEach(function (fn) { fn(); });
275};
276
277
278/**
279 * @returns
280 * an object which is a current state snapshot or the
281 * emulator and the parser;
282 */
283TTVCanvas.prototype.snapshot = function()
284{
285 return {
286 emulator: this.emu.freeze(),
287 parser: this.parser.getBuffer()
288 };
289};
290
291/**
292 * Takes a snapshop as set with snapshot() and configures
293 * the emulator and parser accordingly
294 *
295 * @param {Object} obj
296 * The snapshot object as returned by snapshot()
297 */
298TTVCanvas.prototype.thaw = function(obj)
299{
300 this.emu.thaw(obj.emulator);
301 this.parser.setBuffer(obj.parser);
302};
303
304/**
305 * Used to signal a change of a given region which will cause it to be
306 * updated with the next draw-update
307 *
308 * @param {Number} y
309 * The index of the row which contains the changes
310 *
311 * @param {Number} minx
312 * The starting index of the column
313 *
314 * @param {Number} maxx
315 * The index of the last column
316 */
317TTVCanvas.prototype.makeSpanDirty = function(y, minx, maxx)
318{
319 if (y>=this.emu.height || minx<0 || maxx>=this.emu.width)
320 throw Error("makeSpanDirty "+y+" "+this.emu.height+" "+minx+ " "+maxx+ " "+this.emu.width);
321
322 var s = this.dirtySpans[y];
323
324 if (s.min>minx)
325 s.min = minx;
326
327 if (s.max<maxx)
328 s.max = maxx;
329}
330
331/**
332 * Copy the current state of the display into the canvas.
333 */
334TTVCanvas.prototype.updateCanvas = function ()
335{
336 var ctx = this.canvas.getContext('2d');
337 ctx.textBaseline = "bottom";
338 ctx.lineWidth = 1;
339 ctx.beginPath();
340
341 var w = this.font.charWidth;
342 var h = this.font.charHeight;
343
344 var save, color, strokeColor
345
346 if (!this.fontsize)
347 {
348 for (var y = 0; y < this.emu.height; y++)
349 {
350 var span = this.dirtySpans[y];
351 for (var x = span.min; x <= span.max; x++)
352 {
353 var idx = y*this.emu.width+x;
354
355 var fcolor = this.emu.scr.c.fcolor[idx]%10;
356 var bcolor = this.emu.scr.c.bcolor[idx]%10;
357 var bold = this.emu.scr.c.bold[idx];
358 var ch = this.emu.scr.c.text[idx];
359
360 // Display cursor (fixme: could be any cursor)
361 //if (this.cursor.cur.x==x && this.cursor.cur.y==y && this.cursor.cur.visible)
362 var fg, bg;
363 var show = this.cursor.x==x && this.cursor.y==y && this.cursor.visible;
364 if (!show)
365 {
366 fg = (fcolor>9 || bold ? this.hiRGB : this.stdRGB)[fcolor];
367 bg = (bcolor>9 ? this.hiRGB : this.stdRGB)[bcolor];
368 }
369 else
370 {
371 bg = (fcolor>9 || bold ? this.hiRGB : this.stdRGB)[fcolor];
372 fg = (bcolor>9 ? this.hiRGB : this.stdRGB)[bcolor];
373 }
374 /*
375 if (!fg)
376 {
377 this.debug("FGcolor @ "+x+"/"+y+" ["+fcolor+"]");
378 fg = '#167ff0';
379 }
380
381 if (!bg)
382 {
383 this.debug("BGcolor @ "+x+"/"+y+" ["+bcolor+"]");
384 bg = '#167ff0';
385 }
386
387 if (this.fontsize) //33s
388 {
389 // font-style font-variant font-weight font-size/line-height font-family
390
391 // font-style: normal, italic, oblique
392 // font-variant: normal, small-caps
393 // font-weight: normal, bold, bolder, lighter (100-900)
394
395 ctx.fillStyle = bg;
396 ctx.fillRect(x*w, y*h, w, h);
397 if (ch.charCodeAt(0)!=32)
398 {
399 ctx.fillStyle = fg;
400
401 var font = bold ? this.boldFontName : this.fontName;
402 if (save!=font)
403 ctx.font = save = font;
404
405 ctx.fillText(ch, x*w, (y+1)*h);
406 }
407 }
408 else*/ // 27s
409 {
410 if (bold && this.boldfont)
411 this.boldfont.drawChar(ctx, ch, x*w, y*h, fg, bg);
412 else
413 this.font.drawChar(ctx, ch, x*w, y*h, fg, bg);
414 }
415
416 // konsole
417 // \033]50;CursorShape=0\007 block
418 // \033]50;CursorShape=1\007 vertical line on the left
419 // \033]50;CursorShape=2\007 underline
420
421 // note: this is not super efficient
422 if (this.emu.scr.c.underline[idx])// || (show && !this.emu.scr.c.blink[idx])
423 {
424 if (fg != strokeColor)
425 {
426 ctx.stroke();
427 ctx.strokeStyle = strokeColor = fg;
428 ctx.beginPath();
429 }
430
431 ctx.moveTo(x*w, (y+1)*h-1);
432 ctx.lineTo((x+1)*w, (y+1)*h-1);
433 }
434 }
435 span.min = this.emu.width-1;
436 span.max = 0;
437 }
438 }
439 else // 30s
440 {
441 // This is trying to minimize the frequence of color and font changes
442 for (var y = 0; y < this.emu.height; y++)
443 {
444 var span = this.dirtySpans[y];
445 for (var x = span.min; x <= span.max; x++)
446 {
447 var idx = y*this.emu.width+x;
448 var show = this.cursor.x==x && this.cursor.y==y && this.cursor.visible;
449
450 // Display cursor (fixme: could be any cursor)
451 var bg;
452 if (!show)
453 {
454 var bcolor = this.emu.scr.c.bcolor[idx]%10;
455 bg = (bcolor>9 ? this.hiRGB : this.stdRGB)[bcolor];
456 }
457 else
458 {
459 var fcolor = this.emu.scr.c.fcolor[idx]%10;
460 bg = (fcolor>9 || this.emu.scr.c.bold[idx] ? this.hiRGB : this.stdRGB)[fcolor];
461 }
462
463 if (color!=bg)
464 ctx.fillStyle = color = bg;
465 ctx.fillRect(x*w, y*h, w, h);
466 }
467 for (var x = span.min; x <= span.max; x++)
468 {
469 var idx = y*this.emu.width+x;
470
471 var bold = this.emu.scr.c.bold[idx];
472 var ch = this.emu.scr.c.text[idx];
473 var show = this.cursor.x==x && this.cursor.y==y && this.cursor.visible;
474
475 var fg;
476 if (!show)
477 {
478 var fcolor = this.emu.scr.c.fcolor[idx]%10;
479 fg = (fcolor>9 || bold ? this.hiRGB : this.stdRGB)[fcolor];
480 }
481 else
482 {
483 var bcolor = this.emu.scr.c.bcolor[idx]%10;
484 fg = (bcolor>9 ? this.hiRGB : this.stdRGB)[bcolor];
485 }
486
487
488 if (ch.charCodeAt(0)!=32)
489 {
490 if (color!=fg)
491 ctx.fillStyle = color = fg;
492
493 var font = this.normalFontName;
494 var offset = this.normalFontOffset;
495 if (bold)
496 {
497 font = this.boldFontName;
498 offset = this.boldFontOffset;
499 }
500 if (this.emu.scr.c.lowintensity[idx])
501 {
502 font = this.lightFontName;
503 offset = this.lightFontOffset;
504 }
505
506 if (save!=font)
507 ctx.font = save = font;
508
509 ctx.fillText(ch, x*w+offset, (y+1)*h);
510 ctx.stroke();
511 }
512
513 if (this.emu.scr.c.underline[idx])// || (show && !this.emu.scr.c.blink[idx])
514 {
515 if (fg != strokeColor)
516 {
517 ctx.stroke();
518 ctx.strokeStyle = strokeColor = fg;
519 ctx.beginPath();
520 }
521
522 ctx.moveTo(x*w, (y+1)*h-1);
523 ctx.lineTo((x+1)*w, (y+1)*h-1);
524 }
525 }
526 span.min = this.emu.width-1;
527 span.max = 0;
528 }
529 }
530
531 ctx.stroke();
532}
Note: See TracBrowser for help on using the repository browser.