2009-07-05 18:45:53 +00:00
# include "quakedef.h"
# ifdef AVAIL_FREETYPE
# include "glquake.h"
# 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 ;
2009-07-14 15:59:49 +00:00
qboolean triedtoloadfreetype ;
dllhandle_t * fontmodule ;
FT_Error ( VARGS * pFT_Init_FreeType ) ( FT_Library * alibrary ) ;
FT_Error ( VARGS * pFT_Load_Char ) ( FT_Face face , FT_ULong char_code , FT_Int32 load_flags ) ;
FT_Error ( VARGS * pFT_Set_Pixel_Sizes ) ( FT_Face face , FT_UInt pixel_width , FT_UInt pixel_height ) ;
FT_Error ( VARGS * pFT_New_Face ) ( FT_Library library , const char * pathname , FT_Long face_index , FT_Face * aface ) ;
FT_Error ( VARGS * pFT_Done_Face ) ( FT_Face face ) ;
2009-07-05 18:45:53 +00:00
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
2009-07-14 15:59:49 +00:00
if ( charidx > = 0xe100 & & charidx < = 0xe1ff )
{
static const char * imgs [ ] =
{
" inv_shotgun " ,
" inv_sshotgun " ,
" inv_nailgun " ,
" inv_snailgun " ,
" inv_rlaunch " ,
" inv_srlaunch " ,
" inv_lightng " ,
" inv2_shotgun " ,
" inv2_sshotgun " ,
" inv2_nailgun " ,
" inv2_snailgun " ,
" inv2_rlaunch " ,
" inv2_srlaunch " ,
" inv2_lightng " ,
" sb_shells " ,
" sb_nails " ,
" sb_rocket " ,
" sb_cells " ,
" sb_armor1 " ,
" sb_armor2 " ,
" sb_armor3 " ,
" sb_key1 " ,
" sb_key2 " ,
" sb_invis " ,
" sb_invuln " ,
" sb_suit " ,
" sb_quad " ,
" sb_sigil1 " ,
" sb_sigil2 " ,
" sb_sigil3 " ,
" sb_sigil4 " ,
" face1 " ,
" face_p1 " ,
" face2 " ,
" face_p2 " ,
" face3 " ,
" face_p3 " ,
" face4 " ,
" face_p4 " ,
" face5 " ,
" face_p5 " ,
" face_invis " ,
" face_invul2 " ,
" face_inv2 " ,
" face_quad "
} ;
qpic_t * wadimg ;
unsigned char * src ;
unsigned int img [ 64 * 64 ] ;
int nw , nh ;
int x , y ;
unsigned int stepx , stepy ;
unsigned int srcx , srcy ;
if ( charidx - 0xe100 > = sizeof ( imgs ) / sizeof ( imgs [ 0 ] ) )
wadimg = NULL ;
else
wadimg = W_SafeGetLumpName ( imgs [ charidx - 0xe100 ] ) ;
if ( wadimg )
{
nh = wadimg - > height ;
nw = wadimg - > width ;
while ( nh < f - > charheight )
{
nh * = 2 ;
nw * = 2 ;
}
if ( nh > f - > charheight )
{
nw = ( nw * f - > charheight ) / nh ;
nh = f - > charheight ;
}
stepy = 0x10000 * ( ( float ) wadimg - > height / nh ) ;
stepx = 0x10000 * ( ( float ) wadimg - > width / nw ) ;
if ( nh > 64 )
nh = 64 ;
if ( nw > 64 )
nw = 64 ;
srcy = 0 ;
for ( y = 0 ; y < nh ; y + + )
{
src = ( unsigned char * ) ( wadimg - > data ) ;
src + = wadimg - > width * ( srcy > > 16 ) ;
srcy + = stepy ;
srcx = 0 ;
for ( x = 0 ; x < nw ; x + + )
{
img [ x + y * 64 ] = d_8to24rgbtable [ src [ srcx > > 16 ] ] ;
srcx + = stepx ;
}
}
c = Font_LoadGlyphData ( f , charidx , false , img , nw , nh , 64 ) ;
if ( c )
{
c - > left = 0 ;
c - > top = f - > charheight - ( f - > charheight - nh ) - 1 ;
c - > advance = nw ;
return c ;
}
}
}
if ( pFT_Load_Char ( f - > face , charidx , FT_LOAD_RENDER ) )
2009-07-05 18:45:53 +00:00
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 )
2009-07-14 15:59:49 +00:00
{
dllfunction_t ft2funcs [ ] =
{
{ ( void * * ) & pFT_Init_FreeType , " FT_Init_FreeType " } ,
{ ( void * * ) & pFT_Load_Char , " FT_Load_Char " } ,
{ ( void * * ) & pFT_Set_Pixel_Sizes , " FT_Set_Pixel_Sizes " } ,
{ ( void * * ) & pFT_New_Face , " FT_New_Face " } ,
{ ( void * * ) & pFT_Init_FreeType , " FT_Init_FreeType " } ,
{ ( void * * ) & pFT_Done_Face , " FT_Done_Face " } ,
{ NULL , NULL }
} ;
if ( triedtoloadfreetype )
return NULL ;
triedtoloadfreetype = true ;
fontmodule = Sys_LoadLibrary ( " freetype6 " , ft2funcs ) ;
if ( ! fontmodule )
return NULL ;
error = pFT_Init_FreeType ( & fontlib ) ;
2009-07-05 18:45:53 +00:00
if ( error )
return NULL ;
}
//fixme: use FT_Open_Face eventually
2009-07-14 15:59:49 +00:00
if ( pFT_New_Face ( fontlib , fontfilename , 0 , & face ) )
2009-07-05 18:45:53 +00:00
{
return NULL ;
}
2009-07-14 15:59:49 +00:00
error = pFT_Set_Pixel_Sizes ( face , 0 , height ) ;
2009-07-05 18:45:53 +00:00
if ( error )
{
return NULL ;
}
f = malloc ( sizeof ( * f ) ) ;
memset ( f , 0 , sizeof ( * f ) ) ;
f - > face = face ;
f - > charheight = height ;
2009-07-14 15:59:49 +00:00
for ( i = 0 ; i < FONTPLANES ; i + + )
{
if ( ! fontplanes . texnum [ i ] )
fontplanes . texnum [ i ] = GL_AllocNewTexture ( ) ;
}
2009-07-05 18:45:53 +00:00
for ( i = 0 ; i < FONTCHARS ; i + + )
{
f - > chars [ i ] . texplane = INVALIDPLANE ;
}
return f ;
}
void Font_Free ( struct font_s * f )
{
2009-07-14 15:59:49 +00:00
struct charcache_s * * link ;
for ( link = & fontplanes . oldestchar ; * link ; )
{
if ( * link > = f - > chars & & * link < = f - > chars + FONTCHARS )
{
* link = ( * link ) - > nextchar ;
if ( ! * link )
fontplanes . newestchar = NULL ;
}
else
link = & ( * link ) - > nextchar ;
}
pFT_Done_Face ( f - > face ) ;
2009-07-05 18:45:53 +00:00
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 ;
}
2009-07-14 15:59:49 +00:00
int GLFont_CharHeight ( struct font_s * font )
2009-07-05 18:45:53 +00:00
{
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