1 /*
2  * Nestopia UE
3  *
4  * Copyright (C) 2007-2008 R. Belmont
5  * Copyright (C) 2012-2021 R. Danbrook
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  *
22  */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include <time.h>
28 
29 #include "core/api/NstApiEmulator.hpp"
30 #include "core/api/NstApiInput.hpp"
31 #include "core/api/NstApiVideo.hpp"
32 #include "core/api/NstApiNsf.hpp"
33 
34 #include <FL/gl.h>
35 
36 #include "nstcommon.h"
37 #include "video.h"
38 #include "config.h"
39 #include "font.h"
40 #include "png.h"
41 
42 using namespace Nes::Api;
43 
44 static int overscan_offset, overscan_height;
45 
46 static uint32_t videobuf[VIDBUF_MAXSIZE]; // Maximum possible internal size
47 
48 static Video::RenderState::Filter filter;
49 static Video::RenderState renderstate;
50 
51 static dimensions_t basesize, rendersize, screensize;
52 static osdtext_t osdtext;
53 
54 extern void *custompalette;
55 
56 extern nstpaths_t nstpaths;
57 extern Emulator emulator;
58 
59 GLuint gl_texture_id = 0;
60 
nst_ogl_init()61 void nst_ogl_init() {
62 	// Initialize OpenGL
63 	glEnable(GL_TEXTURE_2D);
64 
65 	glGenTextures(1, &gl_texture_id);
66 	glBindTexture(GL_TEXTURE_2D, gl_texture_id);
67 
68 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, conf.video_linear_filter ? GL_LINEAR : GL_NEAREST);
69 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
70 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
71 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
72 
73 	conf.video_fullscreen ?
74 	glViewport(screensize.w / 2.0f - rendersize.w / 2.0f, 0, rendersize.w, rendersize.h) :
75 	glViewport(0, 0, rendersize.w, rendersize.h);
76 
77 	glDisable(GL_DEPTH_TEST);
78 	glDisable(GL_ALPHA_TEST);
79 	glDisable(GL_BLEND);
80 	glDisable(GL_LIGHTING);
81 	glMatrixMode(GL_PROJECTION);
82 	glLoadIdentity();
83 	glOrtho(0.0, rendersize.w * conf.video_scale_factor, rendersize.h * conf.video_scale_factor, 0.0, -1.0, 1.0);
84 	glMatrixMode(GL_MODELVIEW);
85 	glLoadIdentity();
86 }
87 
nst_ogl_deinit()88 void nst_ogl_deinit() {
89 	// Deinitialize OpenGL
90 	if (gl_texture_id) {
91 		glDeleteTextures(1, &gl_texture_id);
92 	}
93 }
94 
nst_ogl_render()95 void nst_ogl_render() {
96 	// Render the scene
97 	glTexImage2D(GL_TEXTURE_2D,
98 				0,
99 				GL_RGBA,
100 				basesize.w,
101 				overscan_height,
102 				0,
103 				GL_BGRA,
104 				GL_UNSIGNED_BYTE,
105 		videobuf + overscan_offset);
106 
107 	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
108 	glClear(GL_COLOR_BUFFER_BIT);
109 
110 	glBegin(GL_QUADS);
111 		glTexCoord2f(1.0f, 1.0f);
112 		glVertex2f(rendersize.w * conf.video_scale_factor, rendersize.h * conf.video_scale_factor);
113 
114 		glTexCoord2f(1.0f, 0.0f);
115 		glVertex2f(rendersize.w * conf.video_scale_factor, 0.0);
116 
117 		glTexCoord2f(0.0f, 0.0f);
118 		glVertex2f(0.0, 0.0);
119 
120 		glTexCoord2f(0.0f, 1.0f);
121 		glVertex2f(0, rendersize.h * conf.video_scale_factor);
122 	glEnd();
123 }
124 
nst_video_refresh()125 void nst_video_refresh() {
126 	// Refresh the video settings
127 	nst_ogl_deinit();
128 
129 	nst_ogl_init();
130 }
131 
video_init()132 void video_init() {
133 	// Initialize video
134 	nst_ogl_deinit();
135 
136 	video_set_dimensions();
137 	video_set_filter();
138 
139 	nst_ogl_init();
140 
141 	if (nst_nsf()) { video_clear_buffer(); video_disp_nsf(); }
142 }
143 
video_toggle_filterupdate()144 void video_toggle_filterupdate() {
145 	// Clear the filter update flag
146 	Video video(emulator);
147 	video.ClearFilterUpdateFlag();
148 }
149 
video_set_filter()150 void video_set_filter() {
151 	// Set the filter
152 	Video video(emulator);
153 	int scalefactor = conf.video_scale_factor;
154 	if (conf.video_scale_factor > 4) { scalefactor = 4; }
155 	if ((conf.video_scale_factor > 3) && (conf.video_filter == 5)) { scalefactor = 3; }
156 
157 	switch(conf.video_filter) {
158 		case 0:	// None
159 			filter = Video::RenderState::FILTER_NONE;
160 			break;
161 
162 		case 1: // NTSC
163 			filter = Video::RenderState::FILTER_NTSC;
164 			break;
165 
166 		case 2: // xBR
167 			switch (scalefactor) {
168 				case 2:
169 					filter = Video::RenderState::FILTER_2XBR;
170 					break;
171 				case 3:
172 					filter = Video::RenderState::FILTER_3XBR;
173 					break;
174 				case 4:
175 					filter = Video::RenderState::FILTER_4XBR;
176 					break;
177 				default:
178 					filter = Video::RenderState::FILTER_NONE;
179 					break;
180 			}
181 			break;
182 
183 		case 3: // scale HQx
184 			switch (scalefactor) {
185 				case 2:
186 					filter = Video::RenderState::FILTER_HQ2X;
187 					break;
188 				case 3:
189 					filter = Video::RenderState::FILTER_HQ3X;
190 					break;
191 				case 4:
192 					filter = Video::RenderState::FILTER_HQ4X;
193 					break;
194 				default:
195 					filter = Video::RenderState::FILTER_NONE;
196 					break;
197 			}
198 			break;
199 
200 		case 4: // 2xSaI
201 			filter = Video::RenderState::FILTER_2XSAI;
202 			break;
203 
204 		case 5: // scale x
205 			switch (scalefactor) {
206 				case 2:
207 					filter = Video::RenderState::FILTER_SCALE2X;
208 					break;
209 				case 3:
210 					filter = Video::RenderState::FILTER_SCALE3X;
211 					break;
212 				default:
213 					filter = Video::RenderState::FILTER_NONE;
214 					break;
215 			}
216 			break;
217 		break;
218 	}
219 
220 	// Set the sprite limit:  false = enable sprite limit, true = disable sprite limit
221 	video.EnableUnlimSprites(conf.video_unlimited_sprites ? true : false);
222 
223 	// Set Palette options
224 	switch (conf.video_palette_mode) {
225 		case 0: // YUV
226 			video.GetPalette().SetMode(Video::Palette::MODE_YUV);
227 			break;
228 
229 		case 1: // RGB
230 			video.GetPalette().SetMode(Video::Palette::MODE_RGB);
231 			break;
232 
233 		case 2: // Custom
234 			video.GetPalette().SetMode(Video::Palette::MODE_CUSTOM);
235 			video.GetPalette().SetCustom((const unsigned char (*)[3])custompalette, Video::Palette::EXT_PALETTE);
236 			break;
237 
238 		default: break;
239 	}
240 
241 	// Set YUV Decoder/Picture options
242 	if (video.GetPalette().GetMode() == Video::Palette::MODE_YUV) {
243 		switch (conf.video_decoder) {
244 			case 0: // Consumer
245 				video.SetDecoder(Video::DECODER_CONSUMER);
246 				break;
247 
248 			case 1: // Canonical
249 				video.SetDecoder(Video::DECODER_CANONICAL);
250 				break;
251 
252 			case 2: // Alternative (Canonical with yellow boost)
253 				video.SetDecoder(Video::DECODER_ALTERNATIVE);
254 				break;
255 
256 			default: break;
257 		}
258 	}
259 
260 	video.SetBrightness(conf.video_brightness);
261 	video.SetSaturation(conf.video_saturation);
262 	video.SetContrast(conf.video_contrast);
263 	video.SetHue(conf.video_hue);
264 
265 	// Set NTSC options
266 	if (conf.video_filter == 1) {
267 		switch (conf.video_ntsc_mode) {
268 			case 0:	// Composite
269 				video.SetSaturation(Video::DEFAULT_SATURATION_COMP);
270 				video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP);
271 				video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP);
272 				video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP);
273 				video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP);
274 				video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP);
275 				break;
276 
277 			case 1:	// S-Video
278 				video.SetSaturation(Video::DEFAULT_SATURATION_SVIDEO);
279 				video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO);
280 				video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO);
281 				video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO);
282 				video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO);
283 				video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO);
284 				break;
285 
286 			case 2:	// RGB
287 				video.SetSaturation(Video::DEFAULT_SATURATION_RGB);
288 				video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB);
289 				video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB);
290 				video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB);
291 				video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB);
292 				video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB);
293 				break;
294 
295 			case 3: // Monochrome
296 				video.SetSaturation(Video::DEFAULT_SATURATION_MONO);
297 				video.SetSharpness(Video::DEFAULT_SHARPNESS_MONO);
298 				video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_MONO);
299 				video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_MONO);
300 				video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_MONO);
301 				video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_MONO);
302 				break;
303 
304 			case 4: // Custom
305 				video.SetSaturation(conf.video_saturation);
306 				video.SetSharpness(conf.video_ntsc_sharpness);
307 				video.SetColorResolution(conf.video_ntsc_resolution);
308 				video.SetColorBleed(conf.video_ntsc_bleed);
309 				video.SetColorArtifacts(conf.video_ntsc_artifacts);
310 				video.SetColorFringing(conf.video_ntsc_fringing);
311 				break;
312 
313 			default: break;
314 		}
315 	}
316 
317 	// Set xBR options
318 	if (conf.video_filter == 2) {
319 		video.SetCornerRounding(conf.video_xbr_corner_rounding);
320 		video.SetBlend(conf.video_xbr_pixel_blending);
321 	}
322 
323 	// Set up the render state parameters
324 	renderstate.filter = filter;
325 	renderstate.width = basesize.w;
326 	renderstate.height = basesize.h;
327 	renderstate.bits.count = 32;
328 
329 	int e = 1; // Check Endianness
330 	if ((int)*((unsigned char *)&e) == 1) { // Little Endian
331 		renderstate.bits.mask.r = 0x00ff0000;
332 		renderstate.bits.mask.g = 0x0000ff00;
333 		renderstate.bits.mask.b = 0x000000ff;
334 	}
335 	else { // Big Endian
336 		renderstate.bits.mask.r = 0x0000ff00;
337 		renderstate.bits.mask.g = 0x00ff0000;
338 		renderstate.bits.mask.b = 0xff000000;
339 	}
340 
341 	if (NES_FAILED(video.SetRenderState(renderstate))) {
342 		fprintf(stderr, "Nestopia core rejected render state\n");
343 		exit(1);
344 	}
345 }
346 
nst_video_get_dimensions_render()347 dimensions_t nst_video_get_dimensions_render() {
348 	// Return the dimensions of the rendered video
349 	return rendersize;
350 }
351 
nst_video_get_dimensions_screen()352 dimensions_t nst_video_get_dimensions_screen() {
353 	// Return the dimensions of the screen
354 	return screensize;
355 }
356 
nst_video_set_dimensions_screen(dimensions_t scrsize)357 void nst_video_set_dimensions_screen(dimensions_t scrsize) {
358 	screensize = scrsize;
359 }
360 
video_set_dimensions()361 void video_set_dimensions() {
362 	// Set up the video dimensions
363 	int scalefactor = conf.video_scale_factor;
364 	if (conf.video_scale_factor > 4) { scalefactor = 4; }
365 	if ((conf.video_scale_factor > 3) && (conf.video_filter == 5)) { scalefactor = 3; }
366 	int wscalefactor = conf.video_scale_factor;
367 	int tvwidth = nst_pal() ? PAL_TV_WIDTH : TV_WIDTH;
368 
369 	switch(conf.video_filter) {
370 		case 0:	// None
371 			basesize.w = Video::Output::WIDTH;
372 			basesize.h = Video::Output::HEIGHT;
373 			conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = basesize.w * wscalefactor;
374 			rendersize.h = basesize.h * wscalefactor;
375 			overscan_offset = basesize.w * OVERSCAN_TOP;
376 			overscan_height = basesize.h - OVERSCAN_TOP - OVERSCAN_BOTTOM;
377 			break;
378 
379 		case 1: // NTSC
380 			basesize.w = Video::Output::NTSC_WIDTH;
381 			rendersize.w = (basesize.w / 2) * wscalefactor;
382 			basesize.h = Video::Output::HEIGHT;
383 			rendersize.h = basesize.h * wscalefactor;
384 			overscan_offset = basesize.w * OVERSCAN_TOP;
385 			overscan_height = basesize.h - OVERSCAN_TOP - OVERSCAN_BOTTOM;
386 			break;
387 
388 		case 2: // xBR
389 		case 3: // HqX
390 		case 5: // ScaleX
391 			basesize.w = Video::Output::WIDTH * scalefactor;
392 			basesize.h = Video::Output::HEIGHT * scalefactor;
393 			conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = Video::Output::WIDTH * wscalefactor;
394 			rendersize.h = Video::Output::HEIGHT * wscalefactor;
395 			overscan_offset = basesize.w * OVERSCAN_TOP * scalefactor;
396 			overscan_height = basesize.h - (OVERSCAN_TOP + OVERSCAN_BOTTOM) * scalefactor;
397 			break;
398 
399 		case 4: // 2xSaI
400 			basesize.w = Video::Output::WIDTH * 2;
401 			basesize.h = Video::Output::HEIGHT * 2;
402 			conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = Video::Output::WIDTH * wscalefactor;
403 			rendersize.h = Video::Output::HEIGHT * wscalefactor;
404 			overscan_offset = basesize.w * OVERSCAN_TOP * 2;
405 			overscan_height = basesize.h - (OVERSCAN_TOP + OVERSCAN_BOTTOM) * 2;
406 			break;
407 	}
408 
409 	if (!conf.video_unmask_overscan) {
410 		rendersize.h -= (OVERSCAN_TOP + OVERSCAN_BOTTOM) * scalefactor;
411 	}
412 	else { overscan_offset = 0; overscan_height = basesize.h; }
413 
414 	// Calculate the aspect from the height because it's smaller
415 	if (conf.video_fullscreen) {
416 		float aspect = (float)screensize.h / (float)rendersize.h;
417 		rendersize.w *= aspect;
418 		rendersize.h *= aspect;
419 	}
420 }
421 
video_lock_screen(void * & ptr)422 long video_lock_screen(void*& ptr) {
423 	ptr = videobuf;
424 	return basesize.w * 4;
425 }
426 
video_unlock_screen(void *)427 void video_unlock_screen(void*) {
428 	int xscale = renderstate.width / Video::Output::WIDTH;;
429 	int yscale = renderstate.height / Video::Output::HEIGHT;
430 
431 	if (osdtext.drawtext) {
432 		nst_video_text_draw(osdtext.textbuf, osdtext.xpos * xscale, osdtext.ypos * yscale, osdtext.bg);
433 		osdtext.drawtext--;
434 	}
435 
436 	if (osdtext.drawtime) {
437 		nst_video_text_draw(osdtext.timebuf, 208 * xscale, 218 * yscale, false);
438 	}
439 }
440 
video_screenshot_flip(unsigned char * pixels,int width,int height,int bytes)441 void video_screenshot_flip(unsigned char *pixels, int width, int height, int bytes) {
442 	// Flip the pixels
443 	int rowsize = width * bytes;
444 	unsigned char *row = (unsigned char*)malloc(rowsize);
445 	unsigned char *low = pixels;
446 	unsigned char *high = &pixels[(height - 1) * rowsize];
447 
448 	for (; low < high; low += rowsize, high -= rowsize) {
449 		memcpy(row, low, rowsize);
450 		memcpy(low, high, rowsize);
451 		memcpy(high, row, rowsize);
452 	}
453 	free(row);
454 }
455 
video_screenshot(const char * filename)456 void video_screenshot(const char* filename) {
457 	// Take a screenshot in .png format
458 	unsigned char *pixels;
459 	pixels = (unsigned char*)malloc(sizeof(unsigned char) * rendersize.w * rendersize.h * 4);
460 
461 	// Read the pixels and flip them vertically
462 	glReadPixels(0, 0, rendersize.w, rendersize.h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
463 	video_screenshot_flip(pixels, rendersize.w, rendersize.h, 4);
464 
465 	if (filename == NULL) {
466 		// Set the filename
467 		char sshotpath[512];
468 		snprintf(sshotpath, sizeof(sshotpath), "%sscreenshots/%s-%ld-%d.png", nstpaths.nstdir, nstpaths.gamename, time(NULL), rand() % 899 + 100);
469 
470 		// Save the file
471 		lodepng_encode32_file(sshotpath, (const unsigned char*)pixels, rendersize.w, rendersize.h);
472 		fprintf(stderr, "Screenshot: %s\n", sshotpath);
473 	}
474 	else {
475 		lodepng_encode32_file(filename, (const unsigned char*)pixels, rendersize.w, rendersize.h);
476 	}
477 
478 	free(pixels);
479 }
480 
video_clear_buffer()481 void video_clear_buffer() {
482 	// Write black to the video buffer
483 	memset(videobuf, 0x00000000, VIDBUF_MAXSIZE);
484 }
485 
video_disp_nsf()486 void video_disp_nsf() {
487 	// Display NSF text
488 	Nsf nsf(emulator);
489 
490 	int xscale = renderstate.width / Video::Output::WIDTH;;
491 	int yscale = renderstate.height / Video::Output::HEIGHT;;
492 
493 	nst_video_text_draw(nsf.GetName(), 4 * xscale, 16 * yscale, false);
494 	nst_video_text_draw(nsf.GetArtist(), 4 * xscale, 28 * yscale, false);
495 	nst_video_text_draw(nsf.GetCopyright(), 4 * xscale, 40 * yscale, false);
496 
497 	char currentsong[10];
498 	snprintf(currentsong, sizeof(currentsong), "%d / %d", nsf.GetCurrentSong() +1, nsf.GetNumSongs());
499 	nst_video_text_draw(currentsong, 4 * xscale, 52 * yscale, false);
500 
501 	nst_ogl_render();
502 }
503 
nst_video_print(const char * text,int xpos,int ypos,int seconds,bool bg)504 void nst_video_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
505 	snprintf(osdtext.textbuf, sizeof(osdtext.textbuf), "%s", text);
506 	osdtext.xpos = xpos;
507 	osdtext.ypos = ypos;
508 	osdtext.drawtext = seconds * nst_pal() ? 50 : 60;
509 	osdtext.bg = bg;
510 }
511 
nst_video_print_time(const char * timebuf,bool drawtime)512 void nst_video_print_time(const char *timebuf, bool drawtime) {
513 	snprintf(osdtext.timebuf, sizeof(osdtext.timebuf), "%s", timebuf);
514 	osdtext.drawtime = drawtime;
515 }
516 
nst_video_text_draw(const char * text,int xpos,int ypos,bool bg)517 void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg) {
518 	// Draw text on screen
519 	uint32_t w = 0xc0c0c0c0; // "White", actually Grey
520 	uint32_t b = 0x00000000; // Black
521 	uint32_t g = 0x00358570; // Nestopia UE Green
522 	uint32_t d = 0x00255f65; // Nestopia UE Dark Green
523 
524 	int numchars = strlen(text);
525 
526 	int letterypos;
527 	int letterxpos;
528 	int letternum = 0;
529 
530 	if (bg) { // Draw background borders
531 		for (int i = 0; i < numchars * 8; i++) { // Rows above and below
532 			videobuf[(xpos + i) + ((ypos - 1) * renderstate.width)] = g;
533 			videobuf[(xpos + i) + ((ypos + 8) * renderstate.width)] = g;
534 		}
535 		for (int i = 0; i < 8; i++) { // Columns on both sides
536 			videobuf[(xpos - 1) + ((ypos + i) * renderstate.width)] = g;
537 			videobuf[(xpos + (numchars * 8)) + ((ypos + i) * renderstate.width)] = g;
538 		}
539 	}
540 
541 	for (int tpos = 0; tpos < (8 * numchars); tpos+=8) {
542 		nst_video_text_match(text, &letterxpos, &letterypos, letternum);
543 		for (int row = 0; row < 8; row++) { // Draw Rows
544 			for (int col = 0; col < 8; col++) { // Draw Columns
545 				switch (nesfont[row + letterypos][col + letterxpos]) {
546 					case '.':
547 						videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = w;
548 						break;
549 
550 					case '+':
551 						videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = g;
552 						break;
553 
554 					default:
555 						if (bg) { videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = d; }
556 						break;
557 				}
558 			}
559 		}
560 		letternum++;
561 	}
562 }
563 
nst_video_text_match(const char * text,int * xpos,int * ypos,int strpos)564 void nst_video_text_match(const char *text, int *xpos, int *ypos, int strpos) {
565 	// Match letters to draw on screen
566 	switch (text[strpos]) {
567 		case ' ': *xpos = 0; *ypos = 0; break;
568 		case '!': *xpos = 8; *ypos = 0; break;
569 		case '"': *xpos = 16; *ypos = 0; break;
570 		case '#': *xpos = 24; *ypos = 0; break;
571 		case '$': *xpos = 32; *ypos = 0; break;
572 		case '%': *xpos = 40; *ypos = 0; break;
573 		case '&': *xpos = 48; *ypos = 0; break;
574 		case '\'': *xpos = 56; *ypos = 0; break;
575 		case '(': *xpos = 64; *ypos = 0; break;
576 		case ')': *xpos = 72; *ypos = 0; break;
577 		case '*': *xpos = 80; *ypos = 0; break;
578 		case '+': *xpos = 88; *ypos = 0; break;
579 		case ',': *xpos = 96; *ypos = 0; break;
580 		case '-': *xpos = 104; *ypos = 0; break;
581 		case '.': *xpos = 112; *ypos = 0; break;
582 		case '/': *xpos = 120; *ypos = 0; break;
583 		case '0': *xpos = 0; *ypos = 8; break;
584 		case '1': *xpos = 8; *ypos = 8; break;
585 		case '2': *xpos = 16; *ypos = 8; break;
586 		case '3': *xpos = 24; *ypos = 8; break;
587 		case '4': *xpos = 32; *ypos = 8; break;
588 		case '5': *xpos = 40; *ypos = 8; break;
589 		case '6': *xpos = 48; *ypos = 8; break;
590 		case '7': *xpos = 56; *ypos = 8; break;
591 		case '8': *xpos = 64; *ypos = 8; break;
592 		case '9': *xpos = 72; *ypos = 8; break;
593 		case ':': *xpos = 80; *ypos = 8; break;
594 		case ';': *xpos = 88; *ypos = 8; break;
595 		case '<': *xpos = 96; *ypos = 8; break;
596 		case '=': *xpos = 104; *ypos = 8; break;
597 		case '>': *xpos = 112; *ypos = 8; break;
598 		case '?': *xpos = 120; *ypos = 8; break;
599 		case '@': *xpos = 0; *ypos = 16; break;
600 		case 'A': *xpos = 8; *ypos = 16; break;
601 		case 'B': *xpos = 16; *ypos = 16; break;
602 		case 'C': *xpos = 24; *ypos = 16; break;
603 		case 'D': *xpos = 32; *ypos = 16; break;
604 		case 'E': *xpos = 40; *ypos = 16; break;
605 		case 'F': *xpos = 48; *ypos = 16; break;
606 		case 'G': *xpos = 56; *ypos = 16; break;
607 		case 'H': *xpos = 64; *ypos = 16; break;
608 		case 'I': *xpos = 72; *ypos = 16; break;
609 		case 'J': *xpos = 80; *ypos = 16; break;
610 		case 'K': *xpos = 88; *ypos = 16; break;
611 		case 'L': *xpos = 96; *ypos = 16; break;
612 		case 'M': *xpos = 104; *ypos = 16; break;
613 		case 'N': *xpos = 112; *ypos = 16; break;
614 		case 'O': *xpos = 120; *ypos = 16; break;
615 		case 'P': *xpos = 0; *ypos = 24; break;
616 		case 'Q': *xpos = 8; *ypos = 24; break;
617 		case 'R': *xpos = 16; *ypos = 24; break;
618 		case 'S': *xpos = 24; *ypos = 24; break;
619 		case 'T': *xpos = 32; *ypos = 24; break;
620 		case 'U': *xpos = 40; *ypos = 24; break;
621 		case 'V': *xpos = 48; *ypos = 24; break;
622 		case 'W': *xpos = 56; *ypos = 24; break;
623 		case 'X': *xpos = 64; *ypos = 24; break;
624 		case 'Y': *xpos = 72; *ypos = 24; break;
625 		case 'Z': *xpos = 80; *ypos = 24; break;
626 		case '[': *xpos = 88; *ypos = 24; break;
627 		case '\\': *xpos = 96; *ypos = 24; break;
628 		case ']': *xpos = 104; *ypos = 24; break;
629 		case '^': *xpos = 112; *ypos = 24; break;
630 		case '_': *xpos = 120; *ypos = 24; break;
631 		case '`': *xpos = 0; *ypos = 32; break;
632 		case 'a': *xpos = 8; *ypos = 32; break;
633 		case 'b': *xpos = 16; *ypos = 32; break;
634 		case 'c': *xpos = 24; *ypos = 32; break;
635 		case 'd': *xpos = 32; *ypos = 32; break;
636 		case 'e': *xpos = 40; *ypos = 32; break;
637 		case 'f': *xpos = 48; *ypos = 32; break;
638 		case 'g': *xpos = 56; *ypos = 32; break;
639 		case 'h': *xpos = 64; *ypos = 32; break;
640 		case 'i': *xpos = 72; *ypos = 32; break;
641 		case 'j': *xpos = 80; *ypos = 32; break;
642 		case 'k': *xpos = 88; *ypos = 32; break;
643 		case 'l': *xpos = 96; *ypos = 32; break;
644 		case 'm': *xpos = 104; *ypos = 32; break;
645 		case 'n': *xpos = 112; *ypos = 32; break;
646 		case 'o': *xpos = 120; *ypos = 32; break;
647 		case 'p': *xpos = 0; *ypos = 40; break;
648 		case 'q': *xpos = 8; *ypos = 40; break;
649 		case 'r': *xpos = 16; *ypos = 40; break;
650 		case 's': *xpos = 24; *ypos = 40; break;
651 		case 't': *xpos = 32; *ypos = 40; break;
652 		case 'u': *xpos = 40; *ypos = 40; break;
653 		case 'v': *xpos = 48; *ypos = 40; break;
654 		case 'w': *xpos = 56; *ypos = 40; break;
655 		case 'x': *xpos = 64; *ypos = 40; break;
656 		case 'y': *xpos = 72; *ypos = 40; break;
657 		case 'z': *xpos = 80; *ypos = 40; break;
658 		case '{': *xpos = 88; *ypos = 40; break;
659 		case '|': *xpos = 96; *ypos = 40; break;
660 		case '}': *xpos = 104; *ypos = 40; break;
661 		case '~': *xpos = 112; *ypos = 40; break;
662 		//case ' ': *xpos = 120; *ypos = 40; break; // Triangle
663 		default: *xpos = 0; *ypos = 0; break;
664 	}
665 }
666