#include #include #include #ifdef CONF_PLATFORM_MACOSX #include #include #else #include #include #endif static int word_length(const char *text) { int s = 1; while(1) { if(*text == 0) return s-1; if(*text == '\n' || *text == '\t' || *text == ' ') return s; text++; s++; } } static float text_r=1; static float text_g=1; static float text_b=1; static float text_a=1; static struct FONT *default_font = 0; void gfx_text_set_default_font(struct FONT *font) { default_font = font; } void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags) { mem_zero(cursor, sizeof(*cursor)); cursor->font_size = font_size; cursor->start_x = x; cursor->start_y = y; cursor->x = x; cursor->y = y; cursor->line_count = 1; cursor->line_width = -1; cursor->flags = flags; cursor->charcount = 0; } void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width) { TEXT_CURSOR cursor; gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER); cursor.line_width = max_width; gfx_text_ex(&cursor, text, -1); } float gfx_text_width(void *font_set_v, float size, const char *text, int length) { TEXT_CURSOR cursor; gfx_text_set_cursor(&cursor, 0, 0, size, 0); gfx_text_ex(&cursor, text, length); return cursor.x; } void gfx_text_color(float r, float g, float b, float a) { text_r = r; text_g = g; text_b = b; text_a = a; } /* ft2 texture */ #include #include FT_FREETYPE_H static FT_Library ft_library; #define MAX_CHARACTERS 64 /* GL_LUMINANCE can be good for debugging*/ static int font_texture_format = GL_ALPHA; static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36}; #define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int)) typedef struct FONTCHAR { int id; /* these values are scaled to the font size */ /* width * font_size == real_size */ float width; float height; float offset_x; float offset_y; float advance_x; float uvs[4]; int64 touch_time; } FONTCHAR; typedef struct FONTSIZEDATA { int font_size; FT_Face *face; unsigned textures[2]; int texture_width; int texture_height; int num_x_chars; int num_y_chars; FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS]; int current_character; } FONTSIZEDATA; typedef struct FONT { char filename[128]; FT_Face ft_face; FONTSIZEDATA sizes[NUM_FONT_SIZES]; } FONT; static int font_get_index(int pixelsize) { int i; for(i = 0; i < NUM_FONT_SIZES; i++) { if(font_sizes[i] >= pixelsize) return i; } return NUM_FONT_SIZES-1; } FONT *gfx_font_load(const char *filename) { int i; FONT *font = mem_alloc(sizeof(FONT), 1); mem_zero(font, sizeof(*font)); str_copy(font->filename, filename, sizeof(font->filename)); if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face)) { mem_free(font); return NULL; } for(i = 0; i < NUM_FONT_SIZES; i++) font->sizes[i].font_size = -1; return font; }; void gfx_font_destroy(FONT *font) { mem_free(font); } void gfx_font_init() { FT_Init_FreeType(&ft_library); } static void grow(unsigned char *in, unsigned char *out, int w, int h) { int y, x; for(y = 0; y < h; y++) for(x = 0; x < w; x++) { int c = in[y*w+x]; int s_y, s_x; for(s_y = -1; s_y <= 1; s_y++) for(s_x = -1; s_x <= 1; s_x++) { int get_x = x+s_x; int get_y = y+s_y; if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h) { int index = get_y*w+get_x; if(in[index] > c) c = in[index]; } } out[y*w+x] = c; } } static void font_init_texture(FONTSIZEDATA *font, int width, int height) { int i; void *mem = mem_alloc(width*height, 1); mem_zero(mem, width*height); font->texture_width = width; font->texture_height = height; font->num_x_chars = 32; font->num_y_chars = 32; font->current_character = 0; glGenTextures(2, font->textures); for(i = 0; i < 2; i++) { glBindTexture(GL_TEXTURE_2D, font->textures[i]); 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, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem); } mem_free(mem); } static void font_init_index(FONT *font, int index) { font->sizes[index].font_size = font_sizes[index]; //FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face); font_init_texture(&font->sizes[index], 1024*2, 1024*2); } static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize) { int index = font_get_index(pixelsize); if(font->sizes[index].font_size != font_sizes[index]) font_init_index(font, index); return &font->sizes[index]; } static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data) { int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars); int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars); glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, sizedata->texture_width/sizedata->num_x_chars, sizedata->texture_height/sizedata->num_y_chars, font_texture_format, GL_UNSIGNED_BYTE, data); //dbg_msg("font", "uploaded %d at %d (%d %d)", chr, slot_id, x, y); } /* 8k of data used for rendering glyphs */ static unsigned char glyphdata[(4096/64) * (4096/64)]; static unsigned char glyphdata_outlined[(4096/64) * (4096/64)]; static int font_get_slot(FONTSIZEDATA *sizedata) { int char_count = sizedata->num_x_chars*sizedata->num_y_chars; if(sizedata->current_character < char_count) { int i = sizedata->current_character; sizedata->current_character++; return i; } /* kick out the oldest */ /* TODO: remove this linear search */ { int oldest = 0; int i; for(i = 1; i < char_count; i++) { if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time) oldest = i; } return oldest; } } static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr) { FT_Bitmap *bitmap; int slot_id = sizedata->current_character; int slot_w = sizedata->texture_width / sizedata->num_x_chars; int slot_h = sizedata->texture_height / sizedata->num_y_chars; int slot_size = slot_w*slot_h; int outline_thickness = 1; int x = 1; int y = 1; int px, py; FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size); if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER)) { dbg_msg("font", "error loading glyph %d", chr); return -1; } bitmap = &font->ft_face->glyph->bitmap; /* fetch slot */ slot_id = font_get_slot(sizedata); if(slot_id < 0) return -1; /* adjust spacing */ if(sizedata->font_size >= 18) outline_thickness = 2; x += outline_thickness; y += outline_thickness; /* prepare glyph data */ mem_zero(glyphdata, slot_size); if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { for(py = 0; py < bitmap->rows; py++) for(px = 0; px < bitmap->width; px++) glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px]; } else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { for(py = 0; py < bitmap->rows; py++) for(px = 0; px < bitmap->width; px++) { if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8)))) glyphdata[(py+y)*slot_w+px+x] = 255; } } if(0) for(py = 0; py < slot_w; py++) for(px = 0; px < slot_h; px++) glyphdata[py*slot_w+px] = 255; /* upload the glyph */ font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata); if(outline_thickness == 1) { grow(glyphdata, glyphdata_outlined, slot_w, slot_h); font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined); } else { grow(glyphdata, glyphdata_outlined, slot_w, slot_h); grow(glyphdata_outlined, glyphdata, slot_w, slot_h); font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata); } /* set char info */ { FONTCHAR *fontchr = &sizedata->characters[slot_id]; float scale = 1.0f/sizedata->font_size; float uscale = 1.0f/sizedata->texture_width; float vscale = 1.0f/sizedata->texture_height; int height = bitmap->rows + outline_thickness*2 + 2; int width = bitmap->width + outline_thickness*2 + 2; fontchr->id = chr; fontchr->height = height * scale; fontchr->width = width * scale; fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale; fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale; fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale; fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars); fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars); fontchr->uvs[2] = fontchr->uvs[0] + width*uscale; fontchr->uvs[3] = fontchr->uvs[1] + height*vscale; } return slot_id; } static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr) { FONTCHAR *fontchr = NULL; /* search for the character */ /* TODO: remove this linear search */ int i; for(i = 0; i < sizedata->current_character; i++) { if(sizedata->characters[i].id == chr) { fontchr = &sizedata->characters[i]; break; } } /* check if we need to render the character */ if(!fontchr) { int index = font_render_glyph(font, sizedata, chr); if(index >= 0) fontchr = &sizedata->characters[index]; } /* touch the character */ /* TODO: don't call time_get here */ if(fontchr) fontchr->touch_time = time_get(); return fontchr; } /* must only be called from the rendering function as the font must be set to the correct size */ static void font_setsize(FONT *font, int size) { FT_Set_Pixel_Sizes(font->ft_face, 0, size); } static float font_kerning(FONT *font, int left, int right) { FT_Vector kerning = {0,0}; FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning ); return (kerning.x>>6); } static int utf8_decode(const unsigned char **ptr) { const unsigned char *buf = *ptr; int ch = 0; do { if((*buf&0x80) == 0x0) /* 0xxxxxxx */ { ch = *buf; buf++; } else if((*buf&0xE0) == 0xC0) /* 110xxxxx */ { ch = (*buf++ & 0x3F) << 6; if(!(*buf)) break; ch += (*buf++ & 0x3F); if(ch == 0) ch = -1; } else if((*buf & 0xF0) == 0xE0) /* 1110xxxx */ { ch = (*buf++ & 0x1F) << 12; if(!(*buf)) break; ch += (*buf++ & 0x3F) << 6; if(!(*buf)) break; ch += (*buf++ & 0x3F); if(ch == 0) ch = -1; } else if((*buf & 0xF8) == 0xF0) /* 11110xxx */ { ch = (*buf++ & 0x0F) << 18; if(!(*buf)) break; ch += (*buf++ & 0x3F) << 12; if(!(*buf)) break; ch += (*buf++ & 0x3F) << 6; if(!(*buf)) break; ch += (*buf++ & 0x3F); if(ch == 0) ch = -1; } else break; /* valid */ *ptr = buf; return ch; } while(0); /* invalid */ *ptr = buf; return -1; } void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length) { FONT *font = cursor->font; FONTSIZEDATA *sizedata = NULL; float screen_x0, screen_y0, screen_x1, screen_y1; float fake_to_screen_x, fake_to_screen_y; int actual_x, actual_y; int actual_size; int i; int got_new_line = 0; float draw_x, draw_y; float cursor_x, cursor_y; const char *end; float size = cursor->font_size; /* to correct coords, convert to screen coords, round, and convert back */ gfx_getscreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1); fake_to_screen_x = (gfx_screenwidth()/(screen_x1-screen_x0)); fake_to_screen_y = (gfx_screenheight()/(screen_y1-screen_y0)); actual_x = cursor->x * fake_to_screen_x; actual_y = cursor->y * fake_to_screen_y; cursor_x = actual_x / fake_to_screen_x; cursor_y = actual_y / fake_to_screen_y; /* same with size */ actual_size = size * fake_to_screen_y; size = actual_size / fake_to_screen_y; /* fetch font data */ if(!font) font = default_font; if(!font) return; sizedata = font_get_size(font, actual_size); font_setsize(font, actual_size); /* set length */ if(length < 0) length = strlen(text); end = text + length; /* if we don't want to render, we can just skip the first outline pass */ i = 1; if(cursor->flags&TEXTFLAG_RENDER) i = 0; for(;i < 2; i++) { const unsigned char *current = (unsigned char *)text; const unsigned char *end = current+length; //int to_render = length; draw_x = cursor_x; draw_y = cursor_y; if(cursor->flags&TEXTFLAG_RENDER) { // TODO: Make this better glEnable(GL_TEXTURE_2D); if (i == 0) glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]); else glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]); gfx_quads_begin(); if (i == 0) gfx_setcolor(0.0f, 0.0f, 0.0f, 0.3f*text_a); else gfx_setcolor(text_r, text_g, text_b, text_a); } while(current < end) { int new_line = 0; //int this_batch = (int)(end-current); const unsigned char *batch_end = end; if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END)) { int wlen = word_length((char *)current); TEXT_CURSOR compare = *cursor; compare.x = draw_x; compare.y = draw_y; compare.flags &= ~TEXTFLAG_RENDER; compare.line_width = -1; gfx_text_ex(&compare, text, wlen); if(compare.x-draw_x > cursor->line_width) { /* word can't be fitted in one line, cut it */ TEXT_CURSOR cutter = *cursor; cutter.charcount = 0; cutter.x = draw_x; cutter.y = draw_y; cutter.flags &= ~TEXTFLAG_RENDER; cutter.flags |= TEXTFLAG_STOP_AT_END; gfx_text_ex(&cutter, (const char *)current, wlen); wlen = cutter.charcount; new_line = 1; if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */ wlen = 0; } else if(compare.x-cursor->start_x > cursor->line_width) { new_line = 1; wlen = 0; } batch_end = current + wlen; //this_batch = wlen; } /*if((const char *)current+this_batch > end) this_batch = (const char *)end-(const char *)current; end = */ /*to_render -= this_batch;*/ while(current < batch_end) { float advance = 0; int character = 0; FONTCHAR *chr; // TODO: UTF-8 decode character = utf8_decode(¤t); if(character == '\n') { draw_x = cursor->start_x; draw_y += size; draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; /* current++; */ continue; } chr = font_get_char(font, sizedata, character); if(chr) { if(cursor->flags&TEXTFLAG_RENDER) { gfx_quads_setsubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]); gfx_quads_drawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size); } advance = chr->advance_x; /* + font_kerning(font, character, *(current+1));*/ } if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width) { /* we hit the end of the line, no more to render or count */ current = end; break; } draw_x += advance*size; cursor->charcount++; /* current++; */ } if(new_line) { draw_x = cursor->start_x; draw_y += size; got_new_line = 1; draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */ draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y; } } if(cursor->flags&TEXTFLAG_RENDER) gfx_quads_end(); } cursor->x = draw_x; if(got_new_line) cursor->y = draw_y; }