1 #include "engine.h"
2 
3 static hashnameset<font> fonts;
4 static font *fontdef = NULL;
5 static int fontdeftex = 0;
6 
7 font *curfont = NULL;
8 int curfonttex = 0;
9 
newfont(char * name,char * tex,int * defaultw,int * defaulth,int * scale)10 void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *scale)
11 {
12     font *f = &fonts[name];
13     if(!f->name) f->name = newstring(name);
14     f->texs.shrink(0);
15     f->texs.add(textureload(tex));
16     f->chars.shrink(0);
17     f->charoffset = '!';
18     f->defaultw = *defaultw;
19     f->defaulth = *defaulth;
20     f->scale = *scale > 0 ? *scale : f->defaulth;
21     f->bordermin = 0.49f;
22     f->bordermax = 0.5f;
23     f->outlinemin = -1;
24     f->outlinemax = 0;
25 
26     fontdef = f;
27     fontdeftex = 0;
28 }
29 
fontborder(float * bordermin,float * bordermax)30 void fontborder(float *bordermin, float *bordermax)
31 {
32     if(!fontdef) return;
33 
34     fontdef->bordermin = *bordermin;
35     fontdef->bordermax = max(*bordermax, *bordermin+0.01f);
36 }
37 
fontoutline(float * outlinemin,float * outlinemax)38 void fontoutline(float *outlinemin, float *outlinemax)
39 {
40     if(!fontdef) return;
41 
42     fontdef->outlinemin = min(*outlinemin, *outlinemax-0.01f);
43     fontdef->outlinemax = *outlinemax;
44 }
45 
fontoffset(char * c)46 void fontoffset(char *c)
47 {
48     if(!fontdef) return;
49 
50     fontdef->charoffset = c[0];
51 }
52 
fontscale(int * scale)53 void fontscale(int *scale)
54 {
55     if(!fontdef) return;
56 
57     fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth;
58 }
59 
fonttex(char * s)60 void fonttex(char *s)
61 {
62     if(!fontdef) return;
63 
64     Texture *t = textureload(s);
65     loopv(fontdef->texs) if(fontdef->texs[i] == t) { fontdeftex = i; return; }
66     fontdeftex = fontdef->texs.length();
67     fontdef->texs.add(t);
68 }
69 
fontchar(float * x,float * y,float * w,float * h,float * offsetx,float * offsety,float * advance)70 void fontchar(float *x, float *y, float *w, float *h, float *offsetx, float *offsety, float *advance)
71 {
72     if(!fontdef) return;
73 
74     font::charinfo &c = fontdef->chars.add();
75     c.x = *x;
76     c.y = *y;
77     c.w = *w ? *w : fontdef->defaultw;
78     c.h = *h ? *h : fontdef->defaulth;
79     c.offsetx = *offsetx;
80     c.offsety = *offsety;
81     c.advance = *advance ? *advance : c.offsetx + c.w;
82     c.tex = fontdeftex;
83 }
84 
fontskip(int * n)85 void fontskip(int *n)
86 {
87     if(!fontdef) return;
88     loopi(max(*n, 1))
89     {
90         font::charinfo &c = fontdef->chars.add();
91         c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = 0;
92         c.tex = 0;
93     }
94 }
95 
96 COMMANDN(font, newfont, "ssiii");
97 COMMAND(fontborder, "ff");
98 COMMAND(fontoutline, "ff");
99 COMMAND(fontoffset, "s");
100 COMMAND(fontscale, "i");
101 COMMAND(fonttex, "s");
102 COMMAND(fontchar, "fffffff");
103 COMMAND(fontskip, "i");
104 
fontalias(const char * dst,const char * src)105 void fontalias(const char *dst, const char *src)
106 {
107     font *s = fonts.access(src);
108     if(!s) return;
109     font *d = &fonts[dst];
110     if(!d->name) d->name = newstring(dst);
111     d->texs = s->texs;
112     d->chars = s->chars;
113     d->charoffset = s->charoffset;
114     d->defaultw = s->defaultw;
115     d->defaulth = s->defaulth;
116     d->scale = s->scale;
117     d->bordermin = s->bordermin;
118     d->bordermax = s->bordermax;
119     d->outlinemin = s->outlinemin;
120     d->outlinemax = s->outlinemax;
121 
122     fontdef = d;
123     fontdeftex = d->texs.length()-1;
124 }
125 
126 COMMAND(fontalias, "ss");
127 
findfont(const char * name)128 font *findfont(const char *name)
129 {
130     return fonts.access(name);
131 }
132 
setfont(const char * name)133 bool setfont(const char *name)
134 {
135     font *f = fonts.access(name);
136     if(!f) return false;
137     curfont = f;
138     return true;
139 }
140 
141 static vector<font *> fontstack;
142 
pushfont()143 void pushfont()
144 {
145     fontstack.add(curfont);
146 }
147 
popfont()148 bool popfont()
149 {
150     if(fontstack.empty()) return false;
151     curfont = fontstack.pop();
152     return true;
153 }
154 
gettextres(int & w,int & h)155 void gettextres(int &w, int &h)
156 {
157     if(w < MINRESW || h < MINRESH)
158     {
159         if(MINRESW > w*MINRESH/h)
160         {
161             h = h*MINRESW/w;
162             w = MINRESW;
163         }
164         else
165         {
166             w = w*MINRESH/h;
167             h = MINRESH;
168         }
169     }
170 }
171 
text_widthf(const char * str)172 float text_widthf(const char *str)
173 {
174     float width, height;
175     text_boundsf(str, width, height);
176     return width;
177 }
178 
179 #define FONTTAB (4*FONTW)
180 #define TEXTTAB(x) ((int((x)/FONTTAB)+1.0f)*FONTTAB)
181 
tabify(const char * str,int * numtabs)182 void tabify(const char *str, int *numtabs)
183 {
184     int tw = max(*numtabs, 0)*FONTTAB-1, tabs = 0;
185     for(float w = text_widthf(str); w <= tw; w = TEXTTAB(w)) ++tabs;
186     int len = strlen(str);
187     char *tstr = newstring(len + tabs);
188     memcpy(tstr, str, len);
189     memset(&tstr[len], '\t', tabs);
190     tstr[len+tabs] = '\0';
191     stringret(tstr);
192 }
193 
194 COMMAND(tabify, "si");
195 
draw_textf(const char * fstr,float left,float top,...)196 void draw_textf(const char *fstr, float left, float top, ...)
197 {
198     defvformatstring(str, top, fstr);
199     draw_text(str, left, top);
200 }
201 
202 const matrix4x3 *textmatrix = NULL;
203 float textscale = 1;
204 
draw_char(Texture * & tex,int c,float x,float y,float scale)205 static float draw_char(Texture *&tex, int c, float x, float y, float scale)
206 {
207     font::charinfo &info = curfont->chars[c-curfont->charoffset];
208     if(tex != curfont->texs[info.tex])
209     {
210         xtraverts += gle::end();
211         tex = curfont->texs[info.tex];
212         glBindTexture(GL_TEXTURE_2D, tex->id);
213     }
214 
215     x *= textscale;
216     y *= textscale;
217     scale *= textscale;
218 
219     float x1 = x + scale*info.offsetx,
220           y1 = y + scale*info.offsety,
221           x2 = x + scale*(info.offsetx + info.w),
222           y2 = y + scale*(info.offsety + info.h),
223           tx1 = info.x / tex->xs,
224           ty1 = info.y / tex->ys,
225           tx2 = (info.x + info.w) / tex->xs,
226           ty2 = (info.y + info.h) / tex->ys;
227 
228     if(textmatrix)
229     {
230         gle::attrib(textmatrix->transform(vec2(x1, y1))); gle::attribf(tx1, ty1);
231         gle::attrib(textmatrix->transform(vec2(x2, y1))); gle::attribf(tx2, ty1);
232         gle::attrib(textmatrix->transform(vec2(x2, y2))); gle::attribf(tx2, ty2);
233         gle::attrib(textmatrix->transform(vec2(x1, y2))); gle::attribf(tx1, ty2);
234     }
235     else
236     {
237         gle::attribf(x1, y1); gle::attribf(tx1, ty1);
238         gle::attribf(x2, y1); gle::attribf(tx2, ty1);
239         gle::attribf(x2, y2); gle::attribf(tx2, ty2);
240         gle::attribf(x1, y2); gle::attribf(tx1, ty2);
241     }
242 
243     return scale*info.advance;
244 }
245 
246 VARP(textbright, 0, 85, 100);
247 
248 //stack[sp] is current color index
text_color(char c,char * stack,int size,int & sp,bvec color,int a)249 static void text_color(char c, char *stack, int size, int &sp, bvec color, int a)
250 {
251     if(c=='s') // save color
252     {
253         c = stack[sp];
254         if(sp<size-1) stack[++sp] = c;
255     }
256     else
257     {
258         xtraverts += gle::end();
259         if(c=='r') c = stack[(sp > 0) ? --sp : sp]; // restore color
260         else stack[sp] = c;
261         switch(c)
262         {
263             case '0': color = bvec( 64, 255, 128); break;   // green: player talk
264             case '1': color = bvec( 96, 160, 255); break;   // blue: "echo" command
265             case '2': color = bvec(255, 192,  64); break;   // yellow: gameplay messages
266             case '3': color = bvec(255,  64,  64); break;   // red: important errors
267             case '4': color = bvec(128, 128, 128); break;   // gray
268             case '5': color = bvec(192,  64, 192); break;   // magenta
269             case '6': color = bvec(255, 128,   0); break;   // orange
270             case '7': color = bvec(255, 255, 255); break;   // white
271             case '8': color = bvec( 80, 207, 229); break;   // "Tesseract Blue"
272             case '9': color = bvec(160, 240, 120); break;
273             default: gle::color(color, a); return;          // provided color: everything else
274         }
275         if(textbright != 100) color.scale(textbright, 100);
276         gle::color(color, a);
277     }
278 }
279 
280 #define TEXTSKELETON \
281     float y = 0, x = 0, scale = curfont->scale/float(curfont->defaulth);\
282     int i;\
283     for(i = 0; str[i]; i++)\
284     {\
285         TEXTINDEX(i)\
286         int c = uchar(str[i]);\
287         if(c=='\t')      { x = TEXTTAB(x); TEXTWHITE(i) }\
288         else if(c==' ')  { x += scale*curfont->defaultw; TEXTWHITE(i) }\
289         else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\
290         else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\
291         else if(curfont->chars.inrange(c-curfont->charoffset))\
292         {\
293             float cw = scale*curfont->chars[c-curfont->charoffset].advance;\
294             if(cw <= 0) continue;\
295             if(maxwidth >= 0)\
296             {\
297                 int j = i;\
298                 float w = cw;\
299                 for(; str[i+1]; i++)\
300                 {\
301                     int c = uchar(str[i+1]);\
302                     if(c=='\f') { if(str[i+2]) i++; continue; }\
303                     if(!curfont->chars.inrange(c-curfont->charoffset)) break;\
304                     float cw = scale*curfont->chars[c-curfont->charoffset].advance;\
305                     if(cw <= 0 || w + cw > maxwidth) break;\
306                     w += cw;\
307                 }\
308                 if(x + w > maxwidth && x > 0) { (void)j; TEXTLINE(j-1) x = 0; y += FONTH; }\
309                 TEXTWORD\
310             }\
311             else { TEXTCHAR(i) }\
312         }\
313     }
314 
315 //all the chars are guaranteed to be either drawable or color commands
316 #define TEXTWORDSKELETON \
317                 for(; j <= i; j++)\
318                 {\
319                     TEXTINDEX(j)\
320                     int c = uchar(str[j]);\
321                     if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\
322                     else { float cw = scale*curfont->chars[c-curfont->charoffset].advance; TEXTCHAR(j) }\
323                 }
324 
325 #define TEXTEND(cursor) if(cursor >= i) { do { TEXTINDEX(cursor); } while(0); }
326 
text_visible(const char * str,float hitx,float hity,int maxwidth)327 int text_visible(const char *str, float hitx, float hity, int maxwidth)
328 {
329     #define TEXTINDEX(idx)
330     #define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx;
331     #define TEXTLINE(idx) if(y+FONTH > hity) return idx;
332     #define TEXTCOLOR(idx)
333     #define TEXTCHAR(idx) x += cw; TEXTWHITE(idx)
334     #define TEXTWORD TEXTWORDSKELETON
335     TEXTSKELETON
336     #undef TEXTINDEX
337     #undef TEXTWHITE
338     #undef TEXTLINE
339     #undef TEXTCOLOR
340     #undef TEXTCHAR
341     #undef TEXTWORD
342     return i;
343 }
344 
345 //inverse of text_visible
text_posf(const char * str,int cursor,float & cx,float & cy,int maxwidth)346 void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth)
347 {
348     #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; }
349     #define TEXTWHITE(idx)
350     #define TEXTLINE(idx)
351     #define TEXTCOLOR(idx)
352     #define TEXTCHAR(idx) x += cw;
353     #define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
354     cx = cy = 0;
355     TEXTSKELETON
356     TEXTEND(cursor)
357     #undef TEXTINDEX
358     #undef TEXTWHITE
359     #undef TEXTLINE
360     #undef TEXTCOLOR
361     #undef TEXTCHAR
362     #undef TEXTWORD
363 }
364 
text_boundsf(const char * str,float & width,float & height,int maxwidth)365 void text_boundsf(const char *str, float &width, float &height, int maxwidth)
366 {
367     #define TEXTINDEX(idx)
368     #define TEXTWHITE(idx)
369     #define TEXTLINE(idx) if(x > width) width = x;
370     #define TEXTCOLOR(idx)
371     #define TEXTCHAR(idx) x += cw;
372     #define TEXTWORD x += w;
373     width = 0;
374     TEXTSKELETON
375     height = y + FONTH;
376     TEXTLINE(_)
377     #undef TEXTINDEX
378     #undef TEXTWHITE
379     #undef TEXTLINE
380     #undef TEXTCOLOR
381     #undef TEXTCHAR
382     #undef TEXTWORD
383 }
384 
385 Shader *textshader = NULL;
386 
draw_text(const char * str,float left,float top,int r,int g,int b,int a,int cursor,int maxwidth)387 void draw_text(const char *str, float left, float top, int r, int g, int b, int a, int cursor, int maxwidth)
388 {
389     #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; }
390     #define TEXTWHITE(idx)
391     #define TEXTLINE(idx)
392     #define TEXTCOLOR(idx) if(usecolor) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a);
393     #define TEXTCHAR(idx) draw_char(tex, c, left+x, top+y, scale); x += cw;
394     #define TEXTWORD TEXTWORDSKELETON
395     char colorstack[10];
396     colorstack[0] = '\0'; //indicate user color
397     bvec color(r, g, b);
398     if(textbright != 100) color.scale(textbright, 100);
399     int colorpos = 0;
400     float cx = -FONTW, cy = 0;
401     bool usecolor = true;
402     if(a < 0) { usecolor = false; a = -a; }
403     Texture *tex = curfont->texs[0];
404     Shader *oldshader = Shader::lastshader;
405     (textshader ? textshader : hudtextshader)->set();
406     LOCALPARAMF(textparams, curfont->bordermin, curfont->bordermax, curfont->outlinemin, curfont->outlinemax);
407     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
408     glBindTexture(GL_TEXTURE_2D, tex->id);
409     gle::color(color, a);
410     gle::defvertex(textmatrix ? 3 : 2);
411     gle::deftexcoord0();
412     gle::begin(GL_QUADS);
413     TEXTSKELETON
414     TEXTEND(cursor)
415     xtraverts += gle::end();
416     if(cursor >= 0 && (totalmillis/250)&1)
417     {
418         gle::color(color, a);
419         if(maxwidth >= 0 && cx >= maxwidth && cx > 0) { cx = 0; cy += FONTH; }
420         draw_char(tex, '_', left+cx, top+cy, scale);
421         xtraverts += gle::end();
422     }
423     gle::disable();
424     if(oldshader == hudshader)
425     {
426         oldshader->bindprograms();
427         gle::colorf(1, 1, 1);
428     }
429     #undef TEXTINDEX
430     #undef TEXTWHITE
431     #undef TEXTLINE
432     #undef TEXTCOLOR
433     #undef TEXTCHAR
434     #undef TEXTWORD
435 }
436 
reloadfonts()437 void reloadfonts()
438 {
439     enumerate(fonts, font, f,
440         loopv(f.texs) if(!reloadtexture(*f.texs[i])) fatal("failed to reload font texture");
441     );
442 }
443 
444