Line data Source code
1 : /* rendertext.cpp: core text functionality for console/ui rendering
2 : *
3 : * libprimis uses a font image generated by the `tessfont` utility which has
4 : * letters sampled from it by reading small sub-rectangles of the overall font
5 : * image
6 : *
7 : * rendertext supports loading, recoloring, resizing, and rendering text to the
8 : * screen from these font images, which contain raster images of the font to be
9 : * rendered
10 : *
11 : * only a single font can be used at a time, though it can be scaled as needed
12 : * for various different purposes in UIs (e.g. titles vs body text)
13 : *
14 : * fonts are generated by the external `tessfont` utility and consist of a
15 : * raster image containing the acceptable characters which are sampled as needed
16 : */
17 : #include "../libprimis-headers/cube.h"
18 : #include "../../shared/geomexts.h"
19 : #include "../../shared/glemu.h"
20 : #include "../../shared/glexts.h"
21 :
22 : #include "rendergl.h"
23 : #include "rendertext.h"
24 : #include "shaderparam.h"
25 : #include "texture.h"
26 :
27 : #include "interface/control.h"
28 :
29 : static std::unordered_map<std::string, font> fonts;
30 : static font *fontdef = nullptr;
31 : static int fontdeftex = 0;
32 :
33 : font *curfont = nullptr;
34 :
35 : //adds a new font to the hashnameset "fonts" given the parameters passed
36 2 : static void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *scale)
37 : {
38 2 : auto insert = fonts.insert( {name, font()} ).first;
39 2 : font *f = &((*insert).second);
40 2 : f->name = std::string(name);
41 2 : f->texs.clear();
42 2 : f->texs.push_back(textureload(tex));
43 2 : f->chars.clear();
44 2 : f->charoffset = '!';
45 2 : f->defaultw = *defaultw;
46 2 : f->defaulth = *defaulth;
47 2 : f->scale = *scale > 0 ? *scale : f->defaulth;
48 2 : f->bordermin = 0.49f;
49 2 : f->bordermax = 0.5f;
50 2 : f->outlinemin = -1;
51 2 : f->outlinemax = 0;
52 :
53 2 : fontdef = f;
54 2 : fontdeftex = 0;
55 2 : }
56 :
57 : //sets the fontdef gvar's bordermin/max to the values passed
58 1 : static void fontborder(float *bordermin, float *bordermax)
59 : {
60 1 : if(!fontdef)
61 : {
62 0 : return;
63 : }
64 1 : fontdef->bordermin = *bordermin;
65 1 : fontdef->bordermax = std::max(*bordermax, *bordermin+0.01f);
66 : }
67 :
68 : //sets the fontdef gvar's outlinemin/max to the values passed
69 1 : static void fontoutline(float *outlinemin, float *outlinemax)
70 : {
71 1 : if(!fontdef)
72 : {
73 0 : return;
74 : }
75 1 : fontdef->outlinemin = std::min(*outlinemin, *outlinemax-0.01f);
76 1 : fontdef->outlinemax = *outlinemax;
77 : }
78 :
79 : /* fontoffset
80 : * sets the character offset for the currently loaded font
81 : *
82 : * Arguments:
83 : * c: a pointer to a char * array representing the character to be offset to
84 : *
85 : * Only the first element of the array is accepted, all others are ignored
86 : *
87 : */
88 1 : static void fontoffset(char *c)
89 : {
90 1 : if(!fontdef)
91 : {
92 0 : return;
93 : }
94 1 : fontdef->charoffset = c[0];
95 : }
96 :
97 : /* fontscale
98 : * sets the global scale for fonts
99 : *
100 : * Arguments:
101 : * scale: a pointer to an integer representing the new scale to set the font to
102 : *
103 : * If the scale parameter points to the value 0, the font scale is et to its default value.
104 : *
105 : */
106 1 : static void fontscale(int *scale)
107 : {
108 1 : if(!fontdef)
109 : {
110 0 : return;
111 : }
112 :
113 1 : fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth;
114 : }
115 :
116 :
117 : /* fonttex
118 : * adds a texture for fonts to be loaded from
119 : *
120 : * Arguments:
121 : * s: a pointer to a char * array representing the path to a font texture file
122 : */
123 1 : static void fonttex(char *s)
124 : {
125 1 : if(!fontdef)
126 : {
127 1 : return;
128 : }
129 1 : Texture *t = textureload(s);
130 1 : for(uint i = 0; i < fontdef->texs.size(); i++)
131 : {
132 1 : if(fontdef->texs[i] == t)
133 : {
134 1 : fontdeftex = i;
135 1 : return;
136 : }
137 : }
138 0 : fontdeftex = fontdef->texs.size();
139 0 : fontdef->texs.push_back(t);
140 : }
141 :
142 : /* fontchar
143 : * adds an entry to the fontdef vector
144 : * sets the new entry in the vector to have the parameters passed
145 : *
146 : */
147 1 : static void fontchar(float *x, float *y, float *w, float *h, float *offsetx, float *offsety, float *advance)
148 : {
149 1 : if(!fontdef)
150 : {
151 0 : return;
152 : }
153 1 : fontdef->chars.emplace_back();
154 1 : font::charinfo &c = fontdef->chars.back();
155 1 : c.x = *x;
156 1 : c.y = *y;
157 1 : c.w = *w ? *w : fontdef->defaultw;
158 1 : c.h = *h ? *h : fontdef->defaulth;
159 1 : c.offsetx = *offsetx;
160 1 : c.offsety = *offsety;
161 1 : c.advance = *advance ? *advance : c.offsetx + c.w;
162 1 : c.tex = fontdeftex;
163 : }
164 :
165 : /* fontskip
166 : * addes an entry to the fontdef vector, which is empty
167 : */
168 1 : static void fontskip(int *n)
169 : {
170 1 : if(!fontdef)
171 : {
172 0 : return;
173 : }
174 2 : for(int i = 0; i < std::max(*n, 1); ++i)
175 : {
176 1 : fontdef->chars.emplace_back();
177 1 : font::charinfo &c = fontdef->chars.back();
178 1 : c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = 0;
179 1 : c.tex = 0;
180 : }
181 : }
182 :
183 1 : bool setfont(const char *name)
184 : {
185 1 : auto itr = fonts.find(name);
186 1 : if(itr == fonts.end())
187 : {
188 0 : return false;
189 : }
190 1 : curfont = &(*itr).second;
191 1 : return true;
192 : }
193 :
194 : static std::stack<font *> fontstack;
195 :
196 0 : void pushfont()
197 : {
198 0 : fontstack.push(curfont);
199 0 : }
200 :
201 0 : bool popfont()
202 : {
203 0 : if(fontstack.empty())
204 : {
205 0 : return false;
206 : }
207 0 : curfont = fontstack.top();
208 0 : fontstack.pop();
209 0 : return true;
210 : }
211 :
212 0 : void gettextres(int &w, int &h)
213 : {
214 0 : if(w < minreswidth || h < minresheight)
215 : {
216 0 : if(minreswidth > w*minresheight/h)
217 : {
218 0 : h = h*minreswidth/w;
219 0 : w = minreswidth;
220 : }
221 : else
222 : {
223 0 : w = w*minresheight/h;
224 0 : h = minresheight;
225 : }
226 : }
227 0 : }
228 :
229 0 : float text_widthf(const char *str)
230 : {
231 : float width, height;
232 0 : text_boundsf(str, width, height);
233 0 : return width;
234 : }
235 :
236 0 : static int texttab(float x)
237 : {
238 0 : return (static_cast<int>((x)/(4*fontwidth()))+1.0f)*(4*fontwidth());
239 : }
240 :
241 : float textscale = 1;
242 :
243 : #define TEXTSKELETON \
244 : float y = 0, \
245 : x = 0, \
246 : scale = curfont->scale/static_cast<float>(curfont->defaulth);\
247 : int i;\
248 : for(i = 0; str[i]; i++)\
249 : {\
250 : TEXTINDEX(i) /*textindex *must* be defined before runtime, it is not defined above here*/ \
251 : int c = static_cast<uchar>(str[i]);\
252 : if(c=='\t')\
253 : {\
254 : x = texttab(x);\
255 : TEXTWHITE(i)\
256 : }\
257 : else if(c==' ') \
258 : { \
259 : x += scale*curfont->defaultw; \
260 : TEXTWHITE(i) /*textwhite *must* be defined before runtime, it is not defined above here*/ \
261 : }\
262 : else if(c=='\n') \
263 : { \
264 : TEXTLINE(i) x = 0; \
265 : y += FONTH; \
266 : }\
267 : else if(c=='\f') \
268 : { \
269 : if(str[i+1]) \
270 : { \
271 : i++; \
272 : TEXTCOLOR(i) /*textcolor *must* be defined before runtime, it is not defined above here*/ \
273 : } \
274 : }\
275 : else if(curfont->chars.size() > static_cast<uint>(c-curfont->charoffset))\
276 : {\
277 : float cw = scale*curfont->chars[c-curfont->charoffset].advance;\
278 : if(cw <= 0) \
279 : { \
280 : continue; \
281 : } \
282 : if(maxwidth >= 0)\
283 : {\
284 : int j = i;\
285 : float w = cw;\
286 : for(; str[i+1]; i++)\
287 : {\
288 : int c = static_cast<uchar>(str[i+1]);\
289 : if(c=='\f') \
290 : { \
291 : if(str[i+2]) \
292 : { \
293 : i++; \
294 : } \
295 : continue; \
296 : } \
297 : if(!(curfont->chars.size() > static_cast<uint>(c-curfont->charoffset))) \
298 : { \
299 : break; \
300 : } \
301 : float cw = scale*curfont->chars[c-curfont->charoffset].advance; \
302 : if(cw <= 0 || w + cw > maxwidth) \
303 : { \
304 : break; \
305 : } \
306 : w += cw; \
307 : } \
308 : if(x + w > maxwidth && x > 0) \
309 : { \
310 : static_cast<void>(j); \
311 : TEXTLINE(j-1); \
312 : x = 0; \
313 : y += FONTH; } \
314 : TEXTWORD \
315 : } \
316 : else \
317 : { \
318 : TEXTCHAR(i) \
319 : }\
320 : }\
321 : }
322 :
323 : //all the chars are guaranteed to be either drawable or color commands
324 : #define TEXTWORDSKELETON \
325 : for(; j <= i; j++)\
326 : {\
327 : TEXTINDEX(j) /*textindex *must* be defined before runtime, it is not defined above here*/ \
328 : int c = static_cast<uchar>(str[j]);\
329 : if(c=='\f') \
330 : { \
331 : if(str[j+1]) \
332 : { \
333 : j++; \
334 : TEXTCOLOR(j) /*textcolor *must* be defined before runtime, it is not defined above here*/ \
335 : } \
336 : }\
337 : else \
338 : { \
339 : float cw = scale*curfont->chars[c-curfont->charoffset].advance; \
340 : TEXTCHAR(j); \
341 : }\
342 : }
343 :
344 : #define TEXTEND(cursor) \
345 : if(cursor >= i) \
346 : { \
347 : do \
348 : { \
349 : TEXTINDEX(cursor); /*textindex *must* be defined before runtime, it is not defined above here*/ \
350 : } while(0); \
351 : } \
352 :
353 0 : int text_visible(const char *str, float hitx, float hity, int maxwidth)
354 : {
355 : #define TEXTINDEX(idx)
356 : #define TEXTWHITE(idx) \
357 : { \
358 : if(y+FONTH > hity && x >= hitx) \
359 : { \
360 : return idx; \
361 : } \
362 : }
363 : #define TEXTLINE(idx) \
364 : { \
365 : if(y+FONTH > hity) \
366 : { \
367 : return idx; \
368 : } \
369 : }
370 : #define TEXTCOLOR(idx)
371 : #define TEXTCHAR(idx) \
372 : { \
373 : x += cw; \
374 : TEXTWHITE(idx) \
375 : }
376 : #define TEXTWORD TEXTWORDSKELETON
377 0 : TEXTSKELETON
378 : #undef TEXTINDEX
379 : #undef TEXTWHITE
380 : #undef TEXTLINE
381 : #undef TEXTCOLOR
382 : #undef TEXTCHAR
383 : #undef TEXTWORD
384 0 : return i;
385 : }
386 :
387 : //inverse of text_visible
388 0 : void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth)
389 : {
390 : #define TEXTINDEX(idx) \
391 : { \
392 : if(idx == cursor) \
393 : { \
394 : cx = x; \
395 : cy = y; \
396 : break; \
397 : } \
398 : }
399 : #define TEXTWHITE(idx)
400 : #define TEXTLINE(idx)
401 : #define TEXTCOLOR(idx)
402 : #define TEXTCHAR(idx) x += cw;
403 : #define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
404 0 : cx = cy = 0;
405 0 : TEXTSKELETON
406 0 : TEXTEND(cursor)
407 : #undef TEXTINDEX
408 : #undef TEXTWHITE
409 : #undef TEXTLINE
410 : #undef TEXTCOLOR
411 : #undef TEXTCHAR
412 : #undef TEXTWORD
413 0 : }
414 :
415 0 : void text_boundsf(const char *str, float &width, float &height, int maxwidth)
416 : {
417 : #define TEXTINDEX(idx)
418 : #define TEXTWHITE(idx)
419 : #define TEXTLINE(idx) if(x > width) width = x;
420 : #define TEXTCOLOR(idx)
421 : #define TEXTCHAR(idx) x += cw;
422 : #define TEXTWORD x += w;
423 0 : width = 0;
424 0 : TEXTSKELETON
425 0 : height = y + FONTH;
426 0 : TEXTLINE(_)
427 : #undef TEXTINDEX
428 : #undef TEXTWHITE
429 : #undef TEXTLINE
430 : #undef TEXTCOLOR
431 : #undef TEXTCHAR
432 : #undef TEXTWORD
433 0 : }
434 :
435 0 : void reloadfonts()
436 : {
437 0 : for(auto &[k, f] : fonts)
438 : {
439 0 : for(uint i = 0; i < f.texs.size(); i++)
440 : {
441 0 : if(!f.texs[i]->reload())
442 : {
443 0 : fatal("failed to reload font texture");
444 : }
445 : }
446 : }
447 0 : }
448 :
449 1 : void initrendertextcmds()
450 : {
451 1 : addcommand("font", reinterpret_cast<identfun>(newfont), "ssiii", Id_Command);
452 1 : addcommand("fontborder", reinterpret_cast<identfun>(fontborder), "ff", Id_Command);
453 1 : addcommand("fontoutline", reinterpret_cast<identfun>(fontoutline), "ff", Id_Command);
454 1 : addcommand("fontoffset", reinterpret_cast<identfun>(fontoffset), "s", Id_Command);
455 1 : addcommand("fontscale", reinterpret_cast<identfun>(fontscale), "i", Id_Command);
456 1 : addcommand("fonttex", reinterpret_cast<identfun>(fonttex), "s", Id_Command);
457 1 : addcommand("fontchar", reinterpret_cast<identfun>(fontchar), "fffffff", Id_Command);
458 1 : addcommand("fontskip", reinterpret_cast<identfun>(fontskip), "i", Id_Command);
459 1 : }
|