acidportal

- 😈 Worlds smallest Evil Portal on a LilyGo T-QT
git clone git://git.acid.vegas/acidportal.git
Log | Files | Refs | Archive | README | LICENSE

Smooth_font.cpp (19810B)

      1  // Coded by Bodmer 10/2/18, see license in root directory.
      2  // This is part of the TFT_eSPI class and is associated with anti-aliased font functions
      3  
      4 
      5 ////////////////////////////////////////////////////////////////////////////////////////
      6 // New anti-aliased (smoothed) font functions added below
      7 ////////////////////////////////////////////////////////////////////////////////////////
      8 
      9 /***************************************************************************************
     10 ** Function name:           loadFont
     11 ** Description:             loads parameters from a font vlw array in memory
     12 *************************************************************************************x*/
     13 void TFT_eSPI::loadFont(const uint8_t array[])
     14 {
     15   if (array == nullptr) return;
     16   fontPtr = (uint8_t*) array;
     17   loadFont("", false);
     18 }
     19 
     20 #ifdef FONT_FS_AVAILABLE
     21 /***************************************************************************************
     22 ** Function name:           loadFont
     23 ** Description:             loads parameters from a font vlw file
     24 *************************************************************************************x*/
     25 void TFT_eSPI::loadFont(String fontName, fs::FS &ffs)
     26 {
     27   fontFS = ffs;
     28   loadFont(fontName, false);
     29 }
     30 #endif
     31 
     32 /***************************************************************************************
     33 ** Function name:           loadFont
     34 ** Description:             loads parameters from a font vlw file
     35 *************************************************************************************x*/
     36 void TFT_eSPI::loadFont(String fontName, bool flash)
     37 {
     38   /*
     39     The vlw font format does not appear to be documented anywhere, so some reverse
     40     engineering has been applied!
     41 
     42     Header of vlw file comprises 6 uint32_t parameters (24 bytes total):
     43       1. The gCount (number of character glyphs)
     44       2. A version number (0xB = 11 for the one I am using)
     45       3. The font size (in points, not pixels)
     46       4. Deprecated mboxY parameter (typically set to 0)
     47       5. Ascent in pixels from baseline to top of "d"
     48       6. Descent in pixels from baseline to bottom of "p"
     49 
     50     Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes):
     51       1. Glyph Unicode stored as a 32 bit value
     52       2. Height of bitmap bounding box
     53       3. Width of bitmap bounding box
     54       4. gxAdvance for cursor (setWidth in Processing)
     55       5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
     56       6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
     57       7. padding value, typically 0
     58 
     59     The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file.
     60     Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from
     61     0xFF foreground colour, 0x00 background. The library uses a linear interpolation
     62     between the foreground and background RGB component colours. e.g.
     63         pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255
     64     To gain a performance advantage fixed point arithmetic is used with rounding and
     65     division by 256 (shift right 8 bits is faster).
     66 
     67     After the bitmaps is:
     68        1 byte for font name string length (excludes null)
     69        a zero terminated character string giving the font name
     70        1 byte for Postscript name string length
     71        a zero/one terminated character string giving the font name
     72        last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed)
     73 
     74 
     75     Glyph bitmap example is:
     76     // Cursor coordinate positions for this and next character are marked by 'C'
     77     // C<------- gxAdvance ------->C  gxAdvance is how far to move cursor for next glyph cursor position
     78     // |                           |
     79     // |                           |   ascent is top of "d", descent is bottom of "p"
     80     // +-- gdX --+             ascent
     81     // |         +-- gWidth--+     |   gdX is offset to left edge of glyph bitmap
     82     // |   +     x@.........@x  +  |   gdX may be negative e.g. italic "y" tail extending to left of
     83     // |   |     @@.........@@  |  |   cursor position, plot top left corner of bitmap at (cursorX + gdX)
     84     // |   |     @@.........@@ gdY |   gWidth and gHeight are glyph bitmap dimensions
     85     // |   |     .@@@.....@@@@  |  |
     86     // | gHeight ....@@@@@..@@  +  +    <-- baseline
     87     // |   |     ...........@@     |
     88     // |   |     ...........@@     |   gdY is the offset to the top edge of the bitmap
     89     // |   |     .@@.......@@. descent plot top edge of bitmap at (cursorY + ascent - gdY)
     90     // |   +     x..@@@@@@@..x     |   x marks the corner pixels of the bitmap
     91     // |                           |
     92     // +---------------------------+   yAdvance is y delta for the next line, font size or (ascent + descent)
     93     //                                 some fonts can overlay in y direction so may need a user adjust value
     94 
     95   */
     96 
     97   if (fontLoaded) unloadFont();
     98 
     99 #ifdef FONT_FS_AVAILABLE
    100   if (fontName == "") fs_font = false;
    101   else { fontPtr = nullptr; fs_font = true; }
    102 
    103   if (fs_font) {
    104     spiffs = flash; // true if font is in SPIFFS
    105 
    106     if(spiffs) fontFS = SPIFFS;
    107 
    108     // Avoid a crash on the ESP32 if the file does not exist
    109     if (fontFS.exists("/" + fontName + ".vlw") == false) {
    110       Serial.println("Font file " + fontName + " not found!");
    111       return;
    112     }
    113 
    114     fontFile = fontFS.open( "/" + fontName + ".vlw", "r");
    115 
    116     if(!fontFile) return;
    117 
    118     fontFile.seek(0, fs::SeekSet);
    119   }
    120 #else
    121   // Avoid unused varaible warning
    122   fontName = fontName;
    123   flash = flash;
    124 #endif
    125 
    126   gFont.gArray   = (const uint8_t*)fontPtr;
    127 
    128   gFont.gCount   = (uint16_t)readInt32(); // glyph count in file
    129                              readInt32(); // vlw encoder version - discard
    130   gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
    131                              readInt32(); // discard
    132   gFont.ascent   = (uint16_t)readInt32(); // top of "d"
    133   gFont.descent  = (uint16_t)readInt32(); // bottom of "p"
    134 
    135   // These next gFont values might be updated when the Metrics are fetched
    136   gFont.maxAscent  = gFont.ascent;   // Determined from metrics
    137   gFont.maxDescent = gFont.descent;  // Determined from metrics
    138   gFont.yAdvance   = gFont.ascent + gFont.descent;
    139   gFont.spaceWidth = gFont.yAdvance / 4;  // Guess at space width
    140 
    141   fontLoaded = true;
    142 
    143   // Fetch the metrics for each glyph
    144   loadMetrics();
    145 }
    146 
    147 
    148 /***************************************************************************************
    149 ** Function name:           loadMetrics
    150 ** Description:             Get the metrics for each glyph and store in RAM
    151 *************************************************************************************x*/
    152 //#define SHOW_ASCENT_DESCENT
    153 void TFT_eSPI::loadMetrics(void)
    154 {
    155   uint32_t headerPtr = 24;
    156   uint32_t bitmapPtr = headerPtr + gFont.gCount * 28;
    157 
    158 #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
    159   if ( psramFound() )
    160   {
    161     gUnicode  = (uint16_t*)ps_malloc( gFont.gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
    162     gHeight   =  (uint8_t*)ps_malloc( gFont.gCount );    // Height of glyph
    163     gWidth    =  (uint8_t*)ps_malloc( gFont.gCount );    // Width of glyph
    164     gxAdvance =  (uint8_t*)ps_malloc( gFont.gCount );    // xAdvance - to move x cursor
    165     gdY       =  (int16_t*)ps_malloc( gFont.gCount * 2); // offset from bitmap top edge from lowest point in any character
    166     gdX       =   (int8_t*)ps_malloc( gFont.gCount );    // offset for bitmap left edge relative to cursor X
    167     gBitmap   = (uint32_t*)ps_malloc( gFont.gCount * 4); // seek pointer to glyph bitmap in the file
    168   }
    169   else
    170 #endif
    171   {
    172     gUnicode  = (uint16_t*)malloc( gFont.gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
    173     gHeight   =  (uint8_t*)malloc( gFont.gCount );    // Height of glyph
    174     gWidth    =  (uint8_t*)malloc( gFont.gCount );    // Width of glyph
    175     gxAdvance =  (uint8_t*)malloc( gFont.gCount );    // xAdvance - to move x cursor
    176     gdY       =  (int16_t*)malloc( gFont.gCount * 2); // offset from bitmap top edge from lowest point in any character
    177     gdX       =   (int8_t*)malloc( gFont.gCount );    // offset for bitmap left edge relative to cursor X
    178     gBitmap   = (uint32_t*)malloc( gFont.gCount * 4); // seek pointer to glyph bitmap in the file
    179   }
    180 
    181 #ifdef SHOW_ASCENT_DESCENT
    182   Serial.print("ascent  = "); Serial.println(gFont.ascent);
    183   Serial.print("descent = "); Serial.println(gFont.descent);
    184 #endif
    185 
    186 #ifdef FONT_FS_AVAILABLE
    187   if (fs_font) fontFile.seek(headerPtr, fs::SeekSet);
    188 #endif
    189 
    190   uint16_t gNum = 0;
    191 
    192   while (gNum < gFont.gCount)
    193   {
    194     gUnicode[gNum]  = (uint16_t)readInt32(); // Unicode code point value
    195     gHeight[gNum]   =  (uint8_t)readInt32(); // Height of glyph
    196     gWidth[gNum]    =  (uint8_t)readInt32(); // Width of glyph
    197     gxAdvance[gNum] =  (uint8_t)readInt32(); // xAdvance - to move x cursor
    198     gdY[gNum]       =  (int16_t)readInt32(); // y delta from baseline
    199     gdX[gNum]       =   (int8_t)readInt32(); // x delta from cursor
    200     readInt32(); // ignored
    201 
    202     //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gHeight  = "); Serial.println(gHeight[gNum]);
    203     //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gWidth  = "); Serial.println(gWidth[gNum]);
    204     //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gxAdvance  = "); Serial.println(gxAdvance[gNum]);
    205     //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdY  = "); Serial.println(gdY[gNum]);
    206 
    207     // Different glyph sets have different ascent values not always based on "d", so we could get
    208     // the maximum glyph ascent by checking all characters. BUT this method can generate bad values
    209     // for non-existent glyphs, so we will reply on processing for the value and disable this code for now...
    210     /*
    211     if (gdY[gNum] > gFont.maxAscent)
    212     {
    213       // Try to avoid UTF coding values and characters that tend to give duff values
    214       if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0x7F)) || (gUnicode[gNum] > 0xA0))
    215       {
    216         gFont.maxAscent   = gdY[gNum];
    217 #ifdef SHOW_ASCENT_DESCENT
    218         Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent  = "); Serial.println(gFont.maxAscent);
    219 #endif
    220       }
    221     }
    222     */
    223 
    224     // Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
    225     if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent)
    226     {
    227       // Avoid UTF coding values and characters that tend to give duff values
    228       if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF))
    229       {
    230         gFont.maxDescent   = gHeight[gNum] - gdY[gNum];
    231 #ifdef SHOW_ASCENT_DESCENT
    232         Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]);
    233 #endif
    234       }
    235     }
    236 
    237     gBitmap[gNum] = bitmapPtr;
    238 
    239     bitmapPtr += gWidth[gNum] * gHeight[gNum];
    240 
    241     gNum++;
    242     yield();
    243   }
    244 
    245   gFont.yAdvance = gFont.maxAscent + gFont.maxDescent;
    246 
    247   gFont.spaceWidth = (gFont.ascent + gFont.descent) * 2/7;  // Guess at space width
    248 }
    249 
    250 
    251 /***************************************************************************************
    252 ** Function name:           deleteMetrics
    253 ** Description:             Delete the old glyph metrics and free up the memory
    254 *************************************************************************************x*/
    255 void TFT_eSPI::unloadFont( void )
    256 {
    257   if (gUnicode)
    258   {
    259     free(gUnicode);
    260     gUnicode = NULL;
    261   }
    262 
    263   if (gHeight)
    264   {
    265     free(gHeight);
    266     gHeight = NULL;
    267   }
    268 
    269   if (gWidth)
    270   {
    271     free(gWidth);
    272     gWidth = NULL;
    273   }
    274 
    275   if (gxAdvance)
    276   {
    277     free(gxAdvance);
    278     gxAdvance = NULL;
    279   }
    280 
    281   if (gdY)
    282   {
    283     free(gdY);
    284     gdY = NULL;
    285   }
    286 
    287   if (gdX)
    288   {
    289     free(gdX);
    290     gdX = NULL;
    291   }
    292 
    293   if (gBitmap)
    294   {
    295     free(gBitmap);
    296     gBitmap = NULL;
    297   }
    298 
    299   gFont.gArray = nullptr;
    300 
    301 #ifdef FONT_FS_AVAILABLE
    302   if (fs_font && fontFile) fontFile.close();
    303 #endif
    304 
    305   fontLoaded = false;
    306 }
    307 
    308 
    309 /***************************************************************************************
    310 ** Function name:           readInt32
    311 ** Description:             Get a 32 bit integer from the font file
    312 *************************************************************************************x*/
    313 uint32_t TFT_eSPI::readInt32(void)
    314 {
    315   uint32_t val = 0;
    316 
    317 #ifdef FONT_FS_AVAILABLE
    318   if (fs_font) {
    319     val  = fontFile.read() << 24;
    320     val |= fontFile.read() << 16;
    321     val |= fontFile.read() << 8;
    322     val |= fontFile.read();
    323   }
    324   else
    325 #endif
    326   {
    327     val  = pgm_read_byte(fontPtr++) << 24;
    328     val |= pgm_read_byte(fontPtr++) << 16;
    329     val |= pgm_read_byte(fontPtr++) << 8;
    330     val |= pgm_read_byte(fontPtr++);
    331   }
    332 
    333   return val;
    334 }
    335 
    336 
    337 /***************************************************************************************
    338 ** Function name:           getUnicodeIndex
    339 ** Description:             Get the font file index of a Unicode character
    340 *************************************************************************************x*/
    341 bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index)
    342 {
    343   for (uint16_t i = 0; i < gFont.gCount; i++)
    344   {
    345     if (gUnicode[i] == unicode)
    346     {
    347       *index = i;
    348       return true;
    349     }
    350   }
    351   return false;
    352 }
    353 
    354 
    355 /***************************************************************************************
    356 ** Function name:           drawGlyph
    357 ** Description:             Write a character to the TFT cursor position
    358 *************************************************************************************x*/
    359 // Expects file to be open
    360 void TFT_eSPI::drawGlyph(uint16_t code)
    361 {
    362   uint16_t fg = textcolor;
    363   uint16_t bg = textbgcolor;
    364 
    365   // Check if cursor has moved
    366   if (last_cursor_x != cursor_x)
    367   {
    368     bg_cursor_x = cursor_x;
    369     last_cursor_x = cursor_x;
    370   }
    371 
    372   if (code < 0x21)
    373   {
    374     if (code == 0x20) {
    375       if (_fillbg) fillRect(bg_cursor_x, cursor_y, (cursor_x + gFont.spaceWidth) - bg_cursor_x, gFont.yAdvance, bg);
    376       cursor_x += gFont.spaceWidth;
    377       bg_cursor_x = cursor_x;
    378       last_cursor_x = cursor_x;
    379       return;
    380     }
    381 
    382     if (code == '\n') {
    383       cursor_x = 0;
    384       bg_cursor_x = 0;
    385       last_cursor_x = 0;
    386       cursor_y += gFont.yAdvance;
    387       if (textwrapY && (cursor_y >= height())) cursor_y = 0;
    388       return;
    389     }
    390   }
    391 
    392   uint16_t gNum = 0;
    393   bool found = getUnicodeIndex(code, &gNum);
    394   
    395   if (found)
    396   {
    397 
    398     if (textwrapX && (cursor_x + gWidth[gNum] + gdX[gNum] > width()))
    399     {
    400       cursor_y += gFont.yAdvance;
    401       cursor_x = 0;
    402       bg_cursor_x = 0;
    403     }
    404     if (textwrapY && ((cursor_y + gFont.yAdvance) >= height())) cursor_y = 0;
    405     if (cursor_x == 0) cursor_x -= gdX[gNum];
    406 
    407     uint8_t* pbuffer = nullptr;
    408     const uint8_t* gPtr = (const uint8_t*) gFont.gArray;
    409 
    410 #ifdef FONT_FS_AVAILABLE
    411     if (fs_font)
    412     {
    413       fontFile.seek(gBitmap[gNum], fs::SeekSet);
    414       pbuffer =  (uint8_t*)malloc(gWidth[gNum]);
    415     }
    416 #endif
    417 
    418     int16_t cy = cursor_y + gFont.maxAscent - gdY[gNum];
    419     int16_t cx = cursor_x + gdX[gNum];
    420 
    421     //  if (cx > width() && bg_cursor_x > width()) return;
    422     //  if (cursor_y > height()) return;
    423 
    424     int16_t  fxs = cx;
    425     uint32_t fl = 0;
    426     int16_t  bxs = cx;
    427     uint32_t bl = 0;
    428     int16_t  bx = 0;
    429     uint8_t pixel;
    430 
    431     startWrite(); // Avoid slow ESP32 transaction overhead for every pixel
    432 
    433     int16_t fillwidth  = 0;
    434     int16_t fillheight = 0;
    435 
    436     // Fill area above glyph
    437     if (_fillbg) {
    438       fillwidth  = (cursor_x + gxAdvance[gNum]) - bg_cursor_x;
    439       if (fillwidth > 0) {
    440         fillheight = gFont.maxAscent - gdY[gNum];
    441         // Could be negative
    442         if (fillheight > 0) {
    443           fillRect(bg_cursor_x, cursor_y, fillwidth, fillheight, textbgcolor);
    444         }
    445       }
    446       else {
    447         // Could be negative
    448         fillwidth = 0;
    449       }
    450 
    451       // Fill any area to left of glyph                              
    452       if (bg_cursor_x < cx) fillRect(bg_cursor_x, cy, cx - bg_cursor_x, gHeight[gNum], textbgcolor);
    453       // Set x position in glyph area where background starts
    454       if (bg_cursor_x > cx) bx = bg_cursor_x - cx;
    455       // Fill any area to right of glyph
    456       if (cx + gWidth[gNum] < cursor_x + gxAdvance[gNum]) {
    457         fillRect(cx + gWidth[gNum], cy, (cursor_x + gxAdvance[gNum]) - (cx + gWidth[gNum]), gHeight[gNum], textbgcolor);
    458       }
    459     }
    460 
    461     for (int32_t y = 0; y < gHeight[gNum]; y++)
    462     {
    463 #ifdef FONT_FS_AVAILABLE
    464       if (fs_font) {
    465         if (spiffs)
    466         {
    467           fontFile.read(pbuffer, gWidth[gNum]);
    468           //Serial.println("SPIFFS");
    469         }
    470         else
    471         {
    472           endWrite();    // Release SPI for SD card transaction
    473           fontFile.read(pbuffer, gWidth[gNum]);
    474           startWrite();  // Re-start SPI for TFT transaction
    475           //Serial.println("Not SPIFFS");
    476         }
    477       }
    478 #endif
    479 
    480       for (int32_t x = 0; x < gWidth[gNum]; x++)
    481       {
    482 #ifdef FONT_FS_AVAILABLE
    483         if (fs_font) pixel = pbuffer[x];
    484         else
    485 #endif
    486         pixel = pgm_read_byte(gPtr + gBitmap[gNum] + x + gWidth[gNum] * y);
    487 
    488         if (pixel)
    489         {
    490           if (bl) { drawFastHLine( bxs, y + cy, bl, bg); bl = 0; }
    491           if (pixel != 0xFF)
    492           {
    493             if (fl) {
    494               if (fl==1) drawPixel(fxs, y + cy, fg);
    495               else drawFastHLine( fxs, y + cy, fl, fg);
    496               fl = 0;
    497             }
    498             if (getColor) bg = getColor(x + cx, y + cy);
    499             drawPixel(x + cx, y + cy, alphaBlend(pixel, fg, bg));
    500           }
    501           else
    502           {
    503             if (fl==0) fxs = x + cx;
    504             fl++;
    505           }
    506         }
    507         else
    508         {
    509           if (fl) { drawFastHLine( fxs, y + cy, fl, fg); fl = 0; }
    510           if (_fillbg) {
    511             if (x >= bx) {
    512               if (bl==0) bxs = x + cx;
    513               bl++;
    514             }
    515           }
    516         }
    517       }
    518       if (fl) { drawFastHLine( fxs, y + cy, fl, fg); fl = 0; }
    519       if (bl) { drawFastHLine( bxs, y + cy, bl, bg); bl = 0; }
    520     }
    521 
    522     // Fill area below glyph
    523     if (fillwidth > 0) {
    524       fillheight = (cursor_y + gFont.yAdvance) - (cy + gHeight[gNum]);
    525       if (fillheight > 0) {
    526         fillRect(bg_cursor_x, cy + gHeight[gNum], fillwidth, fillheight, textbgcolor);
    527       }
    528     }
    529 
    530     if (pbuffer) free(pbuffer);
    531     cursor_x += gxAdvance[gNum];
    532     endWrite();
    533   }
    534   else
    535   {
    536     // Point code not in font so draw a rectangle and move on cursor
    537     drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
    538     cursor_x += gFont.spaceWidth + 1;
    539   }
    540   bg_cursor_x = cursor_x;
    541   last_cursor_x = cursor_x;
    542 }
    543 
    544 /***************************************************************************************
    545 ** Function name:           showFont
    546 ** Description:             Page through all characters in font, td ms between screens
    547 *************************************************************************************x*/
    548 void TFT_eSPI::showFont(uint32_t td)
    549 {
    550   if(!fontLoaded) return;
    551 
    552   int16_t cursorX = width(); // Force start of new page to initialise cursor
    553   int16_t cursorY = height();// for the first character
    554   uint32_t timeDelay = 0;    // No delay before first page
    555 
    556   fillScreen(textbgcolor);
    557   
    558   for (uint16_t i = 0; i < gFont.gCount; i++)
    559   {
    560     // Check if this will need a new screen
    561     if (cursorX + gdX[i] + gWidth[i] >= width())  {
    562       cursorX = -gdX[i];
    563 
    564       cursorY += gFont.yAdvance;
    565       if (cursorY + gFont.maxAscent + gFont.descent >= height()) {
    566         cursorX = -gdX[i];
    567         cursorY = 0;
    568         delay(timeDelay);
    569         timeDelay = td;
    570         fillScreen(textbgcolor);
    571       }
    572     }
    573 
    574     setCursor(cursorX, cursorY);
    575     drawGlyph(gUnicode[i]);
    576     cursorX += gxAdvance[i];
    577     yield();
    578   }
    579 
    580   delay(timeDelay);
    581   fillScreen(textbgcolor);
    582 }