/* This code is (C) AllegroGL contributors, and double licensed under * the GPL and zlib licenses. See gpl.txt or zlib.txt for details. */ /** \file aglf.c * \brief Text output and font support in OpenGL. */ #include #include #include #include #include "alleggl.h" #include "allglint.h" #ifdef ALLEGRO_MACOSX #include #else #include #endif #if defined ALLEGRO_WITH_XWINDOWS && !defined ALLEGROGL_GENERIC_DRIVER #include #include #endif #define PREFIX_E "agl-font ERROR: " static int aglf_font_generation_mode = AGL_FONT_POLYGONS; /* find_range: * Searches a font for a specific character. */ static AL_CONST FONT_AGL_DATA *find_range(AL_CONST FONT_AGL_DATA *f, int c) { while (f) { if ((c >= f->start) && (c < f->end)) return f; f = f->next; } return NULL; } /* allegro_gl_printf(FONT *f, float x, float y, float z, int color, char *format, ...) */ /** Equivalent to: *
 *   r = getr(color);
 *   g = getg(color);
 *   b = getb(color);
 *   a = geta(color);
 *   glColor4f(r, g, b, a);
 *   allegro_gl_printf_ex(f, x, y, z,
 *                        format, ...);
 *  
* * Note that the current primary color is not preserved. */ int allegro_gl_printf(AL_CONST FONT *f, float x, float y, float z, int color, AL_CONST char *format, ...) { #define BUF_SIZE 1024 char buf[BUF_SIZE]; va_list ap; if (!__allegro_gl_valid_context) return 0; /* Get the string */ va_start(ap, format); uvszprintf(buf, BUF_SIZE, format, ap); va_end(ap); #undef BUF_SIZE /* Set color */ { GLubyte c[4]; c[0] = (GLubyte)getr(color); c[1] = (GLubyte)getg(color); c[2] = (GLubyte)getb(color); c[3] = (__allegro_gl_use_alpha && bitmap_color_depth(screen) == 32) ? (GLubyte)geta(color) : 255; glColor4ubv(c); } return allegro_gl_printf_ex(f, x, y, z, buf); } /* allegro_gl_printf_ex(FONT *f, float x, float y, float z, * char *format, ...) */ /** Prints a formatted string (printf style) on the screen. * * \param f Which font to use. * \param x,y,z Coordinates to print at. They specify the top-left * corner of the text position. * \param format The format string (see printf() for details) * * For bitmap fonts, the raster position is set to (x,y), * 'z' is ignored. * The current modelview matrix applies to this code so you may want to * use glLoadIdentity() beforehand. On the other hand, you can use the * modelview matrix to apply transformations to the text. This will only work * with textured or vector (outline) fonts. * This function only accepts AllegroGL formated fonts, as converted by * allegro_gl_convert_allegro_font(), or loaded by * allegro_gl_load_system_font() or allegro_gl_load_system_font_ex(). * * Texturing must be enabled for this function to work with * #AGL_FONT_TYPE_TEXTURED fonts. * * Remember to use glEnable(GL_TEXTURE_2D) to enable texturing. * * The resulting size may not be what you expect. For bitmaped fonts, * there is nothing you can do about this appart changing the font itself. * Textured and Vector fonts are more flexible, in that you can use * glScale to adjust the size of the characters. * * If you need to draw the text without the black backround, we suggest * you set up a proper blending mode prior to drawing the text, such as: *
 *     glEnable(GL_BLEND);
 *     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
 *   
* * If you want to emulate the Allegro drawing mode (no blending at all), * then you should use the folowing code instead: * *
 *     glDisable(GL_DEPTH_TEST);
 *     glEnable(GL_BLEND);
 *
 *     glBlendFunc(GL_DST_COLOR, GL_ZERO);
 *     allegro_gl_printf();
 *
 *     glBlendFunc(GL_ONE, GL_ONE);
 *     allegro_gl_printf();    // Same as the one above!
 *
 *     glEnable(GL_DEPTH_TEST);
 *   
* * Have a look at NeHe's Tutorial #20 for details on this technique. * http://nehe.gamedev.net/ * * The most flexible way to use fonts, though, is to use alpha textures * based on a greyscale font. Set the texture format to * GL_ALPHA4 or GL_ALPHA8 before creating the * (textured) font. Then you can set the colour and blend modes like so: * *
 *    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 *    allegro_gl_printf_ex(my_font, x, y, z, "Hi!")
 *  
* * \return The number of characters printed. */ int allegro_gl_printf_ex(AL_CONST FONT *f, float x, float y, float z, AL_CONST char *format, ...) { #define BUF_SIZE 1024 char buf[BUF_SIZE]; va_list ap; AL_CONST FONT_AGL_DATA *range = NULL; int c, pos = 0; int count = 0; AL_CONST FONT_AGL_DATA *d; GLint vert_order, cull_mode; GLint matrix_mode; int restore_rasterpos = 0; GLuint old_texture_bind = 0; GLfloat old_raster_pos[4]; if (!__allegro_gl_valid_context) return 0; /* Check arguments */ if (!format || !f) { TRACE(PREFIX_E "agl_printf: Null parameter\n"); return 0; } if (f->vtable != font_vtable_agl) { TRACE(PREFIX_E "agl_printf: Font parameter isn't of the AGL " "type.\n"); return 0; } d = (AL_CONST FONT_AGL_DATA*)f->data; /* Get the string */ va_start(ap, format); uvszprintf(buf, BUF_SIZE, format, ap); va_end(ap); #undef BUF_SIZE glGetIntegerv(GL_MATRIX_MODE, &matrix_mode); glGetIntegerv(GL_FRONT_FACE, &vert_order); glGetIntegerv(GL_CULL_FACE_MODE, &cull_mode); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glFrontFace(GL_CW); glCullFace(GL_BACK); { GLint temp; glGetIntegerv(GL_TEXTURE_BINDING_2D, &temp); old_texture_bind = (GLuint)temp; } if (d->type == AGL_FONT_TYPE_BITMAP) { glTranslatef(0, 0, -1); glBindTexture(GL_TEXTURE_2D, 0); glGetFloatv(GL_CURRENT_RASTER_POSITION, old_raster_pos); glRasterPos2f(x, y); restore_rasterpos = 1; } else if (d->type == AGL_FONT_TYPE_OUTLINE) { glTranslatef(x, y, z); glBindTexture(GL_TEXTURE_2D, 0); } else if (d->type == AGL_FONT_TYPE_TEXTURED) { glTranslatef(x, y, z); } while ((c = ugetc(buf + pos)) != 0) { pos += ucwidth(c); if ((!range) || (c < range->start) || (c >= range->end)) { /* search for a suitable character range */ range = find_range(d, c); if (!range) { range = find_range(d, (c = '^')); if (!range) continue; } } /* Set up texture */ if (d->type == AGL_FONT_TYPE_TEXTURED) { glBindTexture(GL_TEXTURE_2D, range->texture); } /* draw the character */ c -= range->start; c += range->list_base; glCallList(c); count++; } glPopMatrix(); glMatrixMode(matrix_mode); glFrontFace(vert_order); glCullFace(cull_mode); glBindTexture(GL_TEXTURE_2D, old_texture_bind); if (restore_rasterpos) { glRasterPos4fv(old_raster_pos); } return count; } #ifndef ALLEGROGL_GENERIC_DRIVER #ifdef ALLEGRO_WINDOWS static FONT *win_load_system_font(char *name, int type, int style, int w, int h, float depth, int start, int end) { HFONT hFont; FONT_AGL_DATA *data; FONT *ret; ret = malloc(sizeof(FONT)); if (!ret) { TRACE(PREFIX_E "win_load_system_font: Ran out of memory " "while allocating %i bytes\n", sizeof(FONT)); return NULL; } data = malloc(sizeof(FONT_AGL_DATA)); if (!data) { free(ret); TRACE(PREFIX_E "win_load_system_font: Ran out of memory " "while allocating %i bytes\n", sizeof(FONT_AGL_DATA)); return NULL; } ret->vtable = font_vtable_agl; ret->data = data; data->list_base = glGenLists(end - start); data->start = start; data->end = end; data->next = NULL; data->is_free_chunk = 0; if (type == AGL_FONT_TYPE_BITMAP || type == AGL_FONT_TYPE_DONT_CARE) { HDC dc; hFont = CreateFont( -h, w, 0, 0, (style & AGL_FONT_STYLE_BOLD) ? FW_BOLD : ((style & AGL_FONT_STYLE_BLACK) ? FW_BLACK : FW_NORMAL), ((style & AGL_FONT_STYLE_ITALIC) ? TRUE : FALSE), ((style & AGL_FONT_STYLE_UNDERLINE) ? TRUE : FALSE), ((style & AGL_FONT_STYLE_STRIKEOUT) ? TRUE : FALSE), ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, (style & AGL_FONT_STYLE_ANTI_ALIASED) ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, name); dc = GetDC(win_get_window()); SelectObject(dc, hFont); wglUseFontBitmaps(dc, start, end - start, data->list_base); data->type = AGL_FONT_TYPE_BITMAP; data->data = NULL; } else if (type == AGL_FONT_TYPE_OUTLINE) { HDC dc; GLYPHMETRICSFLOAT *gmf; gmf = malloc(sizeof(GLYPHMETRICSFLOAT) * (end - start)); memset(gmf, 0, sizeof(GLYPHMETRICSFLOAT) * (end - start)); hFont = CreateFont( -h, w, 0, 0, (style & AGL_FONT_STYLE_BOLD) ? FW_BOLD : ((style & AGL_FONT_STYLE_BLACK) ? FW_BLACK : FW_NORMAL), ((style & AGL_FONT_STYLE_ITALIC) ? TRUE : FALSE), ((style & AGL_FONT_STYLE_UNDERLINE) ? TRUE : FALSE), ((style & AGL_FONT_STYLE_STRIKEOUT) ? TRUE : FALSE), ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, (style & AGL_FONT_STYLE_ANTI_ALIASED) ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, name); dc = GetDC(win_get_window()); SelectObject(dc, hFont); wglUseFontOutlines(dc, start, end - start, data->list_base, 0.0, depth, (aglf_font_generation_mode == AGL_FONT_POLYGONS) ? WGL_FONT_POLYGONS : WGL_FONT_LINES, gmf); data->type = AGL_FONT_TYPE_OUTLINE; data->data = gmf; } return ret; } #endif #ifdef ALLEGRO_WITH_XWINDOWS static FONT *x_load_system_font(char *name, int type, int style, int w, int h, float depth, int start, int end) { FONT_AGL_DATA *data; FONT *ret; XFontStruct *xfont; ret = malloc(sizeof(FONT)); if (!ret) { TRACE(PREFIX_E "x_load_system_font: Ran out of memory " "while allocating %zi bytes\n", sizeof(FONT)); return NULL; } data = malloc(sizeof(FONT_AGL_DATA)); if (!data) { free(ret); TRACE(PREFIX_E "x_load_system_font: Ran out of memory " "while allocating %zi bytes\n", sizeof(FONT_AGL_DATA)); return NULL; } ret->vtable = font_vtable_agl; ret->data = data; data->list_base = glGenLists(end - start); data->start = start; data->end = end; data->next = NULL; data->is_free_chunk = 0; if (type == AGL_FONT_TYPE_BITMAP || type == AGL_FONT_TYPE_DONT_CARE) { char buf[256], major_type[256], minor_type[2]; usprintf(major_type, "medium"); if (style & AGL_FONT_STYLE_BOLD) usprintf(major_type, "bold"); minor_type[0] = (style & AGL_FONT_STYLE_ITALIC) ? 'i' : 'r'; minor_type[1] = '\0'; usprintf(buf, "-*-%s-%s-%s-normal-*-%i-*-*-*-*-*-*-*", name, major_type, minor_type, h); /* Load the font */ xfont = XLoadQueryFont(_xwin.display, buf); if (!xfont) { free(ret); free(data); TRACE(PREFIX_E "x_load_system_font: Failed to load " "%s\n", buf); return NULL; } glXUseXFont(xfont->fid, start, end - start, data->list_base); data->type = AGL_FONT_TYPE_BITMAP; data->data = NULL; XFreeFont(_xwin.display, xfont); } else { /* Not Yet Implemented */ return NULL; } return ret; } #endif #endif /* ALLEGROGL_GENERIC_DRIVER */ /* void allegro_gl_set_font_generation_mode(int mode) */ /** Set the font generation mode for system fonts. * * \b Note: This function is deprecated and will be removed * in a future version. * * \param mode Can be either #AGL_FONT_POLYGONS or #AGL_FONT_LINES. * for creating polygonal or line characters. * Default is #AGL_FONT_POLYGONS. * * Subsequent calls to allegro_gl_load_system_font() and * allegro_gl_load_system_font_ex() may be affected by this function. * * \deprecated */ void allegro_gl_set_font_generation_mode(int mode) { aglf_font_generation_mode = mode; return; } /* FONT *allegro_gl_load_system_font(char *name, int style, int w, int h) */ /** Short hand for aglf_load_system_font_ex(name, #AGL_FONT_TYPE_OUTLINE, * style, w, h, 0.0f, 32, 256) * * \b Note: This function is deprecated and will be removed * in a future version. * * \deprecated */ FONT *allegro_gl_load_system_font(char *name, int style, int w, int h) { return allegro_gl_load_system_font_ex(name, AGL_FONT_TYPE_OUTLINE, style, w, h, 0.0f, 32, 256); } /* FONT *allegro_gl_load_system_font_ex(char *name, int type, int style, int w, int h, float depth, int start, int end) */ /** Loads a system font. * * \b Note: This function is deprecated and will be removed * in a future version. * * \param name The name of the system font ("Courrier" or "Arial" * for example) * \param type The font type to generate (#AGL_FONT_TYPE_DONT_CARE, * #AGL_FONT_TYPE_BITMAP, #AGL_FONT_TYPE_OUTLINE) * \param style The text decorations of the font to create * (#AGL_FONT_STYLE_ITALIC, etc - can be or'ed together) * \param w,h The size of the font characters that will be created. * \param depth The z-depth of the font. 0.0f is flat, 1.0f is very * thick * \param start,end The range of characters to create from 'start' * (included) to 'end' (excluded) in UTF-8 format * (ANSI only on Windows). * * \note * - In #AGL_FONT_TYPE_OUTLINE type, some system fonts seem to be unresponsive * to the size paramaters (w and h). You cannot depend on either a system * font being present, or the font being what you expect - it is possible to * have two fonts with the same name but with different graphic data. * - BITMAP fonts have no depth. * - The width and height parameters are also system dependent, and may or * may not work with the selected font. * * \return The loaded font, or NULL on error. * * \deprecated */ FONT *allegro_gl_load_system_font_ex(char *name, int type, int style, int w, int h, float depth, int start, int end) { FONT *ret = NULL; if (!__allegro_gl_valid_context) return NULL; if (!name) { TRACE(PREFIX_E "load_system_font: Nameless font\n"); return NULL; } /* Load a system font */ #ifndef ALLEGROGL_GENERIC_DRIVER #ifdef ALLEGRO_WINDOWS ret = win_load_system_font(name, type, style, w, h, depth, start, end); #elif defined ALLEGRO_UNIX XLOCK(); ret = x_load_system_font(name, type, style, w, h, depth, start, end); XUNLOCK(); #else /* Other platform */ #endif #endif return ret; } /** void allegro_gl_destroy_font(FONT *usefont) */ /** Destroys the font. * * \param f The AGL font to be destroyed. * * The allocated memory is freed, as well as any display list, or texture * objects it uses. * * You cannot use that font anymore after calling this function. * It's safe to call this function with a NULL pointer; a note will be * placed in allegro.log if DEBUGMODE was defined. * * If NULL is passed as the font to destroy, then this function returns * immediately. */ void allegro_gl_destroy_font(FONT *f) { FONT_AGL_DATA *data; if (!f) { return; } if (f->vtable != font_vtable_agl) { TRACE(PREFIX_E "destroy_font: Font is not of AGL type\n"); return; } data = f->data; if (!data) { TRACE(PREFIX_E "destroy_font: Font is inconsistent\n"); return; } /* Iterate through every segment of the font */ while (data) { FONT_AGL_DATA *datanext; /* Release all resources taken up by this font */ if (data->type == AGL_FONT_TYPE_BITMAP || data->type == AGL_FONT_TYPE_OUTLINE || data->type == AGL_FONT_TYPE_TEXTURED) { if (__allegro_gl_valid_context) { if (data->list_base) glDeleteLists(data->list_base, data->end - data->start); if (data->texture) glDeleteTextures(1, &data->texture); } } if (data->type == AGL_FONT_TYPE_OUTLINE) { if (data->data) free(data->data); } else if (data->type == AGL_FONT_TYPE_TEXTURED) { if (data->data) destroy_bitmap(data->data); if (data->glyph_coords) free(data->glyph_coords); } else if (data->type == AGL_FONT_TYPE_BITMAP) { if (data->data) { int i; FONT_GLYPH **gl = data->data; for (i = 0; i < data->end - data->start; i++) { if (gl[i]) free(gl[i]); } free(gl); } } datanext = data->next; if (data->is_free_chunk) free(data); data = datanext; } free(f->data); if (f != font) free(f); return; } /* size_t allegro_gl_list_font_textures(FONT *f, GLuint *ids, size_t max_num_id) */ /** List the texture ID of all textures forming the specified font. * * The font specified must be an AllegroGL font. * * If ids is not NULL, then the ID numbers of all textures used by the font * are written to the GLuint array pointed by ids. The size of that array * is specified by the max_num_id parameter. This function will never write * more than 'max_num_id' values in the ids array. * * If f is NULL, then zero is returned and the ids array is never touched. * * If the font does not contain any textures (because it is a bitmap or outline * font, for example), then zero is returned. * * \return Number of texture IDs that make up this font. * * Here are two examples of the use of this function: * *
 *   int num_ids = allegro_gl_list_font_textures(font, NULL, 0);
 *
 *   GLuint *id = malloc(sizeof(GLuint) * num_ids);
 *
 *   if (!id) {
 *     //handle error
 *   }
 *
 *   allegro_gl_list_font_textures(font, id, num_ids);
 *
 *   for (i = 0; i < num_ids; i++) {
 *       glBindTexture(GL_TEXTURE_2D, id[i]);
 *       // Use this texture
 *   }
 *
 *   free(id);
 * 
* *
 *   GLint id[10];  // Reserve a safe number
 *   GLint num_ids = allegro_gl_list_font_textures(font, id, 10);
 * 
*/ size_t allegro_gl_list_font_textures(FONT *f, GLuint *ids, size_t max_num_id) { size_t num_ids = 0; FONT_AGL_DATA *data; if (!f) { return 0; } if (f->vtable != font_vtable_agl) { TRACE(PREFIX_E "list_font_textures: Font is not of AGL type\n"); return 0; } data = f->data; if (!data) { TRACE(PREFIX_E "list_font_textures: Font is inconsistent\n"); return 0; } if (!__allegro_gl_valid_context) { return 0; } /* Iterate through all font segments */ while (data) { if (data->texture) { /* Add the texture ID in the array, if it's not NULL and if there * is room. */ if (ids && num_ids < max_num_id) { ids[num_ids] = data->texture; } num_ids++; } data = data->next; } return num_ids; }