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 : //sets the fontdef gvar's outlinemin/max to the values passed
78 1 : static void fontoutline(const float *outlinemin, const float *outlinemax)
79 : {
80 1 : if(!fontdef)
81 : {
82 0 : return;
83 : }
84 1 : fontdef->outlinemin = std::min(*outlinemin, *outlinemax-0.01f);
85 1 : fontdef->outlinemax = *outlinemax;
86 : }
87 :
88 : /**
89 : * @brief sets the character offset for the currently loaded font
90 : *
91 : * Only the first element of the array is accepted, all others are ignored
92 : *
93 : * @param c a pointer to a char * array representing the character to be offset to
94 : */
95 1 : static void fontoffset(const char *c)
96 : {
97 1 : if(!fontdef)
98 : {
99 0 : return;
100 : }
101 1 : fontdef->charoffset = c[0];
102 : }
103 :
104 : /**
105 : * @brief sets the global scale for fonts
106 : *
107 : * If the scale parameter points to the value 0, the font scale is et to its default value.
108 : *
109 : * @param scale a pointer to an integer representing the new scale to set the font to
110 : */
111 1 : static void fontscale(int *scale)
112 : {
113 1 : if(!fontdef)
114 : {
115 0 : return;
116 : }
117 :
118 1 : fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth;
119 : }
120 :
121 : /**
122 : * @brief Adds an entry to the fontdef vector
123 : *
124 : * sets the new entry in the vector to have the parameters passed
125 : *
126 : * @param x x size of the charinfo
127 : * @param y y size of the charinfo
128 : * @param w width of the charinfo
129 : * @param h width of the charinfo
130 : * @param offsetx positional offset in x direction
131 : * @param offsety positional offset in y direction
132 : * @param advance x advance, if zero, set to offsetx + width
133 : */
134 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)
135 : {
136 1 : if(!fontdef)
137 : {
138 0 : return;
139 : }
140 1 : fontdef->chars.emplace_back();
141 1 : Font::CharInfo &c = fontdef->chars.back();
142 1 : c.x = *x;
143 1 : c.y = *y;
144 1 : c.w = *w ? *w : fontdef->defaultw;
145 1 : c.h = *h ? *h : fontdef->defaulth;
146 1 : c.offsetx = *offsetx;
147 1 : c.offsety = *offsety;
148 1 : c.advance = *advance ? *advance : c.offsetx + c.w;
149 1 : c.tex = fontdeftex;
150 : }
151 :
152 : /**
153 : * @brief Adds empty entries to fontdef
154 : *
155 : * adds entries to the fontdef vector, which are empty. All of their values are set
156 : * to zero.
157 : *
158 : * @param n pointer to the number of elements to add. At least one element will be added.
159 : */
160 1 : static void fontskip(const int *n)
161 : {
162 1 : if(!fontdef)
163 : {
164 0 : return;
165 : }
166 2 : for(int i = 0; i < std::max(*n, 1); ++i)
167 : {
168 1 : fontdef->chars.emplace_back();
169 1 : Font::CharInfo &c = fontdef->chars.back();
170 1 : c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = 0;
171 1 : c.tex = 0;
172 : }
173 : }
174 :
175 1 : bool setfont(const char *name)
176 : {
177 1 : auto itr = fonts.find(name);
178 1 : if(itr == fonts.end())
179 : {
180 0 : return false;
181 : }
182 1 : curfont = &(*itr).second;
183 1 : return true;
184 : }
185 :
186 0 : void setfont(Font *f)
187 : {
188 0 : if(f)
189 : {
190 0 : curfont = f;
191 : }
192 0 : }
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(size_t 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("fontchar", reinterpret_cast<identfun>(fontchar), "fffffff", Id_Command);
457 1 : addcommand("fontskip", reinterpret_cast<identfun>(fontskip), "i", Id_Command);
458 1 : }
|