OctaCore/src/engine/rendertext.cc

447 lines
12 KiB
C++

#include "rendertext.hh"
#include <shared/command.hh>
#include <shared/glemu.hh>
#include "main.hh" // fatal, totalmillis
#include "rendergl.hh" // xtraverts
#include "texture.hh"
static hashnameset<font> fonts;
static font *fontdef = nullptr;
static int fontdeftex = 0;
font *curfont = nullptr;
static void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *scale)
{
font *f = &fonts[name];
if(!f->name) f->name = newstring(name);
f->texs.shrink(0);
f->texs.add(textureload(tex));
f->chars.shrink(0);
f->charoffset = '!';
f->defaultw = *defaultw;
f->defaulth = *defaulth;
f->scale = *scale > 0 ? *scale : f->defaulth;
f->bordermin = 0.49f;
f->bordermax = 0.5f;
f->outlinemin = -1;
f->outlinemax = 0;
fontdef = f;
fontdeftex = 0;
}
static void fontborder(float *bordermin, float *bordermax)
{
if(!fontdef) return;
fontdef->bordermin = *bordermin;
fontdef->bordermax = max(*bordermax, *bordermin+0.01f);
}
static void fontoutline(float *outlinemin, float *outlinemax)
{
if(!fontdef) return;
fontdef->outlinemin = min(*outlinemin, *outlinemax-0.01f);
fontdef->outlinemax = *outlinemax;
}
static void fontoffset(char *c)
{
if(!fontdef) return;
fontdef->charoffset = c[0];
}
static void fontscale(int *scale)
{
if(!fontdef) return;
fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth;
}
static void fonttex(char *s)
{
if(!fontdef) return;
Texture *t = textureload(s);
loopv(fontdef->texs) if(fontdef->texs[i] == t) { fontdeftex = i; return; }
fontdeftex = fontdef->texs.length();
fontdef->texs.add(t);
}
static void fontchar(float *x, float *y, float *w, float *h, float *offsetx, float *offsety, float *advance)
{
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;
c.offsetx = *offsetx;
c.offsety = *offsety;
c.advance = *advance ? *advance : c.offsetx + c.w;
c.tex = fontdeftex;
}
static void fontskip(int *n)
{
if(!fontdef) return;
loopi(max(*n, 1))
{
font::charinfo &c = fontdef->chars.add();
c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = 0;
c.tex = 0;
}
}
COMMANDN(font, newfont, "ssiii");
COMMAND(fontborder, "ff");
COMMAND(fontoutline, "ff");
COMMAND(fontoffset, "s");
COMMAND(fontscale, "i");
COMMAND(fonttex, "s");
COMMAND(fontchar, "fffffff");
COMMAND(fontskip, "i");
static void fontalias(const char *dst, const char *src)
{
font *s = fonts.access(src);
if(!s) return;
font *d = &fonts[dst];
if(!d->name) d->name = newstring(dst);
d->texs = s->texs;
d->chars = s->chars;
d->charoffset = s->charoffset;
d->defaultw = s->defaultw;
d->defaulth = s->defaulth;
d->scale = s->scale;
d->bordermin = s->bordermin;
d->bordermax = s->bordermax;
d->outlinemin = s->outlinemin;
d->outlinemax = s->outlinemax;
fontdef = d;
fontdeftex = d->texs.length()-1;
}
COMMAND(fontalias, "ss");
# if 0
static font *findfont(const char *name)
{
return fonts.access(name);
}
#endif
bool setfont(const char *name)
{
font *f = fonts.access(name);
if(!f) return false;
curfont = f;
return true;
}
static vector<font *> fontstack;
void pushfont()
{
fontstack.add(curfont);
}
bool popfont()
{
if(fontstack.empty()) return false;
curfont = fontstack.pop();
return true;
}
void gettextres(int &w, int &h)
{
if(w < MINRESW || h < MINRESH)
{
if(MINRESW > w*MINRESH/h)
{
h = h*MINRESW/w;
w = MINRESW;
}
else
{
w = w*MINRESH/h;
h = MINRESH;
}
}
}
float text_widthf(const char *str)
{
float width, height;
text_boundsf(str, width, height);
return width;
}
#define FONTTAB (4*FONTW)
#define TEXTTAB(x) ((int((x)/FONTTAB)+1.0f)*FONTTAB)
static void tabify(const char *str, int *numtabs)
{
int tw = max(*numtabs, 0)*FONTTAB-1, tabs = 0;
for(float w = text_widthf(str); w <= tw; w = TEXTTAB(w)) ++tabs;
int len = strlen(str);
char *tstr = newstring(len + tabs);
memcpy(tstr, str, len);
memset(&tstr[len], '\t', tabs);
tstr[len+tabs] = '\0';
stringret(tstr);
}
COMMAND(tabify, "si");
void draw_textf(const char *fstr, float left, float top, ...)
{
defvformatstring(str, top, fstr);
draw_text(str, left, top);
}
const matrix4x3 *textmatrix = nullptr;
float textscale = 1;
static float draw_char(Texture *&tex, int c, float x, float y, float scale)
{
font::charinfo &info = curfont->chars[c-curfont->charoffset];
if(tex != curfont->texs[info.tex])
{
xtraverts += gle::end();
tex = curfont->texs[info.tex];
glBindTexture(GL_TEXTURE_2D, tex->id);
}
x *= textscale;
y *= textscale;
scale *= textscale;
float x1 = x + scale*info.offsetx,
y1 = y + scale*info.offsety,
x2 = x + scale*(info.offsetx + info.w),
y2 = y + scale*(info.offsety + info.h),
tx1 = info.x / tex->xs,
ty1 = info.y / tex->ys,
tx2 = (info.x + info.w) / tex->xs,
ty2 = (info.y + info.h) / tex->ys;
if(textmatrix)
{
gle::attrib(textmatrix->transform(vec2(x1, y1))); gle::attribf(tx1, ty1);
gle::attrib(textmatrix->transform(vec2(x2, y1))); gle::attribf(tx2, ty1);
gle::attrib(textmatrix->transform(vec2(x2, y2))); gle::attribf(tx2, ty2);
gle::attrib(textmatrix->transform(vec2(x1, y2))); gle::attribf(tx1, ty2);
}
else
{
gle::attribf(x1, y1); gle::attribf(tx1, ty1);
gle::attribf(x2, y1); gle::attribf(tx2, ty1);
gle::attribf(x2, y2); gle::attribf(tx2, ty2);
gle::attribf(x1, y2); gle::attribf(tx1, ty2);
}
return scale*info.advance;
}
VARP(textbright, 0, 85, 100);
//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
{
xtraverts += gle::end();
if(c=='r') { if(sp > 0) --sp; c = stack[sp]; } // restore color
else stack[sp] = c;
switch(c)
{
case '0': color = bvec( 64, 255, 128); break; // green: player talk
case '1': color = bvec( 96, 160, 255); break; // blue: "echo" command
case '2': color = bvec(255, 192, 64); break; // yellow: gameplay messages
case '3': color = bvec(255, 64, 64); break; // red: important errors
case '4': color = bvec(128, 128, 128); break; // gray
case '5': color = bvec(192, 64, 192); break; // magenta
case '6': color = bvec(255, 128, 0); break; // orange
case '7': color = bvec(255, 255, 255); break; // white
case '8': color = bvec( 80, 207, 229); break; // "Tesseract Blue"
case '9': color = bvec(160, 240, 120); break;
default: gle::color(color, a); return; // provided color: everything else
}
if(textbright != 100) color.scale(textbright, 100);
gle::color(color, a);
}
}
#define TEXTSKELETON \
float y = 0, x = 0, scale = curfont->scale/float(curfont->defaulth);\
int i;\
for(i = 0; str[i]; i++)\
{\
TEXTINDEX(i)\
int c = uchar(str[i]);\
if(c=='\t') { x = TEXTTAB(x); TEXTWHITE(i) }\
else if(c==' ') { x += scale*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(curfont->chars.inrange(c-curfont->charoffset))\
{\
float cw = scale*curfont->chars[c-curfont->charoffset].advance;\
if(cw <= 0) continue;\
if(maxwidth >= 0)\
{\
int j = i;\
float w = cw;\
for(; str[i+1]; i++)\
{\
int c = uchar(str[i+1]);\
if(c=='\f') { if(str[i+2]) i++; continue; }\
if(!curfont->chars.inrange(c-curfont->charoffset)) break;\
float cw = scale*curfont->chars[c-curfont->charoffset].advance;\
if(cw <= 0 || w + cw > maxwidth) break;\
w += cw;\
}\
if(x + w > maxwidth && x > 0) { (void)j; 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 = uchar(str[j]);\
if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\
else { float cw = scale*curfont->chars[c-curfont->charoffset].advance; TEXTCHAR(j) }\
}
#define TEXTEND(cursor) if(cursor >= i) { do { TEXTINDEX(cursor); } while(0); }
#if 0
static int text_visible(const char *str, float hitx, float hity, int maxwidth)
{
#define TEXTINDEX(idx)
#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 += cw; TEXTWHITE(idx)
#define TEXTWORD TEXTWORDSKELETON
TEXTSKELETON
#undef TEXTINDEX
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTCHAR
#undef TEXTWORD
return i;
}
#endif
//inverse of text_visible
void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth)
{
#define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; }
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx)
#define TEXTCHAR(idx) x += cw;
#define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
cx = cy = 0;
TEXTSKELETON
TEXTEND(cursor)
#undef TEXTINDEX
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTCHAR
#undef TEXTWORD
}
void text_boundsf(const char *str, float &width, float &height, int maxwidth)
{
#define TEXTINDEX(idx)
#define TEXTWHITE(idx)
#define TEXTLINE(idx) if(x > width) width = x;
#define TEXTCOLOR(idx)
#define TEXTCHAR(idx) x += cw;
#define TEXTWORD x += w;
width = 0;
TEXTSKELETON
height = y + FONTH;
TEXTLINE(_)
#undef TEXTINDEX
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTCHAR
#undef TEXTWORD
}
Shader *textshader = nullptr;
void draw_text(const char *str, float left, float top, int r, int g, int b, int a, int cursor, int maxwidth)
{
#define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; }
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx) if(usecolor) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a);
#define TEXTCHAR(idx) draw_char(tex, c, left+x, top+y, scale); x += cw;
#define TEXTWORD TEXTWORDSKELETON
char colorstack[10];
colorstack[0] = '\0'; //indicate user color
bvec color(r, g, b);
if(textbright != 100) color.scale(textbright, 100);
int colorpos = 0;
float cx = -FONTW, cy = 0;
bool usecolor = true;
if(a < 0) { usecolor = false; a = -a; }
Texture *tex = curfont->texs[0];
(textshader ? textshader : hudtextshader)->set();
LOCALPARAMF(textparams, curfont->bordermin, curfont->bordermax, curfont->outlinemin, curfont->outlinemax);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, tex->id);
gle::color(color, a);
gle::defvertex(textmatrix ? 3 : 2);
gle::deftexcoord0();
gle::begin(GL_QUADS);
TEXTSKELETON
TEXTEND(cursor)
xtraverts += gle::end();
if(cursor >= 0 && (totalmillis/250)&1)
{
gle::color(color, a);
if(maxwidth >= 0 && cx >= maxwidth && cx > 0) { cx = 0; cy += FONTH; }
draw_char(tex, '_', left+cx, top+cy, scale);
xtraverts += gle::end();
}
#undef TEXTINDEX
#undef TEXTWHITE
#undef TEXTLINE
#undef TEXTCOLOR
#undef TEXTCHAR
#undef TEXTWORD
}
void reloadfonts()
{
enumerate(fonts, font, f,
loopv(f.texs) if(!reloadtexture(*f.texs[i])) fatal("failed to reload font texture");
);
}