engine/engine/gl/gl_font.c

517 lines
12 KiB
C
Raw Normal View History

#include "quakedef.h"
#ifdef AVAIL_FREETYPE
#include "glquake.h"
#pragma comment(lib, "../libs/freetype2/objs/freetype239.lib")
#include <ft2build.h>
#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