acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.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 }