/** * @file font.c * * @brief OpenGL font rendering routines. * * Use a displaylist to store ASCII chars rendered with freefont. * There are several drawing methods depending on whether you want * to print it all, print to a max width, print centered or print a * block of text. * * There are hardcoded size limits. 256 characters for all routines * except gl_printText which has a 1024 limit. * * @todo Check if length is too long. */ #include "font.h" #include "ft2build.h" #include "freetype/freetype.h" #include "freetype/ftglyph.h" #include "lephisto.h" #include "log.h" #include "pack.h" #define FONT_DEF "../dat/font.ttf" /**< Default font path. */ /* Default font. */ glFont gl_defFont; /**< Default font. */ glFont gl_smallFont; /**< Small font. */ static void glFontMakeDList(FT_Face face, char ch, GLuint list_base, GLuint* tex_base, int* width_base); /** * @fn void gl_print(const glFont* ft_font, const double x, const double y, * const glColour* c, const char* fmt, ...) * * @brief Print text on screen like printf. * * Defaults ft_font to gl_defFont if NULL. * @param ft_font Font to use (NULL means gl_defFont). * @param x X position to put text at. * @param y Y position to put text at. * @param c Colour to use (uses white if NULL). * @param fmt String formatted like printf to print. * */ void gl_print(const glFont* ft_font, const double x, const double y, const glColour* c, const char* fmt, ...) { /*float h = ft_font->h / .63; // Slightly increases font size. */ char txt[256]; va_list ap; if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return; else { /* convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } glEnable(GL_TEXTURE_2D); glListBase(ft_font->list_base); glMatrixMode(GL_MODELVIEW); glPushMatrix(); /* Translation matrix. */ glTranslated(x - (double)SCREEN_W/2., y - (double)SCREEN_H/2., 0); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glCallLists(strlen(txt), GL_UNSIGNED_BYTE, &txt); glPopMatrix(); /* Translation matrix. */ glDisable(GL_TEXTURE_2D); gl_checkErr(); } /** * @fn int gl_printMax(const glFont* ft_font, const int max, * const double x, const double y, const glColour* c, const char* fmt, ...) * * @brief Act like gl_print but stops displaying text after reaching a certain length. * @param ft_font Font to use (Null means use gl_defFont). * @param max Maximum length to reach. * @param x X position to display text at. * @param y Y position to display text at. * @param c Colour to use (NULL defaults to white). * @param fmt String to display formatted like printf. * @return The number of characters it had to suppress. */ int gl_printMax(const glFont* ft_font, const int max, const double x, const double y, const glColour* c, const char* fmt, ...) { /*float h = ft_font->h / .63; // Slightly increases font size. */ char txt[256]; va_list ap; int i, n, len, ret; if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return -1; else { /* convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } /* Limit the size. */ len = (int)strlen(txt); for(n = 0, i = 0; i < len; i++) { n += ft_font->w[(int)txt[i]]; if(n > max) { ret = len - i; /* Difference. */ txt[i] = '\0'; break; } } /* Display the text. */ glEnable(GL_TEXTURE_2D); glListBase(ft_font->list_base); glMatrixMode(GL_MODELVIEW); /* Projection gets full fast using modelview. */ glPushMatrix(); /* Translation matrix. */ glTranslated(x - (double)SCREEN_W/2., y - (double)SCREEN_H/2., 0); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glCallLists(i, GL_UNSIGNED_BYTE, &txt); glPopMatrix(); /* Translation matrix. */ glDisable(GL_TEXTURE_2D); gl_checkErr(); return ret; } /** * @fn int gl_printMid(const glFont* ft_font, const int width, double x, const double y, * const glColour* c, const char* fmt, ...) * * @brief Display text centered in position and width. * * Will truncate if text is too long. * @param ft_font Font to use (NULL defaults to gl_defFont). * @param width Width of area to center in. * @param x X Position to display text at. * @param y Y Position to display text at. * @param c Colour to use for text (NULL defaults to white). * @param fmt Text to display formatted string like printf. * @return The number of characters it had to truncate. */ int gl_printMid(const glFont* ft_font, const int width, double x, const double y, const glColour* c, const char* fmt, ...) { /*float h = ft_font->h / .63; // Slightly increases font size. */ char txt[256]; va_list ap; int i, n, len, ret; ret = 0; /* Default return value. */ if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return -1; else { /* convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } /* Limit the size. */ len = (int)strlen(txt); for(n = 0, i = 0; i < len; i++) { n += ft_font->w[(int)txt[i]]; if(n > width) { ret = len - i; /* Difference. */ n -= ft_font->w[(int)txt[i]]; /* Actual size. */ txt[i] = '\0'; break; } } x += (double)(width-n)/2.; /* Display the text. */ glEnable(GL_TEXTURE_2D); glListBase(ft_font->list_base); glMatrixMode(GL_MODELVIEW); /* Projection gets full fast using modelview. */ glPushMatrix(); /* Translation matrix. */ glTranslated(x - (double)SCREEN_W/2., y - (double)SCREEN_H/2., 0); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glCallLists(i, GL_UNSIGNED_BYTE, &txt); glPopMatrix(); /* Translation matrix. */ glDisable(GL_TEXTURE_2D); gl_checkErr(); return ret; } /** * @fn int gl_printText(const glFont* ft_font, const int width, const int height, * double bx, double by, glColour* c, const char* fmt, ...) * * @brief Print a block of text that fits in the dimensions given. * * Positions are based on origin being top-left. * @param ft_font Font to use (NULL defaults to gl_defFont). * @param width Maximum width to print to. * @param height Maximum height to print to. * @param bx X position to display text at. * @param by Y position to display text at. * @param c Colour to use (NULL defaults to white). * @param fmt Text to display formatted like printf. * @return 0 on success. */ int gl_printText(const glFont* ft_font, const int width, const int height, double bx, double by, glColour* c, const char* fmt, ...) { /*float h = ft_font->h / .63; // Slightly increase font size. */ char txt[4096]; char buf[256]; va_list ap; int p, i, j, n, m, len, ret, lastspace; double x, y; ret = 0; /* Default return value. */ if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return -1; else { /* Convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } bx -= (double)SCREEN_W/2.; by -= (double)SCREEN_H/2.; x = bx; y = by + height - (double)ft_font->h; /* y is top left corner. */ /* Prepare opengl. */ glEnable(GL_TEXTURE_2D); glListBase(ft_font->list_base); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); len = (int)strlen(txt); /* Limit size per line. */ lastspace = -1; /* Last ' ' or \n int text. */ n = 0; /* Current width. */ i = 0; /* Current position. */ p = -1; /* Where we last drew up to. */ while(i < len+1) { if(by - y > (double)height) return len-lastspace; /* Past height. */ n += ft_font->w[(int)txt[i]]; if((txt[i] == ' ') || (txt[i] == '\n') || (txt[i] == '\0')) lastspace = i; if(((n > width) && ((p != lastspace))) || (txt[i] == '\n') || (txt[i] == '\0')) { /* Time to draw the line. */ m = 0; if(lastspace == -1) lastspace = 0; for(j = 0; j < (lastspace-p-1); j++) { if(txt[p+j+1]=='\t') { p++; continue; } m += ft_font->w[(int)txt[p+j+1]]; if(m > width) break; buf[j] = txt[p+j+1]; } /* No need for null termination. */ glMatrixMode(GL_MODELVIEW); /* using modelview, projection gets full fast. */ glPushMatrix(); /* Translation matrix. */ glTranslated(x, y, 0); /* This is what we are displaying. */ glCallLists(j, GL_UNSIGNED_BYTE, &buf); glPopMatrix(); /* Translation matrix. */ p = lastspace; n = 0; i = lastspace; y -= 1.5*(double)ft_font->h; /* Move position down. */ } i++; } glDisable(GL_TEXTURE_2D); gl_checkErr(); return ret; } /** * @fn int gl_printWidth(const glFont* ft_font, const char* fmt, ...) * * @brief Get the width that it would take to print some text. * * Does not display text on screen. * @param ft_font Font to use (NULL defaults to gl_defFont). * @param fmt Text to calculate the length of. * @return The length of the text in pixels. */ int gl_printWidth(const glFont* ft_font, const char* fmt, ...) { int i, n; char txt[256]; /* Holds the string. */ va_list ap; if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return 0; else { /* Convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } for(n = 0, i = 0; i < (int)strlen(txt); i++) n += ft_font->w[(int)txt[i]]; return n; } /** * @fn int gl_printHeight(const glFont* ft_font, const int width, * const char* fmt, ...) * * @brief Get the height of the text if it were printed. * * Does not display the text on screen. * @param ft_font Font to use (NULL defaults to gl_defFont). * @param width Width to jump to next line once reached. * @param fmt Text to get the height of in printf format. * @return The heiht of the text. */ int gl_printHeight(const glFont* ft_font, const int width, const char* fmt, ...) { char txt[1024]; /* Holds the string. */ va_list ap; int p, i, n, len, lastspace; double y; if(ft_font == NULL) ft_font = &gl_defFont; if(fmt == NULL) return -1; else { /* Convert the symbols to text. */ va_start(ap, fmt); vsprintf(txt, fmt, ap); va_end(ap); } y = 0.; len = (int) strlen(txt); /* Limit the size per line. */ lastspace = -1; /* Last ' ' or '\n' in the text. */ n = 0; /* Current width. */ i = 0; /* Current position. */ p = -1; /* Where we last drew up to. */ while(i < len+1) { n += ft_font->w[(int)txt[i]]; if((txt[i] == ' ') || (txt[i] == '\n') || (txt[i] == '\0')) lastspace = i; if(((n > width) && (p != lastspace)) || (txt[i] == '\n') || (txt[i] == '\0')) { p = lastspace; n = 0; i = lastspace; y += 1.5*(double)ft_font->h; /* Move position down. */ } i++; } return (int)(y - 0.5*(double)ft_font->h); } /* ================ * FONT! * ================ */ /** * @fn static void glFontMakeDList(FT_Face face, char ch, GLuint list_base, * GLuint* tex_base, int* width_base) * * @brief Makes the font display list. */ static void glFontMakeDList(FT_Face face, char ch, GLuint list_base, GLuint* tex_base, int* width_base) { FT_Glyph glyph; FT_Bitmap bitmap; GLubyte* expanded_data; int w, h; int i, j; double x, y; if(FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_FORCE_AUTOHINT)) WARN("FT_Load_Glyph failed"); if(FT_Get_Glyph(face->glyph, &glyph)) WARN("FT_Ge_Glyph failed"); /* Convert your glyph to a bitmap. */ FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph; bitmap = bitmap_glyph->bitmap; /* To simplify. */ /* Need the POT wrapping for GL. */ w = gl_pot(bitmap.width); h = gl_pot(bitmap.rows); /* Memory for textured data. */ /* Bitmap is useing two channels, one for luminosity and one for alpha. */ expanded_data = (GLubyte*)malloc(sizeof(GLubyte)*2*w*h + 1); for(j = 0; j < h; j++) { for(i = 0; i < w; i++) { expanded_data[2*(i+j*w)] = expanded_data[2*(i+j*w)+1] = (i >= bitmap.width || j >= bitmap.rows) ? 0 : bitmap.buffer[i + bitmap.width*j]; } } /* Create the GL texture. */ glBindTexture(GL_TEXTURE_2D, tex_base[(int)ch]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data); free(expanded_data); /* No need for this now. */ /* Create the display lists. */ glNewList(list_base+ch, GL_COMPILE); /* Corrects a spacing flaw between letters and */ /* downwards correction for letters like g or y. */ glPushMatrix(); glTranslated(bitmap_glyph->left, bitmap_glyph->top-bitmap.rows, 0); /* Take the opengl POT wrapping into account. */ x = (double)bitmap.width/(double)w; y = (double)bitmap.rows/(double)h; /* Draw the texture mapped quad. */ glBindTexture(GL_TEXTURE_2D, tex_base[(int)ch]); glBegin(GL_QUADS); glTexCoord2d(0, 0); glVertex2d(0, bitmap.rows); glTexCoord2d(x, 0); glVertex2d(bitmap.width, bitmap.rows); glTexCoord2d(x, y); glVertex2d(bitmap.width, 0); glTexCoord2d(0, y); glVertex2d(0, 0); glEnd(); glPopMatrix(); glTranslated(face->glyph->advance.x >> 6, 0,0); width_base[(int)ch] = (int)(face->glyph->advance.x >> 6); /* End of the display list. */ glEndList(); FT_Done_Glyph(glyph); gl_checkErr(); } /** * @fn void gl_fontInit(glFont* font, const char* fname, const unsigned int h) * * @brief Initializes a font. * @param font Font to load (NULL defaults to gl_defFont). * @param fname Name of the font (from inside packfile, NULL defaults to default font). * @param h Height of the font to generate. */ void gl_fontInit(glFont* font, const char* fname, const unsigned int h) { uint32_t bufsize; int i; if(font == NULL) font = &gl_defFont; FT_Byte* buf = pack_readfile(DATA, (fname!=NULL) ? fname : FONT_DEF, &bufsize); /* Allocatagery. */ font->textures = malloc(sizeof(GLuint)*128); font->w = malloc(sizeof(int)*128); font->h = (int)h; if(font->textures == NULL || font->w == NULL) { WARN("Out of memory!"); return; } /* Create a FreeType font library. */ FT_Library library; if(FT_Init_FreeType(&library)) { WARN("FT_Init_FreeType failed"); } /* Objects that freetype uses to store font info. */ FT_Face face; if(FT_New_Memory_Face(library, buf, bufsize, 0, &face)) WARN("FT_New_Memory_Face failed loading library from %s", fname); /* FreeType is pretty nice and measures using 1/64 of a pixel, therfore expand. */ FT_Set_Char_Size(face, h << 6, h << 6, 96, 96); /* Have OpenGL allocate space for the textures / display lists. */ font->list_base = glGenLists(128); glGenTextures(128, font->textures); /* Create each of the font display lists. */ for(i = 0; i < 128; i++) glFontMakeDList(face, i, font->list_base, font->textures, font->w); /* We can now free the face and library. */ FT_Done_Face(face); FT_Done_FreeType(library); free(buf); } /** * @fn void gl_freeFont(glFont* font) * * @brief Free a loaded font. * @param font Font to free. */ void gl_freeFont(glFont* font) { if(font == NULL) font = &gl_defFont; glDeleteLists(font->list_base, 128); glDeleteTextures(128, font->textures); free(font->textures); free(font->w); }