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