586 lines
16 KiB
C
586 lines
16 KiB
C
/**
|
|
* @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 FT_FREETYPE_H
|
|
#include FT_GLYPH_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);
|
|
static int font_limitSize(const glFont* ft_font, int* width,
|
|
char* txt, const int max);
|
|
|
|
/**
|
|
* @brief Limits the text to max.
|
|
* @param ft_font Font to calculate width with.
|
|
* @param width Actual width it takes up.
|
|
* @param text Text to parse.
|
|
* @param max Max to look for.
|
|
*/
|
|
static int font_limitSize(const glFont* ft_font, int* width,
|
|
char* text, const int max) {
|
|
int n, len, i;
|
|
|
|
/* Limit size. */
|
|
len = (int)strlen(text);
|
|
n = 0;
|
|
for(i = 0; i < len; i++) {
|
|
n += ft_font->w[(int)text[i]];
|
|
if(n > max) {
|
|
n -= ft_font->w[(int)text[i]]; /* Actual size. */
|
|
text[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if(width != NULL)
|
|
(*width) = n;
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the number of characters in text that fit into width.
|
|
* @param ft_font Font to use.
|
|
* @param text Text to check.
|
|
* @param width Width to match.
|
|
* @return Number of characters that fit.
|
|
*/
|
|
int gl_printWidthForText(const glFont* ft_font, char* txt,
|
|
const int width) {
|
|
|
|
int i, n, lastspace;
|
|
|
|
if(ft_font == NULL)
|
|
ft_font = &gl_defFont;
|
|
|
|
/* Limit size per line. */
|
|
lastspace = 0; /* last ' ' or '\n' in the text. */
|
|
n = 0; /* Current width. */
|
|
i = 0; /* Current position. */
|
|
while((txt[i] != '\n') && (txt[i] != '\0')) {
|
|
/* Characters we should ignore. */
|
|
if(txt[i] == '\t') {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
/* Increase size. */
|
|
n += ft_font->w[(int)txt[i]];
|
|
|
|
/* Save last space. */
|
|
if(txt[i] == ' ')
|
|
lastspace = i;
|
|
|
|
/* Check if out of bounds. */
|
|
if(n > width)
|
|
return lastspace;
|
|
|
|
/* Check next character. */
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* @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 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. */
|
|
ret = font_limitSize(ft_font, NULL, txt, max);
|
|
|
|
/* 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(ret, GL_UNSIGNED_BYTE, &txt);
|
|
|
|
glPopMatrix(); /* Translation matrix. */
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
gl_checkErr();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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 n, 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. */
|
|
ret = font_limitSize(ft_font, &n, txt, width);
|
|
|
|
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(ret, GL_UNSIGNED_BYTE, &txt);
|
|
|
|
glPopMatrix(); /* Translation matrix. */
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
gl_checkErr();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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];
|
|
va_list ap;
|
|
int i, p;
|
|
double x, 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);
|
|
}
|
|
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);
|
|
|
|
p = 0; /* Where we last drew up to. */
|
|
while(y - by > -1e-5) {
|
|
i = gl_printWidthForText(ft_font, &txt[p], width);
|
|
|
|
glMatrixMode(GL_MODELVIEW); /* Using MODELVIEW, PROJECTION gets full fast. */
|
|
glPushMatrix(); /* Translation matrix. */
|
|
glTranslated(x, y, 0);
|
|
|
|
glCallLists(i, GL_UNSIGNED_BYTE, &txt[p]); /* The actual displaying. */
|
|
|
|
glPopMatrix(); /* Translation matrix. */
|
|
|
|
if(txt[p+i] == '\0')
|
|
break;
|
|
p += i + 1;
|
|
y -= 1.5*(double)ft_font->h; /* Move position down. */
|
|
}
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
gl_checkErr();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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 i, p;
|
|
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);
|
|
}
|
|
|
|
if(txt[0] == '\0')
|
|
return 0;
|
|
|
|
y = 0.;
|
|
|
|
p = 0;
|
|
do {
|
|
i = gl_printWidthForText(ft_font, &txt[p], width);
|
|
|
|
if(txt[p+i] == '\0')
|
|
break;
|
|
p += i + 1;
|
|
y += 1.5*(double)ft_font->h; /* Move position down. */
|
|
} while(txt[p-1] != '\0');
|
|
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_Bitmap bitmap;
|
|
GLubyte* expanded_data;
|
|
FT_GlyphSlot slot;
|
|
int w, h;
|
|
int i, j;
|
|
double x, y;
|
|
|
|
slot = face->glyph; /* Small shortcut. */
|
|
|
|
/* Load the glyph. */
|
|
if(FT_Load_Char(face, ch, FT_LOAD_RENDER))
|
|
WARN("FT_Load_Char failed.");
|
|
|
|
bitmap = slot->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)] = 0xcf; /* Set LUMINANCE to constant. */
|
|
expanded_data[2*(i+j*w)+1] = /* Alpha varies with bitmap. */
|
|
((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_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
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((double)slot->bitmap_left, (double)(slot->bitmap_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., (double)bitmap.rows);
|
|
glTexCoord2d(x, 0.);
|
|
glVertex2d((double)bitmap.width, (double)bitmap.rows);
|
|
glTexCoord2d(x, y);
|
|
glVertex2d((double)bitmap.width, 0.);
|
|
glTexCoord2d(0., y);
|
|
glVertex2d(0., 0.);
|
|
glEnd();
|
|
|
|
glPopMatrix();
|
|
glTranslated((double)(slot->advance.x >> 6), (double)(slot->advance.y >> 6), 0);
|
|
width_base[(int)ch] = slot->advance.x >> 6;
|
|
|
|
/* End of the display list. */
|
|
glEndList();
|
|
|
|
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) {
|
|
FT_Library library;
|
|
FT_Face face;
|
|
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. */
|
|
if(FT_Init_FreeType(&library)) {
|
|
WARN("FT_Init_FreeType failed");
|
|
}
|
|
|
|
/* Objects that freetype uses to store font info. */
|
|
if(FT_New_Memory_Face(library, buf, bufsize, 0, &face))
|
|
WARN("FT_New_Memory_Face failed loading library from %s", fname);
|
|
|
|
/* Try to resize. */
|
|
if(FT_IS_SCALABLE(face)) {
|
|
if(FT_Set_Char_Size(face,
|
|
0, /* Same as width. */
|
|
h << 6, /* In 1/64th of a pixel. */
|
|
96, /* Create at 96 DPI. */
|
|
96)) /* Create at 96 DPI. */
|
|
WARN("FT_Set_Char_Size failed.");
|
|
} else
|
|
WARN("Font isn't resizable!");
|
|
|
|
/* Selected the character map. */
|
|
if(FT_Select_Charmap(face, FT_ENCODING_UNICODE))
|
|
WARN("FT_Select_Charmap failed to change character mapping.");
|
|
|
|
/* 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);
|
|
}
|
|
|