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