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