FANDOM


// rendertext.cpp: font rendering
 
#include "cube.h"
 
int VIRTW;
bool ignoreblinkingbit = false; // for remote-n-temp override of '\fb'
static hashtable<const char *, font> fonts;
static font *fontdef = NULL;
 
font *curfont = NULL;
 
VARP(allowblinkingtext, 0, 0, 1); // if you're so inclined
VARP(__fontsetting, 0, 0, 2);
 
void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *offsetx, int *offsety, int *offsetw, int *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 = *defaultw;
    f->defaulth = *defaulth;
    f->offsetx = *offsetx;
    f->offsety = *offsety;
    f->offsetw = *offsetw;
    f->offseth = *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, "ssiiiiii");
COMMAND(fontchar, "iiii");
COMMAND(fontskip, "i");
 
string myfont = "default";
void newsetfont(const char *name)
{
    if ( setfont(name) ) copystring(myfont,name);
}
 
bool setfont(const char *name)
{
    font *f = fonts.access(name);
    if(!f) return false;
    int v = -1;
    if(strcmp(name, "default")==0)
        v = 0;
    else if(strcmp(name, "serif")==0)
        v = 1;
    else if(strcmp(name, "mono")==0)
        v = 2;
    if(v!=-1) __fontsetting = v;
    curfont = f;
    return true;
}
COMMANDN(setfont, newsetfont, "s");
 
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; }
    //return NULL;
    font::charinfo &info = curfont->chars[0]; // 0 || (FONTCHARS-1)
    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);
    }
    */
    return 0;
}
 
 
//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') { if(allowblinkingtext && !ignoreblinkingbit) stack[sp] *= -1; } // blinking text - only if allowed
        else stack[sp] = c;
        switch(abs(stack[sp]))
        {
            case '0': color = bvec( 2,  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 - 230 230 20 too bright
            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( 153,  51,  51 ); break;   // dark red: dead admin
            case '8': color = bvec( 192,  64, 192 ); break;   // magenta
            case '9': color = bvec( 255, 102,   0 ); break;   // orange
 
            case 'm': color = bvec(   0, 116,  57 ); break;   // dark green: dead master
            case 'o': color = bvec(  31,  86, 166 ); break;   // dark blue: dead 'owner'
 
            //extendeded color palette
            //case 'a': case 'A':color = bvec( 0xFF, 0xCC, 0xCC); break;   // some lowercase seem to have special meaning like 'b' (flashing text) so not yet using them
            case 'A':color = bvec( 0xff, 0xb7, 0xb7); break;   // red set
            case 'B':color = bvec( 0xCC, 0x33, 0x33); break;   //
            case 'C':color = bvec( 0x66, 0x33, 0x33); break;   //
            case 'D':color = bvec( 0xF8, 0x98, 0x4E); break;   //
 
            case 'E':color = bvec( 0xFF, 0xFF, 0xB7); break;   // yellow set
            case 'F':color = bvec( 0xCC, 0xCC, 0x33); break;   //
            case 'G':color = bvec( 0x66, 0x66, 0x33); break;   //
            case 'H':color = bvec( 0xCC, 0xFC, 0x58); break;   //
 
            case 'I':color = bvec( 0xB7, 0xFF, 0xB7); break;   // green set
            case 'J':color = bvec( 0x33, 0xCC, 0x33); break;   //
            case 'K':color = bvec( 0x33, 0x66, 0x33); break;   //
            case 'L':color = bvec( 0x3F, 0xFF, 0x98); break;   //
 
            case 'M':color = bvec( 0xB7, 0xFF, 0xFF); break;   // cyan set
            case 'N':color = bvec( 0x33, 0xCC, 0xCC); break;   //
            case 'O':color = bvec( 0x33, 0x66, 0x66); break;   //
            case 'P':color = bvec( 0x4F, 0xCC, 0xF8); break;   //
 
            case 'Q':color = bvec( 0xB7, 0xB7, 0xFF); break;   // blue set
            case 'R':color = bvec( 0x33, 0x33, 0xCC); break;   //
            case 'S':color = bvec( 0x33, 0x33, 0x66); break;   //
            case 'T':color = bvec( 0xA0, 0x49, 0xFF); break;   //
 
            case 'U':color = bvec( 0xFF, 0xB7, 0xFF); break;   // magenta set
            case 'V':color = bvec( 0xCC, 0x33, 0xCC); break;   //
            case 'W':color = bvec( 0x66, 0x33, 0x66); break;   //
            case 'X':color = bvec( 0xFF, 0x01, 0xD5); break;   //
 
            case 'Y':color = bvec( 0xC7, 0xD1, 0xE2); break;   // lt gray
            case 'Z':color = bvec( 0x32, 0x32, 0x32); break;   // dark gray
            // white (provided color): everything else
            //default: color = bvec( 255, 255, 255 ); break;
        }
        glColor4ub(color.x, color.y, color.z, stack[sp] > 0 ? a : a * min<float>(fabs(sinf(lastmillis / 200.f) * 1.15f), 1.f));
    }
}
 
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
}
 
/** This is the 1.0.4 function
    It will substituted by draw_text_wip
    I am putting this temporarily here because it is impossible to test without colours : Brahma */
void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth)
{
#define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; cc = str[idx]; }
#define TEXTTAB(idx) TEXTGETCOLUMN
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a);
#define TEXTCHAR(idx) x += draw_char(c, left+x, top+y)+1;
#define TEXTWORD TEXTWORDSKELETON
    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);
    TEXTSKELETON
            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-33) ? curfont->chars[cc-curfont->skip].w + 1 : curfont->defaultw;
        rendercursor(left+cx, top+cy, cw);
    }
#undef TEXTINDEX
#undef TEXTTAB
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTCHAR
#undef TEXTWORD
}
 
/* WIP ALERT */
/*
void draw_text_wip(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");
    );
}