1 // Copyright (c) Charles J. Cliffe
2 // SPDX-License-Identifier: GPL-2.0+
3
4 #include "GLFont.h"
5
6 #include <wx/string.h>
7
8 #include <iostream>
9 #include <fstream>
10 #include <algorithm>
11 #include "cubic_math.h"
12
13 #ifdef _OSX_APP_
14 #include "CoreFoundation/CoreFoundation.h"
15 #endif
16
17 #ifndef RES_FOLDER
18 #define RES_FOLDER L""
19 #endif
20
21 #define GC_DRAW_COUNT_PERIOD 50
22 #define GC_DRAW_COUNT_LIMIT 10
23
GLFontStringCache()24 GLFontStringCache::GLFontStringCache() {
25 gc = 0;
26 }
27
28 //Static initialization of all available fonts,
29 //using aggregate syntax (Cx11+)
30
31 //Fonts must be listed in increasing size for Drawer to work !
32 GLFont GLFont::fonts[GLFont::GLFontSize::GLFONT_SIZE_MAX] = {
33
34 { GLFont::GLFontSize::GLFONT_SIZE12, L"fonts/vera_sans_mono12.fnt" },
35 { GLFont::GLFontSize::GLFONT_SIZE16, L"fonts/vera_sans_mono16.fnt" },
36 { GLFont::GLFontSize::GLFONT_SIZE18, L"fonts/vera_sans_mono18.fnt" },
37 { GLFont::GLFontSize::GLFONT_SIZE24, L"fonts/vera_sans_mono24.fnt" },
38 { GLFont::GLFontSize::GLFONT_SIZE27, L"fonts/vera_sans_mono27.fnt" },
39 { GLFont::GLFontSize::GLFONT_SIZE32, L"fonts/vera_sans_mono32.fnt" },
40 { GLFont::GLFontSize::GLFONT_SIZE36, L"fonts/vera_sans_mono36.fnt" },
41 { GLFont::GLFontSize::GLFONT_SIZE48, L"fonts/vera_sans_mono48.fnt" },
42 { GLFont::GLFontSize::GLFONT_SIZE64, L"fonts/vera_sans_mono64.fnt" },
43 { GLFont::GLFontSize::GLFONT_SIZE72, L"fonts/vera_sans_mono72.fnt" },
44 { GLFont::GLFontSize::GLFONT_SIZE96, L"fonts/vera_sans_mono96.fnt" },
45
46 };
47
48
49 std::atomic<GLFont::GLFontScale> GLFont::currentScale{ GLFont::GLFontScale::GLFONT_SCALE_NORMAL };
50
51
GLFontChar()52 GLFontChar::GLFontChar() :
53 id(0), x(0), y(0), width(0), height(0), xoffset(0), yoffset(0), xadvance(0), aspect(1), index(0) {
54
55 }
56
57 GLFontChar::~GLFontChar() = default;
58
setId(int idval)59 void GLFontChar::setId(int idval) {
60 id = idval;
61 }
62
getId() const63 int GLFontChar::getId() const {
64 return id;
65 }
66
setXOffset(int xofs)67 void GLFontChar::setXOffset(int xofs) {
68 xoffset = xofs;
69 }
70
getXOffset()71 int GLFontChar::getXOffset() {
72 return xoffset;
73 }
74
setYOffset(int yofs)75 void GLFontChar::setYOffset(int yofs) {
76 yoffset = yofs;
77 }
78
getYOffset()79 int GLFontChar::getYOffset() {
80 return yoffset;
81 }
82
setX(int xpos)83 void GLFontChar::setX(int xpos) {
84 x = xpos;
85 }
86
getX()87 int GLFontChar::getX() {
88 return x;
89 }
90
setY(int ypos)91 void GLFontChar::setY(int ypos) {
92 y = ypos;
93 }
94
getY()95 int GLFontChar::getY() {
96 return y;
97 }
98
setWidth(int w)99 void GLFontChar::setWidth(int w) {
100 width = w;
101 if (width && height) {
102 aspect = (float) width / (float) height;
103 }
104 }
105
getWidth()106 int GLFontChar::getWidth() {
107 return width;
108 }
109
setHeight(int h)110 void GLFontChar::setHeight(int h) {
111 height = h;
112 if (width && height) {
113 aspect = (float) width / (float) height;
114 }
115 }
116
getHeight() const117 int GLFontChar::getHeight() const {
118 return height;
119 }
120
setXAdvance(int xadv)121 void GLFontChar::setXAdvance(int xadv) {
122 xadvance = xadv;
123 }
124
getXAdvance()125 int GLFontChar::getXAdvance() {
126 return xadvance;
127 }
128
getAspect() const129 float GLFontChar::getAspect() const {
130 return aspect;
131 }
132
setIndex(unsigned int idx)133 void GLFontChar::setIndex(unsigned int idx) {
134 index = idx;
135 }
136
getIndex() const137 int GLFontChar::getIndex() const {
138 return index;
139 }
140
GLFont(GLFontSize size,std::wstring defFileName)141 GLFont::GLFont(GLFontSize size, std::wstring defFileName):
142 lineHeight(0), base(0), imageWidth(0), imageHeight(0), loaded(false), texId(0), gcCounter(0) {
143
144 fontSizeClass = size;
145
146 fontDefFileSource = defFileName;
147 }
148
149 GLFont::~GLFont() = default;
150
nextParam(std::wistringstream & str)151 std::wstring GLFont::nextParam(std::wistringstream &str) {
152 std::wstring param_str;
153
154 str >> param_str;
155
156 if (param_str.find(L'"') != std::wstring::npos) {
157 std::wstring rest;
158 while (!str.eof() && (std::count(param_str.begin(), param_str.end(), L'"') % 2)) {
159 str >> rest;
160 param_str.append(L" " + rest);
161 }
162 }
163
164 return param_str;
165 }
166
getParamKey(const std::wstring & param_str)167 std::wstring GLFont::getParamKey(const std::wstring& param_str) {
168 std::wstring keyName;
169
170 size_t eqpos = param_str.find(L'=');
171
172 if (eqpos != std::wstring::npos) {
173 keyName = param_str.substr(0, eqpos);
174 }
175
176 return keyName;
177 }
178
getParamValue(const std::wstring & param_str)179 std::wstring GLFont::getParamValue(const std::wstring& param_str) {
180 std::wstring value;
181
182 size_t eqpos = param_str.find(L'=');
183
184 if (eqpos != std::wstring::npos) {
185 value = param_str.substr(eqpos + 1);
186 }
187
188 if (value[0] == L'"' && value[value.length() - 1] == L'"') {
189 value = value.substr(1, value.length() - 2);
190 }
191
192 return value;
193 }
194
loadFontOnce()195 void GLFont::loadFontOnce() {
196
197 if (loaded) {
198 return;
199 }
200
201 #if _OSX_APP_
202 CFBundleRef mainBundle = CFBundleGetMainBundle();
203 CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
204 char path[PATH_MAX];
205 if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
206 {
207 // error!
208 }
209 CFRelease(resourcesURL);
210 wxString resourceFolder = std::string(path) + "/";
211
212 #else
213 wxString resourceFolder = RES_FOLDER;
214 #endif
215
216 wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
217
218 //1) First try : RES_FOLDER/fonts/*
219 wxFileName fontDefFileName = wxFileName(resourceFolder + L"/" + fontDefFileSource);
220
221 bool fontFilePathFound = fontDefFileName.Exists();
222
223 // 2) Second try: [Cubic exe path]/RES_FOLDER/fonts/*
224 if (!fontFilePathFound) {
225
226 fontDefFileName = wxFileName(exePath.GetPath() + L"/" + RES_FOLDER + L"/" + fontDefFileSource);
227 fontFilePathFound = fontDefFileName.Exists();
228 }
229
230 // 3) Third try: [Cubic exe path]/fonts/*
231 if (!fontFilePathFound) {
232
233 fontDefFileName = wxFileName(exePath.GetPath() + L"/" + fontDefFileSource);
234 fontFilePathFound = fontDefFileName.Exists();
235 }
236
237 if (fontFilePathFound) {
238
239 if (!fontDefFileName.IsFileReadable()) {
240 std::cout << "Font file " << fontDefFileName.GetFullPath() << " is not readable?" << std::endl;
241 return;
242 }
243 }
244 else {
245
246 if (!fontDefFileName.FileExists()) {
247 std::cout << "Font file " << fontDefFileName.GetFullPath() << " does not exist?" << std::endl;
248 return;
249 }
250 }
251
252 //Re-compute the resource dir.
253 resourceFolder = fontDefFileName.GetPath();
254
255 std::string fontDefFileNamePath = fontDefFileName.GetFullPath(wxPATH_NATIVE).ToStdString();
256
257 std::wifstream input;
258
259 input.open(fontDefFileNamePath, std::ios::in);
260
261 std::wstring op;
262
263 while (!input.eof()) {
264 input >> op;
265 if (op == L"info") {
266 std::wstring info_param_str;
267 getline(input, info_param_str);
268 std::wistringstream info_param(info_param_str);
269
270 while (!info_param.eof()) {
271 std::wstring param = nextParam(info_param);
272
273 std::wstring paramKey = getParamKey(param);
274 if (paramKey == L"face") {
275 fontName = getParamValue(param);
276 }
277
278 param = nextParam(info_param);
279 paramKey = getParamKey(param);
280 if (paramKey == L"size") {
281
282 std::wistringstream paramValue(getParamValue(param));
283 paramValue >> pixHeight;
284 }
285
286 // std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
287 }
288 } else if (op == L"common") {
289 std::wstring common_param_str;
290 getline(input, common_param_str);
291 std::wistringstream common_param(common_param_str);
292
293 while (!common_param.eof()) {
294 std::wstring param = nextParam(common_param);
295
296 std::wstring paramKey = getParamKey(param);
297 std::wistringstream paramValue(getParamValue(param));
298
299 if (paramKey == L"lineHeight") {
300 paramValue >> lineHeight;
301 } else if (paramKey == L"base") {
302 paramValue >> base;
303 } else if (paramKey == L"scaleW") {
304 paramValue >> imageWidth;
305 } else if (paramKey == L"scaleH") {
306 paramValue >> imageHeight;
307 }
308 // std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
309 }
310 } else if (op == L"page") {
311 std::wstring page_param_str;
312 getline(input, page_param_str);
313 std::wistringstream page_param(page_param_str);
314
315 while (!page_param.eof()) {
316 std::wstring param = nextParam(page_param);
317
318 std::wstring paramKey = getParamKey(param);
319 std::wstring paramValue = getParamValue(param);
320
321 if (paramKey == L"file") {
322 wxFileName imgFileName = wxFileName(resourceFolder, paramValue);
323 imageFile = imgFileName.GetFullPath(wxPATH_NATIVE).ToStdWstring();
324 }
325 // std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
326 }
327
328 } else if (op == L"char") {
329 std::wstring char_param_str;
330 getline(input, char_param_str);
331 std::wistringstream char_param(char_param_str);
332
333 auto *newChar = new GLFontChar;
334
335 while (!char_param.eof()) {
336 std::wstring param = nextParam(char_param);
337
338 std::wstring paramKey = getParamKey(param);
339 std::wistringstream paramValue(getParamValue(param));
340
341 int val;
342
343 if (paramKey == L"id") {
344 paramValue >> val;
345 newChar->setId(val);
346 } else if (paramKey == L"x") {
347 paramValue >> val;
348 newChar->setX(val);
349 } else if (paramKey == L"y") {
350 paramValue >> val;
351 newChar->setY(val);
352 } else if (paramKey == L"width") {
353 paramValue >> val;
354 newChar->setWidth(val);
355 } else if (paramKey == L"height") {
356 paramValue >> val;
357 newChar->setHeight(val);
358 } else if (paramKey == L"xoffset") {
359 paramValue >> val;
360 newChar->setXOffset(val);
361 } else if (paramKey == L"yoffset") {
362 paramValue >> val;
363 newChar->setYOffset(val);
364 } else if (paramKey == L"xadvance") {
365 paramValue >> val;
366 newChar->setXAdvance(val);
367 }
368
369 // std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
370 }
371
372 characters[newChar->getId()] = newChar;
373
374 } else {
375 std::wstring dummy;
376 getline(input, dummy);
377 }
378 }
379
380 if (!imageFile.empty() && imageWidth && imageHeight && !characters.empty()) {
381
382 // Load file and decode image.
383 std::vector<unsigned char> image;
384
385 unsigned int imgWidth = imageWidth, imgHeight = imageHeight;
386
387 //1) First load the raw file to memory using wstring filenames
388 wxFile png_file(imageFile);
389
390 int png_size = png_file.Length();
391
392 auto* raw_image = new unsigned char[png_size];
393
394 if (png_size > 0) {
395
396 int nbRead = png_file.Read((void*)raw_image, png_size);
397
398 if (png_size != nbRead) {
399
400 std::cout << "Error loading the full PNG image file in memory: '" << imageFile << "'" << std::endl;
401 }
402 }
403
404 //2) then load from memory
405 unsigned error = lodepng::decode(image, imgWidth, imgHeight, raw_image, png_size);
406
407 delete[] raw_image;
408 png_file.Close();
409
410 if (error) {
411 std::cout << "Error decoding PNG image file: '" << imageFile << "'" << std::endl;
412 }
413
414 glGenTextures(1, &texId);
415 glEnable(GL_TEXTURE_2D);
416 glBindTexture(GL_TEXTURE_2D, texId);
417 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
418 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
419 glTexImage2D(GL_TEXTURE_2D, 0, 4, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, error?nullptr:(&image[0]));
420 glDisable(GL_TEXTURE_2D);
421
422 std::map<int, GLFontChar *>::iterator char_i;
423
424 gl_vertices.resize(characters.size() * 8); // one quad per char
425 gl_uv.resize(characters.size() * 8);
426
427 unsigned int ofs = 0;
428 for (char_i = characters.begin(); char_i != characters.end(); char_i++) {
429 // int charId = (*char_i).first;
430 GLFontChar *fchar = (*char_i).second;
431
432 float faspect = fchar->getAspect();
433
434 float uv_xpos = (float) fchar->getX() / (float) imageWidth;
435 float uv_ypos = ((float) fchar->getY() / (float) imageHeight);
436 float uv_xofs = (float) fchar->getWidth() / (float) imageWidth;
437 float uv_yofs = ((float) fchar->getHeight() / (float) imageHeight);
438
439 gl_vertices[ofs] = 0;
440 gl_vertices[ofs + 1] = 0;
441 gl_uv[ofs] = uv_xpos;
442 gl_uv[ofs + 1] = uv_ypos + uv_yofs;
443
444 gl_vertices[ofs + 2] = faspect;
445 gl_vertices[ofs + 3] = 0;
446 gl_uv[ofs + 2] = uv_xpos + uv_xofs;
447 gl_uv[ofs + 3] = uv_ypos + uv_yofs;
448
449 gl_vertices[ofs + 4] = faspect;
450 gl_vertices[ofs + 5] = 1;
451 gl_uv[ofs + 4] = uv_xpos + uv_xofs;
452 gl_uv[ofs + 5] = uv_ypos;
453
454 gl_vertices[ofs + 6] = 0;
455 gl_vertices[ofs + 7] = 1;
456 gl_uv[ofs + 6] = uv_xpos;
457 gl_uv[ofs + 7] = uv_ypos;
458
459 fchar->setIndex(ofs);
460
461 ofs += 8;
462 }
463
464 std::cout << "Loaded font '" << fontName << "' from '" << imageFile << "', parsed " << characters.size() << " characters." << std::endl;
465
466 loaded = true;
467 } else {
468 std::cout << "Error loading font file " << imageFile << std::endl;
469 }
470
471 input.close();
472 loaded = true;
473 }
474
getStringWidth(const std::wstring & str,float size,float viewAspect)475 float GLFont::getStringWidth(const std::wstring& str, float size, float viewAspect) {
476
477 float scalex = size / viewAspect;
478
479 float width = 0;
480
481 for (int charId : str) {
482 if (characters.find(charId) == characters.end()) {
483 continue;
484 }
485
486 GLFontChar *fchar = characters[charId];
487
488 float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
489 float advx = (float) fchar->getXAdvance() / (float) imageWidth;
490
491 if (charId == 32) {
492 advx = characters[L'_']->getAspect();
493 }
494
495 width += fchar->getAspect() + advx + ofsx;
496 }
497
498 width *= scalex;
499
500 return width;
501 }
502
503 // Draw string, immediate
drawString(const std::wstring & str,int pxHeight,float xpos,float ypos,Align hAlign,Align vAlign,int vpx,int vpy,bool cacheable)504 void GLFont::drawString(const std::wstring& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
505
506 pxHeight *= 2;
507
508 if (!vpx || !vpy) {
509 GLint vp[4];
510 glGetIntegerv( GL_VIEWPORT, vp);
511 vpx = vp[2];
512 vpy = vp[3];
513 }
514
515 if (cacheable) {
516 gcCounter++;
517
518 std::lock_guard<SpinMutex> lock(cache_busy);
519
520 if (gcCounter > GC_DRAW_COUNT_PERIOD) {
521
522 doCacheGC();
523 gcCounter = 0;
524 }
525
526 GLFontStringCache *fc = nullptr;
527
528 std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
529
530 std::wstringstream sscacheIdx;
531
532 sscacheIdx << vpx << "." << vpy << "." << pxHeight << "." << str;
533
534 std::wstring cacheIdx(sscacheIdx.str());
535
536 cache_iter = stringCache.find(cacheIdx);
537 if (cache_iter != stringCache.end()) {
538 fc = cache_iter->second;
539 fc->gc = 0;
540 }
541
542 if (fc == nullptr) {
543 // std::cout << "cache miss" << std::endl;
544 fc = cacheString(str, pxHeight, vpx, vpy);
545 stringCache[cacheIdx] = fc;
546 }
547
548 drawCacheString(fc, xpos, ypos, hAlign, vAlign);
549
550 return;
551 }
552
553 float size = (float) pxHeight / (float) vpy;
554 float viewAspect = (float) vpx / (float) vpy;
555 float msgWidth = getStringWidth(str, size, viewAspect);
556
557 glPushMatrix();
558 glTranslatef(xpos, ypos, 0.0f);
559
560 switch (vAlign) {
561 case GLFONT_ALIGN_TOP:
562 glTranslatef(0.0, -size, 0.0);
563 break;
564 case GLFONT_ALIGN_CENTER:
565 glTranslatef(0.0, -size/2.0, 0.0);
566 break;
567 default:
568 break;
569 }
570
571 switch (hAlign) {
572 case GLFONT_ALIGN_RIGHT:
573 glTranslatef(-msgWidth, 0.0, 0.0);
574 break;
575 case GLFONT_ALIGN_CENTER:
576 glTranslatef(-msgWidth / 2.0, 0.0, 0.0);
577 break;
578 default:
579 break;
580 }
581
582 glPushMatrix();
583 glScalef(size / viewAspect, size, 1.0f);
584
585 glEnable(GL_TEXTURE_2D);
586 glBindTexture(GL_TEXTURE_2D, texId);
587
588 glEnable(GL_BLEND);
589 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
590
591 glEnableClientState(GL_VERTEX_ARRAY);
592 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
593 glVertexPointer(2, GL_FLOAT, 0, &gl_vertices[0]);
594 glTexCoordPointer(2, GL_FLOAT, 0, &gl_uv[0]);
595
596 for (int charId : str) {
597 if (characters.find(charId) == characters.end()) {
598 continue;
599 }
600
601 GLFontChar *fchar = characters[charId];
602
603 float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
604 float advx = (float) fchar->getXAdvance() / (float) imageWidth;
605
606 if (charId == 32) {
607 advx = characters[L'_']->getAspect();
608 }
609
610 glTranslatef(ofsx, 0.0, 0.0);
611 glDrawArrays(GL_QUADS, fchar->getIndex() / 2, 4);
612 glTranslatef(fchar->getAspect() + advx, 0.0, 0.0);
613 }
614
615 glVertexPointer(2, GL_FLOAT, 0, nullptr);
616 glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
617
618 glDisableClientState(GL_VERTEX_ARRAY);
619 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
620 glPopMatrix();
621 glPopMatrix();
622
623 glDisable(GL_BLEND);
624 glDisable(GL_TEXTURE_2D);
625 }
626
627 // Draw string, immediate, 8 bit version
drawString(const std::string & str,int pxHeight,float xpos,float ypos,Align hAlign,Align vAlign,int vpx,int vpy,bool cacheable)628 void GLFont::drawString(const std::string& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
629
630 //Displayed string is wstring, so use wxString to do the heavy lifting of converting str...
631 wxString wsTmp;
632
633 wsTmp.assign(str);
634
635 drawString(wsTmp.ToStdWstring(), pxHeight, xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
636 }
637
638 // Draw cached GLFontCacheString
drawCacheString(GLFontStringCache * fc,float xpos,float ypos,Align hAlign,Align vAlign) const639 void GLFont::drawCacheString(GLFontStringCache *fc, float xpos, float ypos, Align hAlign, Align vAlign) const {
640
641 float size = (float) fc->pxHeight / (float) fc->vpy;
642
643 glPushMatrix();
644 glTranslatef(xpos, ypos, 0.0f);
645
646 switch (vAlign) {
647 case GLFONT_ALIGN_TOP:
648 glTranslatef(0.0, -size, 0.0);
649 break;
650 case GLFONT_ALIGN_CENTER:
651 glTranslatef(0.0, -size/2.0, 0.0);
652 break;
653 default:
654 break;
655 }
656
657 switch (hAlign) {
658 case GLFONT_ALIGN_RIGHT:
659 glTranslatef(-fc->msgWidth, 0.0, 0.0);
660 break;
661 case GLFONT_ALIGN_CENTER:
662 glTranslatef(-fc->msgWidth / 2.0, 0.0, 0.0);
663 break;
664 default:
665 break;
666 }
667
668 glEnable(GL_TEXTURE_2D);
669 glBindTexture(GL_TEXTURE_2D, texId);
670
671 glEnable(GL_BLEND);
672 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
673
674 glEnableClientState(GL_VERTEX_ARRAY);
675 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
676 glVertexPointer(2, GL_FLOAT, 0, &fc->gl_vertices[0]);
677 glTexCoordPointer(2, GL_FLOAT, 0, &fc->gl_uv[0]);
678
679 glDrawArrays(GL_QUADS, 0, 4 * fc->drawlen);
680
681 glVertexPointer(2, GL_FLOAT, 0, nullptr);
682 glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
683
684 glDisableClientState(GL_VERTEX_ARRAY);
685 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
686
687 glPopMatrix();
688
689 glDisable(GL_BLEND);
690 glDisable(GL_TEXTURE_2D);
691 }
692
693 // Compile optimized GLFontCacheString
cacheString(const std::wstring & str,int pxHeight,int vpx,int vpy)694 GLFontStringCache *GLFont::cacheString(const std::wstring& str, int pxHeight, int vpx, int vpy) {
695
696 auto *fc = new GLFontStringCache;
697
698 fc->pxHeight = pxHeight;
699 fc->vpx = vpx;
700 fc->vpy = vpy;
701
702 float size = (float) pxHeight / (float) vpy;
703 float viewAspect = (float) vpx / (float) vpy;
704
705 fc->msgWidth = getStringWidth(str, size, viewAspect);
706
707 int nChar = 0;
708 for (int charId : str) {
709 if (characters.find(charId) == characters.end()) {
710 continue;
711 }
712 nChar++;
713 }
714
715 fc->drawlen = nChar;
716 fc->gl_vertices.resize(nChar*8);
717 fc->gl_uv.resize(nChar*8);
718
719
720 CubicVR::mat4 trans = CubicVR::mat4::scale(size / viewAspect, size, 1.0f);
721
722 int c = 0;
723 for (int charId : str) {
724 if (characters.find(charId) == characters.end()) {
725 continue;
726 }
727
728 GLFontChar *fchar = characters[charId];
729
730 float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
731 float advx = (float) fchar->getXAdvance() / (float) imageWidth;
732
733 if (charId == 32) {
734 advx = characters[L'_']->getAspect();
735 }
736
737 // freeze transform to buffer
738 trans *= CubicVR::mat4::translate(ofsx, 0.0, 0.0);
739 int charIdx = fchar->getIndex();
740 for (int j = 0; j < 8; j+=2) {
741 CubicVR::vec3 pt(gl_vertices[charIdx + j],gl_vertices[charIdx + j + 1], 0.0);
742 pt = CubicVR::mat4::multiply(trans, pt, true);
743 fc->gl_vertices[c * 8 + j] = pt[0];
744 fc->gl_vertices[c * 8 + j + 1] = pt[1];
745 fc->gl_uv[c * 8 + j] = gl_uv[charIdx + j];
746 fc->gl_uv[c * 8 + j + 1] = gl_uv[charIdx + j + 1];
747 }
748 trans *= CubicVR::mat4::translate(fchar->getAspect() + advx, 0.0, 0.0);
749 c++;
750 }
751
752 return fc;
753 }
754
doCacheGC()755 void GLFont::doCacheGC() {
756
757 std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
758
759 bool flushDone = false;
760
761 //do aging and remove in one pass.
762 cache_iter = stringCache.begin();
763
764 while (cache_iter != stringCache.end()) {
765
766 //aging
767 cache_iter->second->gc--;
768
769 //only flush 1 element per call
770 if (!flushDone && cache_iter->second->gc < -GC_DRAW_COUNT_LIMIT) {
771
772 delete cache_iter->second;
773 cache_iter = stringCache.erase(cache_iter);
774 flushDone = true;
775 }
776 else {
777 cache_iter++;
778 }
779 } //end while
780 }
781
clearCache()782 void GLFont::clearCache() {
783
784 std::lock_guard<SpinMutex> lock(cache_busy);
785
786 std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
787
788 cache_iter = stringCache.begin();
789
790 while (cache_iter != stringCache.end()) {
791
792 delete cache_iter->second;
793 cache_iter = stringCache.erase(cache_iter);
794
795 }
796 }
797
clearAllCaches()798 void GLFont::clearAllCaches() {
799
800 for (int i = 0; i < GLFont::GLFONT_SIZE_MAX; i++) {
801
802 fonts[i].clearCache();
803 }
804 }
805
806
getFont(int requestedSize,double scaleFactor)807 GLFont::Drawer GLFont::getFont(int requestedSize, double scaleFactor) {
808
809 return GLFont::Drawer(requestedSize, scaleFactor);
810 }
811
812
813
setScale(GLFontScale scale)814 void GLFont::setScale(GLFontScale scale) {
815
816 //safety vs. inputs
817 if (scale < GLFONT_SCALE_NORMAL || scale > GLFONT_SCALE_LARGE) {
818
819 scale = GLFontScale::GLFONT_SCALE_NORMAL;
820 }
821
822 currentScale.store(scale);
823
824 //Flush all the GC stuff
825 clearAllCaches();
826 }
827
getScale()828 GLFont::GLFontScale GLFont::getScale() {
829
830 return currentScale.load();
831 }
832
getScaleFactor()833 double GLFont::getScaleFactor() {
834
835 GLFontScale scale = currentScale.load();
836
837 if (scale == GLFONT_SCALE_MEDIUM) {
838
839 return 1.5;
840 }
841 else if (scale == GLFONT_SCALE_LARGE) {
842
843 return 2.0;
844 }
845
846 return 1.0;
847 }
848
getScaledPx(int basicFontSize,double scaleFactor)849 int GLFont::getScaledPx(int basicFontSize, double scaleFactor) {
850 //try to align on an integer pixel size if the targetSize font is available
851 int targetSize = round(basicFontSize * scaleFactor);
852 int resultIndex = 0;
853
854 fonts[0].loadFontOnce();
855
856 for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
857
858 fonts[i + 1].loadFontOnce();
859
860 if (fonts[i + 1].pixHeight <= targetSize) {
861 resultIndex = i + 1;
862 }
863 else {
864 break;
865 }
866 } //end for
867
868 // return font height px
869 return fonts[resultIndex].pixHeight;
870 }
871
872
Drawer(int basicFontSize,double scaleFactor)873 GLFont::Drawer::Drawer(int basicFontSize, double scaleFactor) {
874
875 //Selection of the final font: scan GLFont::fonts to find the biggest font such as
876 // its pixHeight <= basicFontSize * scaleFactor.
877 //then compute finalScaleFactor the zooming factor of renderingFont to reach a
878 //final font size of basicFontSize* scaleFactor:
879 renderingFontIndex = 0;
880
881 //try to align on an integer pixel size if the targetSize font is available
882 int targetSize = round(basicFontSize * scaleFactor);
883
884 fonts[0].loadFontOnce();
885
886 for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
887
888 fonts[i + 1].loadFontOnce();
889
890 if (fonts[i + 1].pixHeight <= targetSize) {
891
892 renderingFontIndex = i + 1;
893 }
894 else {
895 break;
896 }
897 } //end for
898
899 //
900 int rawSize = fonts[renderingFontIndex].pixHeight;
901
902 //targetSize may not be reached yet, so the effective rendering font: fonts[renderingFontIndex] must be scaled up a bit.
903 renderingFontScaleFactor = (double) targetSize / rawSize;
904 }
905
drawString(const std::wstring & str,float xpos,float ypos,Align hAlign,Align vAlign,int vpx,int vpy,bool cacheable) const906 void GLFont::Drawer::drawString(const std::wstring& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) const {
907
908 GLFont& appliedFont = fonts[renderingFontIndex];
909
910 appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
911 }
912
913 //Public drawing font, 8 bit char version.
drawString(const std::string & str,float xpos,float ypos,Align hAlign,Align vAlign,int vpx,int vpy,bool cacheable) const914 void GLFont::Drawer::drawString(const std::string& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) const {
915
916 GLFont& appliedFont = fonts[renderingFontIndex];
917
918 appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
919 }
920
921