LCOV - code coverage report
Current view: top level - engine/render - rendertext.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 54.2 % 120 65
Test Date: 2026-03-06 06:39:57 Functions: 45.0 % 20 9

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

Generated by: LCOV version 2.0-1