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

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