forked from Bodmer/TFT_eSPI
Add smooth (antialiased) fonts
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
// Coded by Bodmer 10/2/18, see license in root directory.
|
||||
// This is part of the TFT_eSPI class and is associated with anti-aliased font functions
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// New anti-aliased (smoothed) font functions added below
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: loadFont
|
||||
** Description: loads parameters from a new font vlw file stored in SPIFFS
|
||||
*************************************************************************************x*/
|
||||
void TFT_eSPI::loadFont(String fontName)
|
||||
{
|
||||
/*
|
||||
The vlw font format does not appear to be documented anywhere, so some reverse
|
||||
engineering has been applied!
|
||||
|
||||
Header of vlw file comprises 6 uint32_t parameters (24 bytes total):
|
||||
1. The gCount (number of character glyphs)
|
||||
2. A version number (0xB = 11 for the one I am using)
|
||||
3. The font size (in points, not pixels)
|
||||
4. Deprecated mboxY parameter (typically set to 0)
|
||||
5. Ascent in pixels from baseline to top of "d"
|
||||
6. Descent in pixels from baseline to bottom of "p"
|
||||
|
||||
Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes):
|
||||
1. Glyph Unicode stored as a 32 bit value
|
||||
2. Height of bitmap bounding box
|
||||
3. Width of bitmap bounding box
|
||||
4. gxAdvance for cursor (setWidth in Processing)
|
||||
5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
|
||||
6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
|
||||
7. padding value, typically 0
|
||||
|
||||
The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file.
|
||||
Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from
|
||||
0xFF foreground colour, 0x00 background. The sketch uses a linear interpolation
|
||||
between the foreground and background RGB component colours. e.g.
|
||||
pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255
|
||||
To gain a performance advantage fixed point arithmetic is used with rounding and
|
||||
division by 256 (shift right 8 bits is faster).
|
||||
|
||||
After the bitmaps is:
|
||||
1 byte for font name string length (excludes null)
|
||||
a zero terminated character string giving the font name
|
||||
1 byte for Postscript name string length
|
||||
a zero/one terminated character string giving the font name
|
||||
last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed)
|
||||
|
||||
Then the font name seen by Java when it's created
|
||||
Then the postscript name of the font
|
||||
Then a boolean to tell if smoothing is on or not.
|
||||
|
||||
Glyph bitmap example is:
|
||||
// Cursor coordinate positions for this and next character are marked by 'C'
|
||||
// C<------- gxAdvance ------->C gxAdvance is how far to move cursor for next glyph cursor position
|
||||
// | |
|
||||
// | | ascent is top of "d", descent is bottom of "p"
|
||||
// +-- gdX --+ ascent
|
||||
// | +-- gWidth--+ | gdX is offset to left edge of glyph bitmap
|
||||
// | + x@.........@x + | gdX may be negative e.g. italic "y" tail extending to left of
|
||||
// | | @@.........@@ | | cursor position, plot top left corner of bitmap at (cursorX + gdX)
|
||||
// | | @@.........@@ gdY | gWidth and gHeight are glyph bitmap dimensions
|
||||
// | | .@@@.....@@@@ | |
|
||||
// | gHeight ....@@@@@..@@ + + <-- baseline
|
||||
// | | ...........@@ |
|
||||
// | | ...........@@ | gdY is the offset to the top edge of the bitmap
|
||||
// | | .@@.......@@. descent plot top edge of bitmap at (cursorY + yAdvance - gdY)
|
||||
// | + x..@@@@@@@..x | x marks the corner pixels of the bitmap
|
||||
// | |
|
||||
// +---------------------------+ yAdvance is y delta for the next line, font size or (ascent + descent)
|
||||
// some fonts can overlay in y direction so may need a user adjust value
|
||||
|
||||
*/
|
||||
|
||||
_gFontFilename = "/" + fontName + ".vlw";
|
||||
|
||||
fontFile = SPIFFS.open( _gFontFilename, "r");
|
||||
|
||||
if(!fontFile) return;
|
||||
|
||||
//unloadFont();
|
||||
|
||||
fontFile.seek(0, fs::SeekSet);
|
||||
|
||||
gFont.gCount = (uint16_t)readInt32(); // glyph count in file
|
||||
readInt32(); // vlw encoder version - discard
|
||||
gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
|
||||
readInt32(); // discard
|
||||
gFont.ascent = (uint16_t)readInt32(); // top of "d"
|
||||
gFont.descent = (uint16_t)readInt32(); // bottom of "p"
|
||||
|
||||
// These next gFont values will be updated when the Metrics are fetched
|
||||
gFont.maxAscent = gFont.ascent; // Determined from metrics
|
||||
gFont.maxDescent = gFont.descent; // Determined from metrics
|
||||
gFont.yAdvance = gFont.ascent + gFont.descent;
|
||||
gFont.spaceWidth = gFont.yAdvance / 4; // Guess at space width
|
||||
|
||||
fontLoaded = true;
|
||||
|
||||
// Fetch the metrics for each glyph
|
||||
loadMetrics(gFont.gCount);
|
||||
|
||||
//fontFile.close();
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: loadMetrics
|
||||
** Description: Get the metrics for each glyph and store in RAM
|
||||
*************************************************************************************x*/
|
||||
//#define SHOW_ASCENT_DESCENT
|
||||
void TFT_eSPI::loadMetrics(uint16_t gCount)
|
||||
{
|
||||
uint32_t headerPtr = 24;
|
||||
uint32_t bitmapPtr = 24 + gCount * 28;
|
||||
|
||||
gUnicode = (uint16_t*)malloc( gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
|
||||
gHeight = (uint8_t*)malloc( gCount ); // Height of glyph
|
||||
gWidth = (uint8_t*)malloc( gCount ); // Width of glyph
|
||||
gxAdvance = (uint8_t*)malloc( gCount ); // xAdvance - to move x cursor
|
||||
gdY = (int8_t*)malloc( gCount ); // offset from bitmap top edge from lowest point in any character
|
||||
gdX = (int8_t*)malloc( gCount ); // offset for bitmap left edge relative to cursor X
|
||||
gBitmap = (uint32_t*)malloc( gCount * 4); // seek pointer to glyph bitmap in SPIFFS file
|
||||
|
||||
#ifdef SHOW_ASCENT_DESCENT
|
||||
Serial.print("ascent = "); Serial.println(gFont.ascent);
|
||||
Serial.print("descent = "); Serial.println(gFont.descent);
|
||||
#endif
|
||||
|
||||
uint16_t gNum = 0;
|
||||
fontFile.seek(headerPtr, fs::SeekSet);
|
||||
while (gNum < gCount)
|
||||
{
|
||||
gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value
|
||||
gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph
|
||||
gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph
|
||||
gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor
|
||||
gdY[gNum] = (int8_t)readInt32(); // y delta from baseline
|
||||
gdX[gNum] = (int8_t)readInt32(); // x delta from cursor
|
||||
readInt32(); // ignored
|
||||
|
||||
// Different glyph sets have different ascent values not always based on "d", so get maximum glyph ascent
|
||||
if (gdY[gNum] > gFont.maxAscent)
|
||||
{
|
||||
// Avoid UTF coding values and characters that tend to give duff values
|
||||
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF))
|
||||
{
|
||||
gFont.maxAscent = gdY[gNum];
|
||||
#ifdef SHOW_ASCENT_DESCENT
|
||||
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent = "); Serial.println(gFont.maxAscent);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
|
||||
if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent)
|
||||
{
|
||||
// Avoid UTF coding values and characters that tend to give duff values
|
||||
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF))
|
||||
{
|
||||
gFont.maxDescent = gHeight[gNum] - gdY[gNum];
|
||||
#ifdef SHOW_ASCENT_DESCENT
|
||||
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
gBitmap[gNum] = bitmapPtr;
|
||||
|
||||
headerPtr += 28;
|
||||
|
||||
bitmapPtr += gWidth[gNum] * gHeight[gNum];
|
||||
|
||||
gNum++;
|
||||
yield();
|
||||
}
|
||||
|
||||
gFont.yAdvance = gFont.maxAscent + gFont.maxDescent;
|
||||
|
||||
gFont.spaceWidth = (gFont.ascent + gFont.descent) * 2/7; // Guess at space width
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: deleteMetrics
|
||||
** Description: Delete the old glyph metrics and free up the memory
|
||||
*************************************************************************************x*/
|
||||
void TFT_eSPI::unloadFont( void )
|
||||
{
|
||||
if (gUnicode)
|
||||
{
|
||||
free(gUnicode);
|
||||
gUnicode = NULL;
|
||||
}
|
||||
|
||||
if (gHeight)
|
||||
{
|
||||
free(gHeight);
|
||||
gHeight = NULL;
|
||||
}
|
||||
|
||||
if (gWidth)
|
||||
{
|
||||
free(gWidth);
|
||||
gWidth = NULL;
|
||||
}
|
||||
|
||||
if (gxAdvance)
|
||||
{
|
||||
free(gxAdvance);
|
||||
gxAdvance = NULL;
|
||||
}
|
||||
|
||||
if (gdY)
|
||||
{
|
||||
free(gdY);
|
||||
gdY = NULL;
|
||||
}
|
||||
|
||||
if (gdX)
|
||||
{
|
||||
free(gdX);
|
||||
gdX = NULL;
|
||||
}
|
||||
|
||||
if (gBitmap)
|
||||
{
|
||||
free(gBitmap);
|
||||
gBitmap = NULL;
|
||||
}
|
||||
fontFile.close();
|
||||
fontLoaded = false;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: decodeUTF8
|
||||
** Description: Line buffer UTF-8 decoder with fall-back to extended ASCII
|
||||
*************************************************************************************x*/
|
||||
#define DECODE_UTF8
|
||||
uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining)
|
||||
{
|
||||
byte c = buf[(*index)++];
|
||||
//Serial.print("Byte from string = 0x"); Serial.println(c, HEX);
|
||||
|
||||
#ifdef DECODE_UTF8
|
||||
// 7 bit Unicode
|
||||
if ((c & 0x80) == 0x00) return c;
|
||||
|
||||
// 11 bit Unicode
|
||||
if (((c & 0xE0) == 0xC0) && (remaining > 1))
|
||||
return ((c & 0x1F)<<6) | (buf[(*index)++]&0x3F);
|
||||
|
||||
// 16 bit Unicode
|
||||
if (((c & 0xF0) == 0xE0) && (remaining > 2))
|
||||
return ((c & 0x0F)<<12) | ((buf[(*index)++]&0x3F)<<6) | ((buf[(*index)++]&0x3F));
|
||||
|
||||
// 21 bit Unicode not supported so fall-back to extended ASCII
|
||||
// if ((c & 0xF8) == 0xF0) return c;
|
||||
#endif
|
||||
|
||||
return c; // fall-back to extended ASCII
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: decodeUTF8
|
||||
** Description: Serial UTF-8 decoder with fall-back to extended ASCII
|
||||
*************************************************************************************x*/
|
||||
uint16_t TFT_eSPI::decodeUTF8(uint8_t c)
|
||||
{
|
||||
|
||||
#ifdef DECODE_UTF8
|
||||
if (decoderState == 0)
|
||||
{
|
||||
// 7 bit Unicode
|
||||
if ((c & 0x80) == 0x00) return (uint16_t)c;
|
||||
|
||||
// 11 bit Unicode
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
decoderBuffer = ((c & 0x1F)<<6);
|
||||
decoderState = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 16 bit Unicode
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
decoderBuffer = ((c & 0x0F)<<12);
|
||||
decoderState = 2;
|
||||
return 0;
|
||||
}
|
||||
// 21 bit Unicode not supported so fall-back to extended ASCII
|
||||
if ((c & 0xF8) == 0xF0) return (uint16_t)c;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decoderState == 2)
|
||||
{
|
||||
decoderBuffer |= ((c & 0x3F)<<6);
|
||||
decoderState--;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoderBuffer |= (c & 0x3F);
|
||||
decoderState = 0;
|
||||
return decoderBuffer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return (uint16_t)c; // fall-back to extended ASCII
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: alphaBlend
|
||||
** Description: Blend foreground and background and return new colour
|
||||
*************************************************************************************x*/
|
||||
uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc)
|
||||
{
|
||||
// For speed use fixed point maths and rounding to permit a power of 2 division
|
||||
uint16_t fgR = ((fgc >> 10) & 0x3E) + 1;
|
||||
uint16_t fgG = ((fgc >> 4) & 0x7E) + 1;
|
||||
uint16_t fgB = ((fgc << 1) & 0x3E) + 1;
|
||||
|
||||
uint16_t bgR = ((bgc >> 10) & 0x3E) + 1;
|
||||
uint16_t bgG = ((bgc >> 4) & 0x7E) + 1;
|
||||
uint16_t bgB = ((bgc << 1) & 0x3E) + 1;
|
||||
|
||||
// Shift right 1 to drop rounding bit and shift right 8 to divide by 256
|
||||
uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
|
||||
uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
|
||||
uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
|
||||
|
||||
// Combine RGB565 colours into 16 bits
|
||||
return (r << 11) | (g << 5) | (b << 0);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: readInt32
|
||||
** Description: Get a 32 bit integer from the font file
|
||||
*************************************************************************************x*/
|
||||
uint32_t TFT_eSPI::readInt32(void)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
val |= fontFile.read() << 24;
|
||||
val |= fontFile.read() << 16;
|
||||
val |= fontFile.read() << 8;
|
||||
val |= fontFile.read();
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: getUnicodeIndex
|
||||
** Description: Get the font file index of a Unicode character
|
||||
*************************************************************************************x*/
|
||||
bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index)
|
||||
{
|
||||
for (uint16_t i = 0; i < gFont.gCount; i++)
|
||||
{
|
||||
if (gUnicode[i] == unicode)
|
||||
{
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: drawGlyph
|
||||
** Description: Write a character to the TFT cursor position
|
||||
*************************************************************************************x*/
|
||||
// Expects file to be open
|
||||
void TFT_eSPI::drawGlyph(uint16_t code)
|
||||
{
|
||||
if (code < 0x21)
|
||||
{
|
||||
if (code == 0x20) {
|
||||
cursor_x += gFont.spaceWidth;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == '\n') {
|
||||
cursor_x = 0;
|
||||
cursor_y += gFont.yAdvance;
|
||||
if (cursor_y >= _height) cursor_y = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t gNum = 0;
|
||||
bool found = getUnicodeIndex(code, &gNum);
|
||||
|
||||
uint16_t fg = textcolor;
|
||||
uint16_t bg = textbgcolor;
|
||||
|
||||
if (found)
|
||||
{
|
||||
|
||||
if (textwrapX && (cursor_x + gWidth[gNum] + gdX[gNum] > _width))
|
||||
{
|
||||
cursor_y += gFont.yAdvance;
|
||||
cursor_x = 0;
|
||||
}
|
||||
if (textwrapY && ((cursor_y + gFont.yAdvance) >= _height)) cursor_y = 0;
|
||||
if (cursor_x == 0) cursor_x -= gdX[gNum];
|
||||
|
||||
fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is taking >30ms for a significant position shift
|
||||
|
||||
uint8_t pbuffer[gWidth[gNum]];
|
||||
|
||||
uint16_t xs = 0;
|
||||
uint16_t dl = 0;
|
||||
|
||||
for (int y = 0; y < gHeight[gNum]; y++)
|
||||
{
|
||||
fontFile.read(pbuffer, gWidth[gNum]); //<//
|
||||
for (int x = 0; x < gWidth[gNum]; x++)
|
||||
{
|
||||
uint8_t pixel = pbuffer[x]; //<//
|
||||
if (pixel)
|
||||
{
|
||||
if (pixel != 0xFF)
|
||||
{
|
||||
if (dl) { drawFastHLine( xs, y + cursor_y + gFont.maxAscent - gdY[gNum], dl, fg); dl = 0; }
|
||||
drawPixel(x + cursor_x + gdX[gNum], y + cursor_y + gFont.maxAscent - gdY[gNum], alphaBlend(pixel, fg, bg));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dl==0) xs = x + cursor_x + gdX[gNum];
|
||||
dl++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dl) { drawFastHLine( xs, y + cursor_y + gFont.maxAscent - gdY[gNum], dl, fg); dl = 0; }
|
||||
}
|
||||
}
|
||||
if (dl) { drawFastHLine( xs, y + cursor_y + gFont.maxAscent - gdY[gNum], dl, fg); dl = 0; }
|
||||
}
|
||||
|
||||
|
||||
cursor_x += gxAdvance[gNum];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a Unicode in font so draw a rectangle and move on cursor
|
||||
drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
|
||||
cursor_x += gFont.spaceWidth + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: showFont
|
||||
** Description: Page through all characters in font, td ms between screens
|
||||
*************************************************************************************x*/
|
||||
void TFT_eSPI::showFont(uint32_t td)
|
||||
{
|
||||
if(!fontLoaded) return;
|
||||
// fontFile = SPIFFS.open( _gFontFilename, "r" );
|
||||
|
||||
if(!fontFile)
|
||||
{
|
||||
fontLoaded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t cursorX = width(); // Force start of new page to initialise cursor
|
||||
int16_t cursorY = height();// for the first character
|
||||
uint32_t timeDelay = 0; // No delay before first page
|
||||
|
||||
fillScreen(textbgcolor);
|
||||
|
||||
for (uint16_t i = 0; i < gFont.gCount; i++)
|
||||
{
|
||||
// Check if this will need a new screen
|
||||
if (cursorX + gdX[i] + gWidth[i] >= width()) {
|
||||
cursorX = -gdX[i];
|
||||
|
||||
cursorY += gFont.yAdvance;
|
||||
if (cursorY + gFont.maxAscent + gFont.descent >= height()) {
|
||||
cursorX = -gdX[i];
|
||||
cursorY = 0;
|
||||
delay(timeDelay);
|
||||
timeDelay = td;
|
||||
fillScreen(textbgcolor);
|
||||
}
|
||||
}
|
||||
|
||||
setCursor(cursorX, cursorY);
|
||||
drawGlyph(gUnicode[i]);
|
||||
cursorX += gxAdvance[i];
|
||||
//cursorX += printToSprite( cursorX, cursorY, i );
|
||||
yield();
|
||||
}
|
||||
|
||||
delay(timeDelay);
|
||||
fillScreen(textbgcolor);
|
||||
//fontFile.close();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user