#include "quakedef.h" #ifdef AVAIL_FREETYPE #include "glquake.h" #pragma comment(lib, "../libs/freetype2/objs/freetype239.lib") #include #include FT_FREETYPE_H static FT_Library fontlib; #define FONTCHARS (1<<16) #define FONTPLANES (1<<2) //no more than 16 textures per font #define PLANEIDXTYPE unsigned char #define CHARIDXTYPE unsigned short #define INVALIDPLANE ((1<<(8*sizeof(PLANEIDXTYPE)))-1) #define PLANEWIDTH (1<<8) #define PLANEHEIGHT PLANEWIDTH #define GEN_CONCHAR_GLYPHS 0 //set to 0 or 1 to define whether to generate glyphs from conchars too, or if it should just draw them as glquake always used to extern cvar_t cl_noblink; typedef struct font_s { struct charcache_s { struct charcache_s *nextchar; PLANEIDXTYPE texplane; unsigned char bmx; unsigned char bmy; unsigned char bmw; unsigned char bmh; short top; short left; unsigned char advance; //how wide this char is, when drawn char pad; } chars[FONTCHARS]; short charheight; FT_Face face; } font_t; typedef struct { int texnum[FONTPLANES]; unsigned char plane[PLANEWIDTH*PLANEHEIGHT][4]; //tracks the current plane PLANEIDXTYPE activeplane; unsigned char planerowx; unsigned char planerowy; unsigned char planerowh; qboolean planechanged; struct charcache_s *oldestchar; struct charcache_s *newestchar; } fontplanes_t; static fontplanes_t fontplanes; void Font_Shutdown(void) { int i; for (i = 0; i < FONTPLANES; i++) fontplanes.texnum[i] = 0; fontplanes.activeplane = 0; fontplanes.oldestchar = NULL; fontplanes.newestchar = NULL; fontplanes.planechanged = 0; fontplanes.planerowx = 0; fontplanes.planerowy = 0; fontplanes.planerowh = 0; } void Font_FlushPlane(font_t *f) { /* assumption: oldest chars must be of the oldest plane */ if (fontplanes.planechanged) { GL_Bind(fontplanes.texnum[fontplanes.activeplane]); GL_Upload32(NULL, (void*)fontplanes.plane, PLANEWIDTH, PLANEHEIGHT, false, true); fontplanes.planechanged = false; } fontplanes.activeplane++; fontplanes.activeplane = fontplanes.activeplane % FONTPLANES; fontplanes.planerowh = 0; fontplanes.planerowx = 0; fontplanes.planerowy = 0; for (; fontplanes.oldestchar; fontplanes.oldestchar = fontplanes.oldestchar->nextchar) { if (fontplanes.oldestchar->texplane != fontplanes.activeplane) break; //invalidate it fontplanes.oldestchar->texplane = INVALIDPLANE; } if (!fontplanes.oldestchar) fontplanes.newestchar = NULL; } struct charcache_s *Font_GetChar(font_t *f, CHARIDXTYPE charidx) { struct charcache_s *c = &f->chars[charidx]; if (c->texplane == INVALIDPLANE) { //not cached, can't get. return NULL; } return c; } struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, int alphaonly, void *data, unsigned char bmw, unsigned char bmh, unsigned int pitch) { int x, y; unsigned char *out; struct charcache_s *c = &f->chars[charidx]; if (fontplanes.planerowx + (int)bmw >= PLANEWIDTH) { fontplanes.planerowx = 0; fontplanes.planerowy += fontplanes.planerowh; fontplanes.planerowh = 0; } if (fontplanes.planerowy+(int)bmh >= PLANEHEIGHT) Font_FlushPlane(f); if (fontplanes.newestchar) fontplanes.newestchar->nextchar = c; else fontplanes.oldestchar = c; fontplanes.newestchar = c; c->nextchar = NULL; c->texplane = fontplanes.activeplane; c->bmx = fontplanes.planerowx; c->bmy = fontplanes.planerowy; c->bmw = bmw; c->bmh = bmh; if (fontplanes.planerowh < (int)bmh) fontplanes.planerowh = bmh; fontplanes.planerowx += bmw; out = (unsigned char *)&fontplanes.plane[c->bmx+(int)c->bmy*PLANEHEIGHT]; if (alphaonly) { for (y = 0; y < bmh; y++) { for (x = 0; x < bmw; x++) { *(unsigned int *)&out[x*4] = 0xffffffff; out[x*4+3] = ((unsigned char*)data)[x]; } data = (char*)data + pitch; out += PLANEWIDTH*4; } } else { pitch*=4; for (y = 0; y < bmh; y++) { for (x = 0; x < bmw; x++) { ((unsigned int*)out)[x] = ((unsigned int*)data)[x]; } data = (char*)data + pitch; out += PLANEWIDTH*4; } } fontplanes.planechanged = true; return c; } struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) { struct charcache_s *c; FT_GlyphSlot slot; FT_Bitmap *bm; #if GEN_CONCHAR_GLYPHS != 0 if (charidx >= 0xe000 && charidx <= 0xe0ff && draw_chars) { int cpos = charidx & 0xff; unsigned int img[64*64], *d; unsigned char *s; int scale; int x,y, ys; if (draw_chars) { d = img; s = draw_chars + 8*(cpos&15)+128*8*(cpos/16); scale = f->charheight/8; if (scale < 1) scale = 1; if (scale > 64/8) scale = 64/8; for (y = 0; y < 8; y++) { for (ys = 0; ys < scale; ys++) { for (x = 0; x < 8*scale; x++) d[x] = d_8to24rgbtable[s[x/scale]]; d+=8*scale; } s+=128; } c = Font_LoadGlyphData(f, charidx, false, img, 8*scale, 8*scale, 8*scale); if (c) { c->advance = 8*scale; c->left = 0; c->top = 7*scale; } return c; } charidx &= 0x7f; } #endif if (FT_Load_Char(f->face, charidx, FT_LOAD_RENDER)) return NULL; slot = f->face->glyph; bm = &slot->bitmap; c = Font_LoadGlyphData(f, charidx, true, bm->buffer, bm->width, bm->rows, bm->pitch); if (c) { c->advance = slot->advance.x >> 6; c->left = slot->bitmap_left; c->top = slot->bitmap_top; } return c; } struct font_s *Font_LoadFont(int height, char *fontfilename) { int i; struct font_s *f; FT_Face face; int error; if (!fontlib) { error = FT_Init_FreeType(&fontlib); if (error) return NULL; } //fixme: use FT_Open_Face eventually if (FT_New_Face(fontlib, fontfilename, 0, &face)) { return NULL; } error = FT_Set_Pixel_Sizes(face, 0, height); if (error) { return NULL; } f = malloc(sizeof(*f)); memset(f, 0, sizeof(*f)); f->face = face; f->charheight = height; fontplanes.texnum[0] = GL_AllocNewTexture(); fontplanes.texnum[1] = GL_AllocNewTexture(); fontplanes.texnum[2] = GL_AllocNewTexture(); fontplanes.texnum[3] = GL_AllocNewTexture(); for (i = 0; i < FONTCHARS; i++) { f->chars[i].texplane = INVALIDPLANE; } return f; } void Font_Free(struct font_s *f) { FT_Done_Face(f->face); free(f); } void GLFont_BeginString(struct font_s *font, int vx, int vy, int *px, int *py) { *px = (vx*glwidth) / (float)vid.width; *py = (vy*glheight) / (float)vid.height; } int GLFont_CharHeight(struct font_s *font, unsigned int charcode) { if (!font) return 8; return font->charheight; } int GLFont_CharWidth(struct font_s *font, unsigned int charcode) { struct charcache_s *c; if (!font) return 8; #if GEN_CONCHAR_GLYPHS == 0 if ((charcode&CON_CHARMASK) >= 0xe000 && (charcode&CON_CHARMASK) <= 0xe0ff) return font->charheight; #endif c = Font_GetChar(font, charcode&CON_CHARMASK); if (!c) { c = Font_TryLoadGlyph(font, charcode&CON_CHARMASK); if (!c) return 0; } return c->advance; } int GLFont_LineBreaks(struct font_s *font, conchar_t *start, conchar_t *end, int scrwidth, int maxlines, conchar_t **starts, conchar_t **ends) { int l, bt; int px; int foundlines = 0; while (start < end) { // scan the width of the line for (px=0, l=0 ; px <= scrwidth;) { if ((start[l]&CON_CHARMASK) == '\n' || (start+l >= end)) break; l++; px += GLFont_CharWidth(font, start[l]); } //if we did get to the end if (px > scrwidth) { bt = l; //backtrack until we find a space while(l > 0 && (start[l-1]&CON_CHARMASK)>' ') { l--; } if (l == 0 && bt>0) l = bt-1; px -= GLFont_CharWidth(font, start[l]); } starts[foundlines] = start; ends[foundlines] = start+l; foundlines++; if (foundlines == maxlines) break; start+=l; // for (l=0 ; l<40 && *start && *start != '\n'; l++) // start++; if ((*start&CON_CHARMASK) == '\n'||!l) start++; // skip the \n } return foundlines; } void GLDraw_FillRGB (int x, int y, int w, int h, float r, float g, float b); int GLFont_DrawChar(struct font_s *font, int px, int py, unsigned int charcode) { struct charcache_s *c; float s0, s1; float t0, t1; float nextx; float sx, sy, sw, sh; int col; if (!font) return px; #if GEN_CONCHAR_GLYPHS == 0 if ((charcode&CON_CHARMASK) >= 0xe000 && (charcode&CON_CHARMASK) <= 0xe0ff) { extern char_texture; if (charcode == 32) return px+font->charheight; // space if (charcode & CON_BLINKTEXT) { if (!cl_noblink.value) if ((int)(realtime*3) & 1) return px+font->charheight; } qglEnable(GL_BLEND); qglDisable(GL_ALPHA_TEST); col = (charcode & CON_FGMASK) >> CON_FGSHIFT; qglColor4f(consolecolours[col].fr, consolecolours[col].fg, consolecolours[col].fb, (charcode & CON_HALFALPHA)?0.5:1); charcode &= 0xff; sx = ((px)*vid.width) / (float)glwidth; sy = ((py+font->charheight/8)*vid.height) / (float)glheight; sw = ((font->charheight)*vid.width) / (float)glwidth; sh = ((font->charheight)*vid.height) / (float)glheight; col = charcode&15; s0 = (float)col/16; s1 = (float)(col+1)/16; col = charcode>>4; t0 = (float)col/16; t1 = (float)(col+1)/16; qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_Bind(char_texture); qglBegin(GL_QUADS); qglTexCoord2f(s0, t0); qglVertex2f(sx, sy); qglTexCoord2f(s1, t0); qglVertex2f(sx+sw, sy); qglTexCoord2f(s1, t1); qglVertex2f(sx+sw, sy+sh); qglTexCoord2f(s0, t1); qglVertex2f(sx, sy+sh); qglEnd(); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); return px+font->charheight; } #endif c = Font_GetChar(font, charcode&CON_CHARMASK); if (!c) { c = Font_TryLoadGlyph(font, charcode&CON_CHARMASK); if (!c) return px; } nextx = px + c->advance; if (charcode & CON_BLINKTEXT) { if (!cl_noblink.value) if ((int)(realtime*3) & 1) return nextx; } // draw background if (charcode & CON_NONCLEARBG) { sx = ((px)*vid.width) / (float)glwidth; sy = ((py+font->charheight/3)*vid.height) / (float)glheight; sw = ((c->advance)*vid.width) / (float)glwidth; sh = ((font->charheight)*vid.height) / (float)glheight; col = (charcode & CON_BGMASK) >> CON_BGSHIFT; GLDraw_FillRGB(sx, sy, sw, sh, consolecolours[col].fr, consolecolours[col].fg, consolecolours[col].fb); } sx = ((px+c->left)*vid.width) / (float)glwidth; sy = ((py+font->charheight-c->top)*vid.height) / (float)glheight; sw = ((c->bmw)*vid.width) / (float)glwidth; sh = ((c->bmh)*vid.height) / (float)glheight; s0 = (float)c->bmx/PLANEWIDTH; t0 = (float)c->bmy/PLANEWIDTH; s1 = (float)(c->bmx+c->bmw)/PLANEWIDTH; t1 = (float)(c->bmy+c->bmh)/PLANEWIDTH; qglEnable(GL_BLEND); qglDisable(GL_ALPHA_TEST); col = (charcode & CON_FGMASK) >> CON_FGSHIFT; qglColor4f(consolecolours[col].fr, consolecolours[col].fg, consolecolours[col].fb, (charcode & CON_HALFALPHA)?0.5:1); qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (fontplanes.planechanged) { GL_Bind(fontplanes.texnum[fontplanes.activeplane]); GL_Upload32(NULL, (void*)fontplanes.plane, PLANEWIDTH, PLANEHEIGHT, false, true); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); fontplanes.planechanged = false; } GL_Bind(fontplanes.texnum[c->texplane]); qglBegin(GL_QUADS); qglTexCoord2f(s0, t0); qglVertex2f(sx, sy); qglTexCoord2f(s1, t0); qglVertex2f(sx+sw, sy); qglTexCoord2f(s1, t1); qglVertex2f(sx+sw, sy+sh); qglTexCoord2f(s0, t1); qglVertex2f(sx, sy+sh); qglEnd(); qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); return nextx; } #endif