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