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