git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@4921 e016b1c5-ed68-44aa-a2f7-31cac88cffde
644 lines
15 KiB
C++
644 lines
15 KiB
C++
// rendertext.cpp: font rendering
|
|
|
|
#include "cube.h"
|
|
|
|
int VIRTW;
|
|
|
|
static hashtable<const char *, font> fonts;
|
|
static font *fontdef = NULL;
|
|
|
|
font *curfont = NULL;
|
|
|
|
void newfont(char *name, char *tex, char *defaultw, char *defaulth, char *offsetx, char *offsety, char *offsetw, char *offseth)
|
|
{
|
|
font *f = fonts.access(name);
|
|
if(!f)
|
|
{
|
|
name = newstring(name);
|
|
f = &fonts[name];
|
|
f->name = name;
|
|
}
|
|
|
|
f->tex = textureload(tex);
|
|
f->chars.shrink(0);
|
|
f->defaultw = ATOI(defaultw);
|
|
f->defaulth = ATOI(defaulth);
|
|
f->offsetx = ATOI(offsetx);
|
|
f->offsety = ATOI(offsety);
|
|
f->offsetw = ATOI(offsetw);
|
|
f->offseth = ATOI(offseth);
|
|
f->skip = 33;
|
|
|
|
fontdef = f;
|
|
}
|
|
|
|
extern GLenum texformat(int bpp);
|
|
|
|
void fontchar(int x, int y, int w, int h)
|
|
{
|
|
if(!fontdef) return;
|
|
|
|
font::charinfo &c = fontdef->chars.add();
|
|
c.x = x;
|
|
c.y = y;
|
|
c.w = w ? w : fontdef->defaultw;
|
|
c.h = h ? h : fontdef->defaulth;
|
|
}
|
|
|
|
void fontskip(int n)
|
|
{
|
|
if(!fontdef) return;
|
|
|
|
fontdef->skip = n;
|
|
}
|
|
|
|
COMMANDN(font, newfont, ARG_8STR);
|
|
COMMAND(fontchar, ARG_4INT);
|
|
COMMAND(fontskip, ARG_1INT);
|
|
|
|
bool setfont(const char *name)
|
|
{
|
|
font *f = fonts.access(name);
|
|
if(!f) return false;
|
|
curfont = f;
|
|
return true;
|
|
}
|
|
|
|
font *getfont(const char *name)
|
|
{
|
|
return fonts.access(name);
|
|
}
|
|
|
|
static vector<font *> fontstack;
|
|
|
|
void pushfont(const char *name)
|
|
{
|
|
fontstack.add(curfont);
|
|
setfont(name);
|
|
}
|
|
|
|
void popfont()
|
|
{
|
|
if(!fontstack.empty()) curfont = fontstack.pop();
|
|
}
|
|
|
|
int text_width(const char *str)
|
|
{
|
|
int width, height;
|
|
text_bounds(str, width, height);
|
|
return width;
|
|
}
|
|
|
|
void draw_textf(const char *fstr, int left, int top, ...)
|
|
{
|
|
defvformatstring(str, top, fstr);
|
|
draw_text(str, left, top);
|
|
}
|
|
|
|
|
|
|
|
extern int shdsize, outline, win32msg;
|
|
|
|
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
#define RMASK 0xff000000
|
|
#define GMASK 0x00ff0000
|
|
#define BMASK 0x0000ff00
|
|
#define AMASK 0x000000ff
|
|
#else
|
|
#define RMASK 0x000000ff
|
|
#define GMASK 0x0000ff00
|
|
#define BMASK 0x00ff0000
|
|
#define AMASK 0xff000000
|
|
#endif
|
|
|
|
// ringbuf for utf8 character storage
|
|
struct charringbuf : ringbuf<font::utf8charinfo, 32>
|
|
{
|
|
// find by character code
|
|
int findbycharcode(int code)
|
|
{
|
|
loopi(len)
|
|
{
|
|
if(data[i].code == code)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
TTF_Font *ttffont = NULL;
|
|
font utf8font;
|
|
charringbuf utf8chars;
|
|
|
|
void initfont()
|
|
{
|
|
static bool initialized = false;
|
|
if(!initialized)
|
|
{
|
|
TTF_Init();
|
|
|
|
int fsize = 64;
|
|
const char *fontname = "packages/misc/font.ttf";
|
|
ttffont = TTF_OpenFont(findfile(path(fontname, true), "r"), fsize);
|
|
|
|
utf8font.defaulth = 0;
|
|
utf8font.defaultw = 0;
|
|
utf8font.offsetw = 0;
|
|
utf8font.offseth = 10;
|
|
utf8font.offsetx = 0;
|
|
utf8font.offsety = 0;
|
|
|
|
initialized = true;
|
|
}
|
|
}
|
|
|
|
void createutf8charset()
|
|
{
|
|
int isize = 512;
|
|
|
|
SDL_Surface *charsetsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, isize, isize, 32, RMASK, GMASK, BMASK, AMASK);
|
|
|
|
if(charsetsurface)
|
|
{
|
|
SDL_Color color;
|
|
color.r = 255;
|
|
color.g = 255;
|
|
color.b = 255;
|
|
|
|
int posx = 0;
|
|
int posy = 0;
|
|
|
|
loopv(utf8chars)
|
|
{
|
|
font::utf8charinfo &charinfo = utf8chars[i];
|
|
int code = charinfo.code;
|
|
|
|
char u[5] = {0,0,0,0,0};
|
|
utf8::append(code, u);
|
|
|
|
SDL_Surface *fontsurface = TTF_RenderUTF8_Blended(ttffont, u, color);
|
|
|
|
// update row/column info
|
|
if(posx + fontsurface->w > charsetsurface->w)
|
|
{
|
|
posx = 0;
|
|
posy += fontsurface->h; // fixme
|
|
}
|
|
if(posy + fontsurface->h > charsetsurface->h)
|
|
break;
|
|
|
|
// blit onto charset
|
|
SDL_SetAlpha(fontsurface, 0, 0);
|
|
blitsurface(charsetsurface, fontsurface, posx, posy);
|
|
|
|
// update charinfo properties
|
|
charinfo.x = posx;
|
|
charinfo.y = posy;
|
|
charinfo.w = fontsurface->w;
|
|
charinfo.h = fontsurface->h;
|
|
|
|
posx += fontsurface->w;
|
|
SDL_FreeSurface(fontsurface);
|
|
}
|
|
|
|
utf8font.tex = createtexturefromsurface("utf8charset", charsetsurface);
|
|
|
|
//extern void savepng(SDL_Surface *s, const char *name);
|
|
//savepng(t, "font.png");
|
|
|
|
SDL_FreeSurface(charsetsurface);
|
|
}
|
|
}
|
|
|
|
void addutf8char(int code)
|
|
{
|
|
// add to buf
|
|
font::utf8charinfo charinfo;
|
|
charinfo.code = code;
|
|
charinfo.x = charinfo.y = charinfo.w = charinfo.h = 0;
|
|
utf8chars.add(charinfo);
|
|
|
|
// update charset
|
|
createutf8charset();
|
|
}
|
|
|
|
font::charinfo *loadchar(int code)
|
|
{
|
|
int idx = utf8chars.findbycharcode(code);
|
|
if(idx >= 0)
|
|
return &utf8chars[idx];
|
|
|
|
// add
|
|
addutf8char(code);
|
|
|
|
idx = utf8chars.findbycharcode(code);
|
|
return &utf8chars[idx];
|
|
}
|
|
|
|
int draw_char(font &f, font::charinfo &info, int charcode, int x, int y)
|
|
{
|
|
// fixme
|
|
glEnd();
|
|
glBindTexture(GL_TEXTURE_2D, f.tex->id);
|
|
glBegin(GL_QUADS);
|
|
|
|
float tc_left = (info.x + f.offsetx) / float(f.tex->xs);
|
|
float tc_top = (info.y + f.offsety) / float(f.tex->ys);
|
|
float tc_right = (info.x + info.w + f.offsetw) / float(f.tex->xs);
|
|
float tc_bottom = (info.y + info.h + f.offseth) / float(f.tex->ys);
|
|
|
|
glTexCoord2f(tc_left, tc_top ); glVertex2f(x, y);
|
|
glTexCoord2f(tc_right, tc_top ); glVertex2f(x + info.w, y);
|
|
glTexCoord2f(tc_right, tc_bottom); glVertex2f(x + info.w, y + info.h);
|
|
glTexCoord2f(tc_left, tc_bottom); glVertex2f(x, y + info.h);
|
|
|
|
xtraverts += 4;
|
|
return info.w;
|
|
}
|
|
|
|
// fixme
|
|
font::charinfo &getcharinfo(int c)
|
|
{
|
|
if(curfont->chars.inrange(c-curfont->skip))
|
|
{
|
|
font::charinfo &info = curfont->chars[c-curfont->skip];
|
|
return info;
|
|
}
|
|
else
|
|
{
|
|
font::charinfo &info = *loadchar(c);
|
|
return info;
|
|
}
|
|
}
|
|
|
|
static int draw_char(int c, int x, int y)
|
|
{
|
|
if(curfont->chars.inrange(c-curfont->skip))
|
|
{
|
|
font::charinfo &info = curfont->chars[c-curfont->skip];
|
|
|
|
return draw_char(*curfont, info, c, x, y);
|
|
}
|
|
else
|
|
{
|
|
// fixme
|
|
glEnd();
|
|
font::charinfo &info = *loadchar(c);
|
|
glBegin(GL_QUADS);
|
|
|
|
return draw_char(utf8font, info, c, x, y);
|
|
}
|
|
}
|
|
|
|
|
|
//stack[sp] is current color index
|
|
static void text_color(char c, char *stack, int size, int &sp, bvec color, int a)
|
|
{
|
|
if(c=='s') // save color
|
|
{
|
|
c = stack[sp];
|
|
if(sp<size-1) stack[++sp] = c;
|
|
}
|
|
else
|
|
{
|
|
if(c=='r') c = stack[(sp > 0) ? --sp : sp]; // restore color
|
|
else if(c == 'b') stack[sp] *= -1;
|
|
else stack[sp] = c;
|
|
switch(abs(stack[sp]))
|
|
{
|
|
case '0': color = bvec( 64, 255, 128 ); break; // green: player talk
|
|
case '1': color = bvec( 96, 160, 255 ); break; // blue: team chat
|
|
case '2': color = bvec( 255, 192, 64 ); break; // yellow: gameplay action messages, only actions done by players
|
|
case '3': color = bvec( 255, 64, 64 ); break; // red: important errors and notes
|
|
case '4': color = bvec( 128, 128, 128 ); break; // gray
|
|
case '5': color = bvec( 255, 255, 255 ); break; // white
|
|
case '6': color = bvec( 96, 48, 0 ); break; // dark brown
|
|
case '7': color = bvec( 128, 48, 48 ); break; // dark red: dead admin
|
|
case '8': color = bvec( 192, 64, 192 ); break; // magenta
|
|
case '9': color = bvec( 255, 128, 0 ); break; // orange
|
|
// white (provided color): everything else
|
|
//default: color = bvec( 255, 255, 255 ); break;
|
|
}
|
|
int b = (int) (sinf(lastmillis / 200.0f) * 115.0f);
|
|
b = stack[sp] > 0 ? 100 : min(abs(b), 100);
|
|
glColor4ub(color.x, color.y, color.z, (a * b) / 100);
|
|
}
|
|
}
|
|
|
|
static vector<int> *columns = NULL;
|
|
|
|
void text_startcolumns()
|
|
{
|
|
if(!columns) columns = new vector<int>;
|
|
}
|
|
|
|
void text_endcolumns()
|
|
{
|
|
DELETEP(columns);
|
|
}
|
|
|
|
#define TABALIGN(x) ((((x)+PIXELTAB)/PIXELTAB)*PIXELTAB)
|
|
|
|
#define TEXTGETCOLUMN \
|
|
if(columns && col<columns->length()) \
|
|
{ \
|
|
colx += (*columns)[col++]; \
|
|
x = colx; \
|
|
} \
|
|
else x = TABALIGN(x);
|
|
|
|
#define TEXTSETCOLUMN \
|
|
if(columns) \
|
|
{ \
|
|
while(col>=columns->length()) columns->add(0); \
|
|
int w = TABALIGN(x) - colx; \
|
|
w = max(w, (*columns)[col]); \
|
|
(*columns)[col] = w; \
|
|
col++; \
|
|
colx += w; \
|
|
x = colx; \
|
|
} \
|
|
else x = TABALIGN(x);
|
|
|
|
|
|
#define TEXTSKELETON \
|
|
int y = 0, x = 0, col = 0, colx = 0;\
|
|
int i;\
|
|
for(i = 0; str[i]; i++)\
|
|
{\
|
|
TEXTINDEX(i)\
|
|
int c = str[i];\
|
|
if(c=='\t') { TEXTTAB(i); TEXTWHITE(i) }\
|
|
else if(c==' ') { x += curfont->defaultw; TEXTWHITE(i) }\
|
|
else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\
|
|
else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\
|
|
else if(c=='\a') { if(str[i+1]) { i++; }}\
|
|
else if(curfont->chars.inrange(c-curfont->skip))\
|
|
{\
|
|
if(maxwidth != -1)\
|
|
{\
|
|
int j = i;\
|
|
int w = curfont->chars[c-curfont->skip].w;\
|
|
for(; str[i+1]; i++)\
|
|
{\
|
|
int c = str[i+1];\
|
|
if(c=='\f') { if(str[i+2]) i++; continue; }\
|
|
if(i-j > 16) break;\
|
|
if(!curfont->chars.inrange(c-curfont->skip)) break;\
|
|
int cw = curfont->chars[c-curfont->skip].w + 1;\
|
|
if(w + cw >= maxwidth) break;\
|
|
w += cw;\
|
|
}\
|
|
if(x + w >= maxwidth && j!=0) { TEXTLINE(j-1) x = 0; y += FONTH; }\
|
|
TEXTWORD\
|
|
}\
|
|
else\
|
|
{ TEXTCHAR(i) }\
|
|
}\
|
|
}
|
|
|
|
//all the chars are guaranteed to be either drawable or color commands
|
|
#define TEXTWORDSKELETON \
|
|
for(; j <= i; j++)\
|
|
{\
|
|
TEXTINDEX(j)\
|
|
int c = str[j];\
|
|
if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\
|
|
else { TEXTCHAR(j) }\
|
|
}
|
|
|
|
int text_visible(const char *str, int hitx, int hity, int maxwidth)
|
|
{
|
|
#define TEXTINDEX(idx)
|
|
#define TEXTTAB(idx) TEXTGETCOLUMN
|
|
#define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx;
|
|
#define TEXTLINE(idx) if(y+FONTH > hity) return idx;
|
|
#define TEXTCOLOR(idx)
|
|
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w+1; TEXTWHITE(idx)
|
|
#define TEXTWORD TEXTWORDSKELETON
|
|
TEXTSKELETON
|
|
#undef TEXTINDEX
|
|
#undef TEXTTAB
|
|
#undef TEXTWHITE
|
|
#undef TEXTLINE
|
|
#undef TEXTCOLOR
|
|
#undef TEXTCHAR
|
|
#undef TEXTWORD
|
|
return i;
|
|
}
|
|
|
|
//inverse of text_visible
|
|
void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth)
|
|
{
|
|
#define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; }
|
|
#define TEXTTAB(idx) TEXTGETCOLUMN
|
|
#define TEXTWHITE(idx)
|
|
#define TEXTLINE(idx)
|
|
#define TEXTCOLOR(idx)
|
|
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
|
|
#define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
|
|
cx = INT_MIN;
|
|
cy = 0;
|
|
TEXTSKELETON
|
|
if(cx == INT_MIN) { cx = x; cy = y; }
|
|
#undef TEXTINDEX
|
|
#undef TEXTTAB
|
|
#undef TEXTWHITE
|
|
#undef TEXTLINE
|
|
#undef TEXTCOLOR
|
|
#undef TEXTCHAR
|
|
#undef TEXTWORD
|
|
}
|
|
|
|
void text_bounds(const char *str, int &width, int &height, int maxwidth)
|
|
{
|
|
#define TEXTINDEX(idx)
|
|
#define TEXTTAB(idx) TEXTSETCOLUMN
|
|
#define TEXTWHITE(idx)
|
|
#define TEXTLINE(idx) if(x > width) width = x;
|
|
#define TEXTCOLOR(idx)
|
|
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
|
|
#define TEXTWORD x += w + 1;
|
|
width = 0;
|
|
TEXTSKELETON
|
|
height = y + FONTH;
|
|
TEXTLINE(_)
|
|
#undef TEXTINDEX
|
|
#undef TEXTTAB
|
|
#undef TEXTWHITE
|
|
#undef TEXTLINE
|
|
#undef TEXTCOLOR
|
|
#undef TEXTCHAR
|
|
#undef TEXTWORD
|
|
}
|
|
|
|
/* WIP ALERT */
|
|
void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth)
|
|
{
|
|
char colorstack[10];
|
|
bvec color(r, g, b);
|
|
int colorpos = 0, cx = INT_MIN, cy = 0, cc = ' ';
|
|
colorstack[0] = 'c'; //indicate user color
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, curfont->tex->bpp==32 ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE);
|
|
glBindTexture(GL_TEXTURE_2D, curfont->tex->id);
|
|
|
|
glBegin(GL_QUADS);
|
|
glColor4ub(color.x, color.y, color.z, a);
|
|
|
|
std::string text(str);
|
|
std::string::iterator begin = text.begin();
|
|
std::string::iterator end = text.end();
|
|
std::string::iterator cursoriter = end;
|
|
if(cursor >= 0 && cursor < utf8::distance(begin, end))
|
|
{
|
|
cursoriter = begin;
|
|
utf8::advance(cursoriter, cursor, end);
|
|
}
|
|
|
|
int y = 0, x = 0, col = 0, colx = 0;
|
|
|
|
for(std::string::iterator iter = text.begin(); iter != text.end(); utf8::next(iter, text.end()))
|
|
{
|
|
int c = utf8::peek_next(iter, text.end());
|
|
|
|
if(iter == cursoriter)
|
|
{
|
|
cx = x;
|
|
cy = y;
|
|
cc = c;
|
|
}
|
|
|
|
if(c=='\t')
|
|
{
|
|
if(columns && col<columns->length())
|
|
{
|
|
colx += (*columns)[col++];
|
|
x = colx;
|
|
}
|
|
else x = TABALIGN(x);
|
|
}
|
|
else if(c==' ')
|
|
{
|
|
x += curfont->defaultw;
|
|
}
|
|
else if(c=='\n')
|
|
{
|
|
x = 0;
|
|
y += FONTH;
|
|
}
|
|
else if(c=='\f')
|
|
{
|
|
std::string::iterator test = iter;
|
|
test++;
|
|
if(test != end)
|
|
{
|
|
c = utf8::next(iter, end);
|
|
text_color(c, colorstack, sizeof(colorstack), colorpos, color, a);
|
|
}
|
|
}
|
|
else if(c=='\a')
|
|
{
|
|
std::string::iterator next = iter;
|
|
next++;
|
|
if(next != end)
|
|
{
|
|
iter++;
|
|
}
|
|
|
|
}
|
|
else if(curfont->chars.inrange(c-curfont->skip))
|
|
{
|
|
font::charinfo &cinfo = getcharinfo(c);
|
|
|
|
if(maxwidth != -1)
|
|
{
|
|
std::string::iterator next = iter;
|
|
int w = cinfo.w;
|
|
|
|
do
|
|
{
|
|
std::string::iterator test = iter;
|
|
int c = utf8::next(test, end);
|
|
if(test == end) break;
|
|
|
|
if(c=='\f')
|
|
{
|
|
std::string::iterator test = iter;
|
|
utf8::advance(test, 2, end);
|
|
if(test == end) break;
|
|
utf8::next(iter, end);
|
|
continue;
|
|
}
|
|
if(utf8::distance(iter, next) > 16) break;
|
|
//if(!curfont->chars.inrange(c-curfont->skip)) fixme
|
|
if(c < curfont->skip) // fixme
|
|
{
|
|
break;
|
|
}
|
|
int cw = getcharinfo(c).w + 1;
|
|
if(w + cw >= maxwidth) break;
|
|
w += cw;
|
|
|
|
utf8::next(iter, end);
|
|
|
|
} while(true);
|
|
|
|
if(x + w >= maxwidth && next != begin) //fixme
|
|
{
|
|
x = 0;
|
|
y += FONTH;
|
|
}
|
|
|
|
for(; next <= iter && next != end; )
|
|
{
|
|
int c = utf8::peek_next(next, end);
|
|
if(next == cursoriter) { cx = x; cy = y; cc = c; }
|
|
|
|
if(c=='\f')
|
|
{
|
|
std::string::iterator test = next;
|
|
utf8::next(test, end);
|
|
if(test != end)
|
|
{
|
|
c = utf8::next(next, end);
|
|
text_color(c, colorstack, sizeof(colorstack), colorpos, color, a);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x += draw_char(c, left+x, top+y)+1;
|
|
}
|
|
|
|
utf8::next(next, end);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
x += draw_char(c, left+x, top+y)+1;
|
|
}
|
|
}
|
|
}
|
|
|
|
glEnd();
|
|
if(cursor >= 0)
|
|
{
|
|
if(cx == INT_MIN) { cx = x; cy = y; }
|
|
if(maxwidth != -1 && cx >= maxwidth) { cx = 0; cy += FONTH; }
|
|
int cw = curfont->chars.inrange(cc-curfont->skip) ? curfont->chars[cc-curfont->skip].w + 1 : curfont->defaultw;
|
|
rendercursor(left+cx, top+cy, cw);
|
|
}
|
|
}
|
|
|
|
void reloadfonts()
|
|
{
|
|
createutf8charset();
|
|
|
|
enumerate(fonts, font, f,
|
|
if(!reloadtexture(*f.tex)) fatal("failed to reload font texture");
|
|
);
|
|
}
|