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