415 lines
11 KiB
C
415 lines
11 KiB
C
#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"
|
|
|
|
// == Font render routines.================================
|
|
// Use a display list to store ASCII chars rendered with
|
|
// freefont. There are several drawing methods depending
|
|
// on whether you want to print it all, to a max width,
|
|
// print centered or print a block of text.
|
|
//
|
|
// There are hardcoded size limits. 256 characters for all
|
|
// routines exept gl_printText which has a 1024 limit.
|
|
//
|
|
// TODO: Check if length is too long.
|
|
// ========================================================
|
|
|
|
// Default font.
|
|
glFont gl_defFont;
|
|
glFont gl_smallFont;
|
|
|
|
static void glFontMakeDList(FT_Face face, char ch,
|
|
GLuint list_base, GLuint* tex_base,
|
|
int* width_base);
|
|
|
|
static int pot(int n);
|
|
|
|
// Get the closest power of two.
|
|
static int pot(int n) {
|
|
int i = 1;
|
|
while(i < n)
|
|
i<<=1;
|
|
return i;
|
|
}
|
|
|
|
// Print text on screen! YES!!!! Just like printf! But different!
|
|
// Defaults ft_font to gl_defFont if NULL.
|
|
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)gl_screen.w/2., y - (double)gl_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);
|
|
}
|
|
|
|
// Acts just like gl_print, but prints to a max length of max.
|
|
// Return the amount of characters we 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)gl_screen.w/2., y - (double)gl_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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Acts just like gl_printMax, but centers the text in the width.
|
|
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)gl_screen.w/2., y - (double)gl_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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Print text with line breaks included to a max width and height precet.
|
|
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[1024];
|
|
char buf[128];
|
|
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)gl_screen.w/2.;
|
|
by -= (double)gl_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++) {
|
|
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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Get the width of the text about to be printed.
|
|
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;
|
|
}
|
|
|
|
// ================
|
|
// FONT!
|
|
// ================
|
|
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 = pot(bitmap.width);
|
|
h = 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);
|
|
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);
|
|
}
|
|
|
|
void gl_fontInit(glFont* font, const char* fname, const unsigned int h) {
|
|
if(font == NULL) font = &gl_defFont;
|
|
|
|
uint32_t bufsize;
|
|
FT_Byte* buf = pack_readfile(DATA, (fname) ? 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.
|
|
unsigned char i;
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|