ssd1306 icon indicating copy to clipboard operation
ssd1306 copied to clipboard

How to get width in pixel of a string to print -> right alignment :-)

Open RoboDurden opened this issue 4 years ago • 22 comments

Took me some time to achieve right alignment of a float. You might like to add these functions to your code. Or may be happy to tell me that such functions already exist :-)

int GetWidth(const char s[],const uint8_t* aFont, char cDot=0)
{
  if (!strlen(s)) return 0;

// why does program crash with the following line ?
  //SCharInfo char_info;  ssd1306_getCharBitmap('.', &char_info);

  uint8_t iFrom = (uint8_t)pgm_read_word(&(aFont[5]));
  uint8_t iTo = iFrom + (uint8_t)pgm_read_word(&(aFont[6]));
  uint16_t  iWidth = (strlen(s)-1) * 1; //char_info.spacing;

  uint16_t i = 0;
  while(s[i])
  {
    uint8_t c = s[i];
    if (  (c>=iFrom) && (c<iTo)  )
      iWidth += (uint8_t)pgm_read_word(&(aFont[9 + (c-iFrom)*4]));
    if (s[i++]==cDot) break;
  }
  return iWidth;
}

// the width in aFont[2] is not correct - bug ?
int GetMaxWidth(const uint8_t* aFont)
{
  uint8_t iWidth = 0;
  uint8_t iNum = (uint8_t)pgm_read_word(&(aFont[6]));
  for (int i=0; i<iNum; i++)
  {
    uint8_t iW = (uint8_t)pgm_read_word(&(aFont[9 + i*4]));
    if (iWidth < iW)  iWidth = iW;
  }
  return iWidth;
}
inline uint8_t GetMaxHeight(const uint8_t* aFont){return (uint8_t)pgm_read_word(&(aFont[2]));};

RoboDurden avatar Jul 28 '19 13:07 RoboDurden

Hi :) What about lcduint_t ssd1306_getTextSize(const char *text, lcduint_t *height);? Could you please describe in details what you're trying to achieve.

lexus2k avatar Jul 28 '19 23:07 lexus2k

Yes thank you, i should take these few minutes to study your api ;-) You might still rethink my

// the width in aFont[2] is not correct - bug ?
int GetMaxWidth(const uint8_t* aFont)

I think your fontgenerator.py puts the wrong width in byte 3 when the asc chars are restricted from say 43 - 57.

This is my helper function to right align a float (based on the '.')(including an optional label):

boolean DisplayFloat(int x, int y, int iWidthWindow, char* sLabel,float f, int iPrec=1,
                      byte iSize=2,uint16_t color = RGB_COLOR8(255,255,255))
{
  int x0 = x;
  int iWidth;
  int iWidthLabel;
  int iWidthFloat;
  const uint8_t* aFont;
  const uint8_t* aFontLabel;
  char s[11];  // max number before mcu crashes : '+23456.890' + 0x00
  boolean bNoLastTry = true;
  do
  {
    switch(iSize)
    {
    case 1: aFont = free_Targa8x16;  aFontLabel = free_Targa8x16; break;
    case 2: aFont = free_Targa11x24;  aFontLabel = free_Targa11x24; break;
    case 3: aFont = free_comicbd26x29;  aFontLabel = free_Targa11x24; break;
    }

    dtostrf(f, 1, iPrec, s);
    iWidthFloat = GetWidth(s,aFont);
    int iWidthInt = GetWidth(s,aFont,'.');  // width from left including '.'
    uint8_t iMaxW = GetMaxWidth(aFont);

    iWidth = bNoLastTry ? iWidthInt+iPrec*iMaxW : iWidthFloat;
    iWidthLabel = GetWidth(sLabel,aFontLabel);
    if (iWidthLabel+iWidth > iWidthWindow)
    {
      if (iPrec > 0)  iPrec--;
      else if (iSize > 1) iSize--;
      else if (iWidthLabel) sLabel[0] = 0;
      else if (bNoLastTry) bNoLastTry = false;
      else return false; // :-/
    }
    else  break;  // :-)
  } while (1);
  
  if (iWidthLabel)
  {
    ssd1306_setColor(RGB_COLOR8(255,255,0));
    ssd1306_setFreeFont(aFontLabel);
    ssd1306_setCursor8(x, y); 
    ssd1306_print8(sLabel);
    x += iWidthLabel;
  }

  uint8_t iMaxH = GetMaxHeight(aFont);
  int iWidthEmpty = iWidthWindow-(iWidthLabel+iWidth); 
  if (iWidthEmpty>0)
  {
    ssd1306_setColor(RGB_COLOR8(0,0,0));
    ssd1306_fillRect8(x,y,x+iWidthEmpty,y+iMaxH);    
    x +=  iWidthEmpty;
  }

  ssd1306_setFreeFont(aFont);
  ssd1306_setColor(color);
  ssd1306_setCursor8(x, y);
  ssd1306_print8(s);
  x += iWidthFloat;
  if (x-x0 < iWidthWindow)
  {
    ssd1306_setColor(RGB_COLOR8(0,0,0));
    ssd1306_fillRect8(x,y,x0+iWidthWindow-1,y+iMaxH);    
  }
  return true;
}

Feel free to adapt it for your library.

RoboDurden avatar Jul 29 '19 08:07 RoboDurden

Good day,

I think your fontgenerator.py puts the wrong width in byte 3 when the asc chars are restricted from say 43 - 57.

This may happen because your font size (43) is too small to reach 57 pixels in height. Can you send me the full command line for your case?

boolean DisplayFloat(int x, int y, int iWidthWindow, char* sLabel,float f, int iPrec=1, byte iSize=2,uint16_t color = RGB_COLOR8(255,255,255))

The library has C-style API, thus default args are not applicable. In any case, float for uC (AVR) gives too much overhead. I will figure out what it is possible to use from the code above.

lexus2k avatar Jul 30 '19 04:07 lexus2k

it happens for the windows Comic Sans Bold font:

python fontgenerator.py --ttf fonts/comicbd.ttf -s 10 -SB 16 -g 43 14 -f new -d > ready/1193851778.h

// ----@@----@@@@-------@@@@----------
// ---@@@---@@@@@@-----@@@@@@---------
// ---@-@---@@--@@-----@@--@@---------
// --@@-@-------@@----@@----@@--------
// -@@--@------@@-----@@----@@--------
// @@@@@@@@--@@@------@@----@@--@@@@--
// @@@@@@@@-@@@-------@@----@@--@@@@--
// -----@---@@---------@@--@@---------
// -----@---@@@@@@-@@--@@@@@@---------
// -----@---@@@@@@-@@---@@@@----------
// -----------------------------------
// -----------------------------------
extern const uint8_t free_comicbd10x12[] PROGMEM;
const uint8_t free_comicbd10x12[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x0A, 0x0C, 0x00,
// GROUP first '+' total 15 chars
//  unicode(LSB,MSB)|count
    0x00, 0x2B, 0x0F, // unicode record
    0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43)
    0x00, 0x0C, 0x03, 0x0C, // char ',' (0x002C/44)
    0x00, 0x12, 0x06, 0x07, // char '-' (0x002D/45)
    0x00, 0x18, 0x02, 0x0A, // char '.' (0x002E/46)
    0x00, 0x1C, 0x06, 0x0B, // char '/' (0x002F/47)
    0x00, 0x28, 0x08, 0x0A, // char '0' (0x0030/48)
    0x00, 0x38, 0x05, 0x0A, // char '1' (0x0031/49)
    0x00, 0x42, 0x06, 0x0A, // char '2' (0x0032/50)
    0x00, 0x4E, 0x06, 0x0A, // char '3' (0x0033/51)
    0x00, 0x5A, 0x08, 0x0A, // char '4' (0x0034/52)
    0x00, 0x6A, 0x07, 0x0A, // char '5' (0x0035/53)
    0x00, 0x78, 0x06, 0x0A, // char '6' (0x0036/54)
    0x00, 0x84, 0x08, 0x0A, // char '7' (0x0037/55)
    0x00, 0x94, 0x07, 0x0A, // char '8' (0x0038/56)
    0x00, 0xA2, 0x07, 0x0A, // char '9' (0x0039/57)
    0x00, 0xB0,
    0x60, 0x60, 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, // char '+' (0x002B/43)
    0x00, 0x00, 0x00, 0x08, 0x0E, 0x02, // char ',' (0x002C/44)
    0x00, 0x60, 0x60, 0x60, 0x60, 0x00, // char '-' (0x002D/45)
    0x00, 0x00, 0x03, 0x03, // char '.' (0x002E/46)
    0x00, 0x80, 0xC0, 0x70, 0x1E, 0x07, 0x04, 0x07, 0x01, 0x00, 0x00, 0x00, // char '/' (0x002F/47)
    0x78, 0xFE, 0x87, 0x03, 0x03, 0x87, 0xFE, 0x78, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, // char '0' (0x0030/48)
    0x00, 0x06, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, // char '1' (0x0031/49)
    0xC6, 0xE7, 0x63, 0x33, 0x1F, 0x0E, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // char '2' (0x0032/50)
    0x86, 0x37, 0x33, 0x33, 0xFF, 0xEE, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, // char '3' (0x0033/51)
    0x60, 0x70, 0x78, 0x6E, 0x63, 0xFF, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // char '4' (0x0034/52)
    0x9F, 0x1F, 0x1B, 0x1B, 0x9B, 0xF3, 0x60, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, // char '5' (0x0035/53)
    0xF8, 0xBC, 0x36, 0x33, 0xB0, 0xE0, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, // char '6' (0x0036/54)
    0x03, 0x03, 0x83, 0xF3, 0x3B, 0x0F, 0x07, 0x03, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0x00, 0xFE, 0x7F, 0x33, 0x33, 0xFF, 0xEE, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, // char '8' (0x0038/56)
    0x1C, 0x3E, 0x63, 0x63, 0xE3, 0xF7, 0x7E, 0x00, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, // char '9' (0x0039/57)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 248 BYTES
};

You can also select the public domain font Black_Chancery:

python fontgenerator.py --ttf public/Black_Chancery.ttf -s 10 -SB 16 -g 43 14 -f new -d > ready/9521772712.h

// --------------------------------
// --------------------------------
// --------------------------------
// ---@@---@@@@--------@@@---------
// --@-@---@--@-------@--@---@@@@--
// --@-@---@--@------@@--@@-@@@@---
// -@--@-----@-------@@--@@--------
// @---@---@@-----@---@--@---------
// -@@@@@-@@@@@--@@@--@@@----------
// ----@----------@----------------
// ----@---------------------------
// --------------------------------
// --------------------------------
extern const uint8_t free_Black_Chancery8x13[] PROGMEM;
const uint8_t free_Black_Chancery8x13[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x08, 0x0D, 0x00,
// GROUP first '+' total 15 chars
//  unicode(LSB,MSB)|count
    0x00, 0x2B, 0x0F, // unicode record
    0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43)
    0x00, 0x0C, 0x01, 0x0A, // char ',' (0x002C/44)
    0x00, 0x0E, 0x06, 0x06, // char '-' (0x002D/45)
    0x00, 0x14, 0x03, 0x0A, // char '.' (0x002E/46)
    0x00, 0x1A, 0x06, 0x0A, // char '/' (0x002F/47)
    0x00, 0x26, 0x06, 0x09, // char '0' (0x0030/48)
    0x00, 0x32, 0x02, 0x09, // char '1' (0x0031/49)
    0x00, 0x36, 0x06, 0x09, // char '2' (0x0032/50)
    0x00, 0x42, 0x05, 0x0B, // char '3' (0x0033/51)
    0x00, 0x4C, 0x06, 0x0B, // char '4' (0x0034/52)
    0x00, 0x58, 0x06, 0x0B, // char '5' (0x0035/53)
    0x00, 0x64, 0x06, 0x09, // char '6' (0x0036/54)
    0x00, 0x70, 0x07, 0x0B, // char '7' (0x0037/55)
    0x00, 0x7E, 0x05, 0x09, // char '8' (0x0038/56)
    0x00, 0x88, 0x06, 0x0C, // char '9' (0x0039/57)
    0x00, 0x94,
    0x00, 0x10, 0x78, 0x14, 0x30, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // char '+' (0x002B/43)
    0x80, 0x03, // char ',' (0x002C/44)
    0x20, 0x30, 0x30, 0x30, 0x10, 0x00, // char '-' (0x002D/45)
    0x00, 0x80, 0x00, 0x01, 0x03, 0x01, // char '.' (0x002E/46)
    0x00, 0x80, 0x60, 0x18, 0x07, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, // char '/' (0x002F/47)
    0x60, 0xF0, 0x08, 0x08, 0xF8, 0x60, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, // char '0' (0x0030/48)
    0xF8, 0xF8, 0x01, 0x01, // char '1' (0x0031/49)
    0x00, 0xB8, 0x88, 0x48, 0x38, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, // char '2' (0x0032/50)
    0x00, 0x08, 0x48, 0x68, 0xD8, 0x04, 0x04, 0x04, 0x04, 0x03, // char '3' (0x0033/51)
    0x80, 0x40, 0x30, 0x08, 0xF8, 0x00, 0x00, 0x01, 0x01, 0x01, 0x07, 0x01, // char '4' (0x0034/52)
    0x40, 0xB0, 0x48, 0x48, 0xC8, 0x80, 0x02, 0x04, 0x04, 0x04, 0x04, 0x03, // char '5' (0x0035/53)
    0x70, 0xFE, 0x13, 0x11, 0x30, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, // char '6' (0x0036/54)
    0x08, 0x88, 0x48, 0x28, 0x18, 0x08, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0xE6, 0x99, 0x11, 0x3B, 0xE6, 0x00, 0x01, 0x01, 0x01, 0x00, // char '8' (0x0038/56)
    0x30, 0xC8, 0x88, 0x08, 0xF8, 0xE0, 0x00, 0x00, 0x08, 0x04, 0x03, 0x00, // char '9' (0x0039/57)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 220 BYTES
};

You can download that ttf here: https://www.dafont.com/black-chancery.font

The library has C-style API, thus default args are not applicable. In any case, float for uC (AVR) gives too much overhead. I will figure out what it is possible to use from the code above.

My DisplayFloat was merely intended as a little gift. I saw that you offer a menu in your lib. So a quick and easy way to display floats might be another nice enhancement of your lib. I am not sure, but if the function does not get used in the main arduino code, it will not be linked ?

But i agree that my function is a bit too special as it maps iSize to a list of fonts. By now i also added different label fonts:

boolean DisplayFloat(int x, int y, int iWidthWindow, char* sLabel,float f, int iPrec=1,byte iSize=2,uint16_t color = RGB_COLOR8(255,255,255))
{
  int x0 = x;

  int iSizeLabel = iSize>2 ? 2 : iSize;
  int iWidth;
  int iWidthLabel;
  int iWidthFloat;
  const uint8_t* aFont;
  const uint8_t* aFontLabel;
  char s[11];  // max number before mcu crashes : '+23456.890' + 0x00
  boolean bNoLastTry = true;
  do
  {
    switch(iSizeLabel)
    {
    case 1: aFontLabel = free_Targa8x16; break;
    case 2: aFontLabel = free_Targa11x24; break;
    }
    switch(iSize)
    {
    case 1: aFont = free_Targa8x16; break;
    case 2: aFont = free_Targa11x24; break;
    case 3: aFont = free_comicbd26x29; break;
    }

    dtostrf(f, 1, iPrec, s);
    iWidthFloat = GetWidth(s,aFont);
    int iWidthInt = GetWidth(s,aFont,'.');  // width from left including '.'
    uint8_t iMaxW = GetMaxWidth(aFont);

    iWidth = bNoLastTry ? iWidthInt+iPrec*iMaxW : iWidthFloat;
    iWidthLabel = GetWidth(sLabel,aFontLabel);
    if (iWidthLabel+iWidth > iWidthWindow)
    {
      if (iPrec > 0)  iPrec--;
      else if (iSizeLabel > 1) iSizeLabel--;
      else if (iSize > 1) iSize--;
      else if (iWidthLabel) sLabel[0] = 0; //iWidthLabel = 0;
      else if (bNoLastTry) bNoLastTry = false;
      else return false; // :-/
    }
    else  break;  // :-)
  } while (1);
  
  if (iWidthLabel)
  {
    ssd1306_setColor(RGB_COLOR8(255,255,0));
    ssd1306_setFreeFont(aFontLabel);
    ssd1306_setCursor8(x, y); 
    ssd1306_print8(sLabel);
    x += iWidthLabel;
  }

  uint8_t iMaxH = GetMaxHeight(aFont);
  int iWidthEmpty = iWidthWindow-(iWidthLabel+iWidth); 
  if (iWidthEmpty>0)
  {
    ssd1306_setColor(RGB_COLOR8(0,0,0));
    ssd1306_fillRect8(x,y,x+iWidthEmpty,y+iMaxH);    
    x +=  iWidthEmpty;
  }

  ssd1306_setFreeFont(aFont);
  ssd1306_setColor(color);
  ssd1306_setCursor8(x, y);
  ssd1306_print8(s);
  x += iWidthFloat;
  if (x-x0 < iWidthWindow)
  {
    ssd1306_setColor(RGB_COLOR8(0,0,0));
    ssd1306_fillRect8(x,y,x0+iWidthWindow-1,y+iMaxH);    
  }
  return true;
}

It is a nice carefree function to quickly print float and int data to screen.

RoboDurden avatar Jul 30 '19 08:07 RoboDurden

Unfortunately, your ssd1306_printFixedN only prints garbage on my ST7735. And i would need a ssd1306_printN anyways.

-> bug or feature request :-)

RoboDurden avatar Jul 30 '19 09:07 RoboDurden

I'm sorry for that, I never had a time to implement ssd1306_printFixedN supporting all font sizes. It doesn't work because font size is greater than 8. Historically, ssd1306 library initially supported only 8-size fonts. And ssd1306_printFixedN() supports only monochrome displays (as far as I remember :) ).

I understand your point on including new function to the library, and I'm trying to have API for 1-bit, 8-bit, 16-bit modes consistent. So, before releasing new version, I need to implement the same API for all display types. My point was: that if somebody wants to use new API on AVR, it will be surprise. But anyway, this can be solved via documentation.

lexus2k avatar Jul 30 '19 11:07 lexus2k

As for font size. I wrote above:

This may happen because your font size (43) is too small to reach 57 pixels in height. Can you send me the full command line for your case?

So, if you use ./fontgenerator.py --ttf comicbd.ttf -s 13 -SB 16 -g 43 14 -f new -t "420=" -d > 1.c, the issue goes away. I increased font size in -s argument. Also, current behavior of the script can be updated in opposite way: to make SB argument too increase font size if it is too small.

lexus2k avatar Jul 30 '19 11:07 lexus2k

what is the -t option ? I still think this is small bug. When aFont[1] is 0x0A, then there must be a char with that width. In the following font (first from above), aFont[1] should have been set to 0x08

// type|width|height|first char 0x02, 0x0A, 0x0C, 0x00, // GROUP first '+' total 15 chars // unicode(LSB,MSB)|count 0x00, 0x2B, 0x0F, // unicode record 0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43) 0x00, 0x0C, 0x03, 0x0C, // char ',' (0x002C/44) 0x00, 0x12, 0x06, 0x07, // char '-' (0x002D/45) 0x00, 0x18, 0x02, 0x0A, // char '.' (0x002E/46) 0x00, 0x1C, 0x06, 0x0B, // char '/' (0x002F/47) 0x00, 0x28, 0x08, 0x0A, // char '0' (0x0030/48) 0x00, 0x38, 0x05, 0x0A, // char '1' (0x0031/49) 0x00, 0x42, 0x06, 0x0A, // char '2' (0x0032/50) 0x00, 0x4E, 0x06, 0x0A, // char '3' (0x0033/51) 0x00, 0x5A, 0x08, 0x0A, // char '4' (0x0034/52) 0x00, 0x6A, 0x07, 0x0A, // char '5' (0x0035/53) 0x00, 0x78, 0x06, 0x0A, // char '6' (0x0036/54) 0x00, 0x84, 0x08, 0x0A, // char '7' (0x0037/55) 0x00, 0x94, 0x07, 0x0A, // char '8' (0x0038/56) 0x00, 0xA2, 0x07, 0x0A, // char '9' (0x0039/57)

As your fontgenerator.py calculates the wrong aFont[1], i had to write my own function:

int GetMaxWidth(const uint8_t* aFont)
{
  uint8_t iWidth = 0;
  uint8_t iNum = (uint8_t)pgm_read_word(&(aFont[6]));
  for (int i=0; i<iNum; i++)
  {
    uint8_t iW = (uint8_t)pgm_read_word(&(aFont[9 + i*4]));
    if (iWidth < iW)  iWidth = iW;
  }
  return iWidth;
}

Your nice fontgenerator.py should always calculate the correct aFont[1] no matter what options are used.

RoboDurden avatar Jul 30 '19 15:07 RoboDurden

Hi,

can you check that python fontgenerator.py --ttf public/Black_Chancery.ttf -s 10 -SB 16 -g 43 14 -f new -d > ready/9521772712.h works for you now?

lexus2k avatar Aug 03 '19 07:08 lexus2k

i put the fontgenerator.py from master and 1.7_dev in my online compilers folder and no change, see below. But i admit that i do not know the syntax of your format. I assume the second byte should be the width of the char with the maximum width:

//  type|width|height|first char
    0x02, 0x08, 0x0D, 0x00,

The following three bit mark the beginning of the first char and the number of chars:

// GROUP first '+' total 15 chars
//  unicode(LSB,MSB)|count
    0x00, 0x2B, 0x0F, // unicode record

And then for every char 4 byte where the third byte is the width of that char:

    0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43)
    0x00, 0x0C, 0x01, 0x0A, // char ',' (0x002C/44)
    0x00, 0x0E, 0x06, 0x06, // char '-' (0x002D/45)
    0x00, 0x14, 0x03, 0x0A, // char '.' (0x002E/46)

Therefore i conclude that the second byte should be the maximum of these third bytes :-)

python fontgenerator.py --ttf public/Black_Chancery.ttf -s 10 -SB 16 -g 43 14 -f new -d > ready/417633350.h

master : 

// ----------
// ----------
// ----------
// --@@@--@@-
// -@--@--@@-
// @@--@@-@@-
// @@--@@-@@-
// -@--@--@@-
// -@@@---@@-
// ----------
// ----------
// ----------
// ----------
extern const uint8_t free_Black_Chancery8x13[] PROGMEM;
const uint8_t free_Black_Chancery8x13[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x08, 0x0D, 0x00,
// GROUP first '+' total 15 chars
//  unicode(LSB,MSB)|count
    0x00, 0x2B, 0x0F, // unicode record
    0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43)
    0x00, 0x0C, 0x01, 0x0A, // char ',' (0x002C/44)
    0x00, 0x0E, 0x06, 0x06, // char '-' (0x002D/45)
    0x00, 0x14, 0x03, 0x0A, // char '.' (0x002E/46)
    0x00, 0x1A, 0x06, 0x0A, // char '/' (0x002F/47)
    0x00, 0x26, 0x06, 0x09, // char '0' (0x0030/48)
    0x00, 0x32, 0x02, 0x09, // char '1' (0x0031/49)
    0x00, 0x36, 0x06, 0x09, // char '2' (0x0032/50)
    0x00, 0x42, 0x05, 0x0B, // char '3' (0x0033/51)
    0x00, 0x4C, 0x06, 0x0B, // char '4' (0x0034/52)
    0x00, 0x58, 0x06, 0x0B, // char '5' (0x0035/53)
    0x00, 0x64, 0x06, 0x09, // char '6' (0x0036/54)
    0x00, 0x70, 0x07, 0x0B, // char '7' (0x0037/55)
    0x00, 0x7E, 0x05, 0x09, // char '8' (0x0038/56)
    0x00, 0x88, 0x06, 0x0C, // char '9' (0x0039/57)
    0x00, 0x94,
    0x00, 0x10, 0x78, 0x14, 0x30, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // char '+' (0x002B/43)
    0x80, 0x03, // char ',' (0x002C/44)
    0x20, 0x30, 0x30, 0x30, 0x10, 0x00, // char '-' (0x002D/45)
    0x00, 0x80, 0x00, 0x01, 0x03, 0x01, // char '.' (0x002E/46)
    0x00, 0x80, 0x60, 0x18, 0x07, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, // char '/' (0x002F/47)
    0x60, 0xF0, 0x08, 0x08, 0xF8, 0x60, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, // char '0' (0x0030/48)
    0xF8, 0xF8, 0x01, 0x01, // char '1' (0x0031/49)
    0x00, 0xB8, 0x88, 0x48, 0x38, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, // char '2' (0x0032/50)
    0x00, 0x08, 0x48, 0x68, 0xD8, 0x04, 0x04, 0x04, 0x04, 0x03, // char '3' (0x0033/51)
    0x80, 0x40, 0x30, 0x08, 0xF8, 0x00, 0x00, 0x01, 0x01, 0x01, 0x07, 0x01, // char '4' (0x0034/52)
    0x40, 0xB0, 0x48, 0x48, 0xC8, 0x80, 0x02, 0x04, 0x04, 0x04, 0x04, 0x03, // char '5' (0x0035/53)
    0x70, 0xFE, 0x13, 0x11, 0x30, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, // char '6' (0x0036/54)
    0x08, 0x88, 0x48, 0x28, 0x18, 0x08, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0xE6, 0x99, 0x11, 0x3B, 0xE6, 0x00, 0x01, 0x01, 0x01, 0x00, // char '8' (0x0038/56)
    0x30, 0xC8, 0x88, 0x08, 0xF8, 0xE0, 0x00, 0x00, 0x08, 0x04, 0x03, 0x00, // char '9' (0x0039/57)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 220 BYTES
};


1.7_dev : 

// ----------
// ----------
// ----------
// --@@@--@@-
// -@--@--@@-
// @@--@@-@@-
// @@--@@-@@-
// -@--@--@@-
// -@@@---@@-
// ----------
// ----------
// ----------
// ----------
extern const uint8_t free_Black_Chancery8x13[] PROGMEM;
const uint8_t free_Black_Chancery8x13[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x08, 0x0D, 0x00,
// GROUP first '+' total 15 chars
//  unicode(LSB,MSB)|count
    0x00, 0x2B, 0x0F, // unicode record
    0x00, 0x00, 0x06, 0x09, // char '+' (0x002B/43)
    0x00, 0x0C, 0x01, 0x0A, // char ',' (0x002C/44)
    0x00, 0x0E, 0x06, 0x06, // char '-' (0x002D/45)
    0x00, 0x14, 0x03, 0x0A, // char '.' (0x002E/46)
    0x00, 0x1A, 0x06, 0x0A, // char '/' (0x002F/47)
    0x00, 0x26, 0x06, 0x09, // char '0' (0x0030/48)
    0x00, 0x32, 0x02, 0x09, // char '1' (0x0031/49)
    0x00, 0x36, 0x06, 0x09, // char '2' (0x0032/50)
    0x00, 0x42, 0x05, 0x0B, // char '3' (0x0033/51)
    0x00, 0x4C, 0x06, 0x0B, // char '4' (0x0034/52)
    0x00, 0x58, 0x06, 0x0B, // char '5' (0x0035/53)
    0x00, 0x64, 0x06, 0x09, // char '6' (0x0036/54)
    0x00, 0x70, 0x07, 0x0B, // char '7' (0x0037/55)
    0x00, 0x7E, 0x05, 0x09, // char '8' (0x0038/56)
    0x00, 0x88, 0x06, 0x0C, // char '9' (0x0039/57)
    0x00, 0x94,
    0x00, 0x10, 0x78, 0x14, 0x30, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // char '+' (0x002B/43)
    0x80, 0x03, // char ',' (0x002C/44)
    0x20, 0x30, 0x30, 0x30, 0x10, 0x00, // char '-' (0x002D/45)
    0x00, 0x80, 0x00, 0x01, 0x03, 0x01, // char '.' (0x002E/46)
    0x00, 0x80, 0x60, 0x18, 0x07, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, // char '/' (0x002F/47)
    0x60, 0xF0, 0x08, 0x08, 0xF8, 0x60, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, // char '0' (0x0030/48)
    0xF8, 0xF8, 0x01, 0x01, // char '1' (0x0031/49)
    0x00, 0xB8, 0x88, 0x48, 0x38, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, // char '2' (0x0032/50)
    0x00, 0x08, 0x48, 0x68, 0xD8, 0x04, 0x04, 0x04, 0x04, 0x03, // char '3' (0x0033/51)
    0x80, 0x40, 0x30, 0x08, 0xF8, 0x00, 0x00, 0x01, 0x01, 0x01, 0x07, 0x01, // char '4' (0x0034/52)
    0x40, 0xB0, 0x48, 0x48, 0xC8, 0x80, 0x02, 0x04, 0x04, 0x04, 0x04, 0x03, // char '5' (0x0035/53)
    0x70, 0xFE, 0x13, 0x11, 0x30, 0xE0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, // char '6' (0x0036/54)
    0x08, 0x88, 0x48, 0x28, 0x18, 0x08, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0xE6, 0x99, 0x11, 0x3B, 0xE6, 0x00, 0x01, 0x01, 0x01, 0x00, // char '8' (0x0038/56)
    0x30, 0xC8, 0x88, 0x08, 0xF8, 0xE0, 0x00, 0x00, 0x08, 0x04, 0x03, 0x00, // char '9' (0x0039/57)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 220 BYTES
};

RoboDurden avatar Aug 03 '19 10:08 RoboDurden

This is my result for ./fontgenerator.py --ttf comicbd.ttf -s 10 -SB 16 -g 43 14 -f new -d > 1.c using 1.7_dev

// --@@@@----@@--
// -@@@@@@--@@@--
// -@@--@@--@@@--
// @@----@@--@@--
// @@----@@--@@--
// @@----@@--@@--
// @@----@@--@@--
// -@@--@@---@@--
// -@@@@@@--@@@@-
// --@@@@---@@@@-
// --------------
// --------------
// --------------
// --------------
// --------------
// --------------
extern const uint8_t free_comicbd10x16[] PROGMEM;
const uint8_t free_comicbd10x16[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x0A, 0x10, 0x00,

lexus2k avatar Aug 04 '19 03:08 lexus2k

Folks, sorry for hijacking this issue, but I have a bit of trouble understanding the variable width font implementation in this library.

Usually, fonts are created such that the running width of the digits 0-9 are all equal even in variable width fonts. I checked comicbd.ttf with FontCreator and this is surely the case there.

But when encoding e.g. the digit 1 glyph using fontgenerator.py it seems that the leading and trailing space is lost, that the glyph starts with the first column containing ink and ends with the last column containing ink (for sure to save space) but without any information about that leading and trailing space retained in the unicode block jump table, making the digit 1 glyph thinner than the digit 0 glyph.

So are all glyphs stripped from their horizontal padding space that was designed into the font?

Kind regards, Sebastian

wangnick avatar Jun 08 '21 19:06 wangnick

I have forgotten everything about this. But i remember that my online font compiler has two sizes and the pixel size must be higher than the tft size in order render all pixels. So this might also apply to your width problem: https://pionierland.de/fonts

RoboDurden avatar Jun 08 '21 19:06 RoboDurden

No, that's not it. Pixel rendering is fine, it's the character spacing that gets compressed, see:

c:\python27\python.exe fontgenerator.py --ttf c:\Users\Hobbyraum\Desktop\FreeSans.otf -s 27 -g 0 :  -f new --demo-only -t 0:1:0
// -----@@@@@@-----------------@@-----------@@@@@@------
// ---@@@@@@@@@@---------------@@---------@@@@@@@@@@----
// --@@@@@@@@@@@@--------------@@--------@@@@@@@@@@@@---
// --@@@@----@@@@-------------@@@--------@@@@----@@@@---
// -@@@@------@@@@----------@@@@@-------@@@@------@@@@--
// -@@@--------@@@-------@@@@@@@@-------@@@--------@@@--
// @@@@--------@@@@-@@@@-@@@@@@@@-@@@@-@@@@--------@@@@-
// @@@----------@@@-@@@@------@@@-@@@@-@@@----------@@@-
// @@@----------@@@-@@@@------@@@-@@@@-@@@----------@@@-
// @@@----------@@@-@@@@------@@@-@@@@-@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@----------@@@-----------@@@------@@@----------@@@-
// @@@@--------@@@@-----------@@@------@@@@--------@@@@-
// -@@@--------@@@------------@@@-------@@@--------@@@--
// -@@@-------@@@@------------@@@-------@@@-------@@@@--
// -@@@@------@@@---@@@@------@@@-@@@@--@@@@------@@@---
// --@@@@@@@@@@@@---@@@@------@@@-@@@@---@@@@@@@@@@@@---
// ---@@@@@@@@@@----@@@@------@@@-@@@@----@@@@@@@@@@----
// -----@@@@@@------@@@@------@@@-@@@@------@@@@@@------

In the font definition glyphs 0 and 1 have the same width.

To correct that and to have the ssd1306 library render the glyphs as intended, though, either a) quite a few non-ink rows are to be emitted (eating up precious flash memory), or else b) the left and right spacing needs to be added to the jump table (making for yet another format, plus also eating up some flash memory).

a) could be built into the fontgenerator as an option ...

wangnick avatar Jun 08 '21 20:06 wangnick

Sorry i am not the programmer. But i see that you did not test my advice to use two sizes but only use the -s option. Try using the two sizes i spoke of like fontgenerator.py --ttf FreeSans.ttf -s 40 -SB 48 -f new -d > 1.txt If that does not help then i can not help you. bye.

RoboDurden avatar Jun 08 '21 20:06 RoboDurden

Hi

For example, the line below generates variable width glyphs always, if specific option is not requested.

./fontgenerator.py --ttf FreeSans.ttf -s 14 -g 0 : -f new -d -t 0:1:0

// --@@@@@---------@@------@@@@@---
// -@@---@@-------@@@-----@@---@@--
// @@----@@-----@@@@@----@@----@@--
// @@-----@--@@----@@-@@-@@-----@--
// @@-----@@-@@----@@-@@-@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@--------@@----@@-----@--
// @@----@@--------@@----@@----@@--
// -@@---@@--@@----@@-@@--@@---@@--
// --@@@@@---@@----@@-@@---@@@@@---
extern const uint8_t free_FreeSans10x13[] PROGMEM;
const uint8_t free_FreeSans10x13[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x0A, 0x0D, 0x00,
// GROUP first '0' total 11 chars
//  unicode(LSB,MSB)|count
    0x00, 0x30, 0x0B, // unicode record
    0x00, 0x00, 0x09, 0x0D,// char '0' (0x0030/48)
    0x00, 0x12, 0x05, 0x0D,// char '1' (0x0031/49)
    0x00, 0x1C, 0x09, 0x0D,// char '2' (0x0032/50)
    0x00, 0x2E, 0x09, 0x0D,// char '3' (0x0033/51)
    0x00, 0x40, 0x09, 0x0D,// char '4' (0x0034/52)
    0x00, 0x52, 0x09, 0x0D,// char '5' (0x0035/53)
    0x00, 0x64, 0x09, 0x0D,// char '6' (0x0036/54)
    0x00, 0x76, 0x09, 0x0D,// char '7' (0x0037/55)
    0x00, 0x88, 0x09, 0x0D,// char '8' (0x0038/56)
    0x00, 0x9A, 0x09, 0x0D,// char '9' (0x0039/57)
    0x00, 0xAC, 0x02, 0x0D,// char ':' (0x003A/58)
    0x00, 0xB0,
    0xFC, 0xFE, 0x03, 0x01, 0x01, 0x01, 0x07, 0xFE, 0xF0, 0x07, 0x0F, 0x18, 0x10, 0x10, 0x10, 0x1C, 0x0F, 0x01, // char '0' (0x0030/48)
    0x04, 0x04, 0x06, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0x1F, // char '1' (0x0031/49)
    0x0C, 0x0F, 0x03, 0x81, 0x81, 0xC1, 0x63, 0x3E, 0x1C, 0x1C, 0x1F, 0x1B, 0x19, 0x18, 0x18, 0x18, 0x18, 0x18, // char '2' (0x0032/50)
    0x0E, 0x0F, 0x01, 0x61, 0x61, 0x61, 0xFF, 0x9E, 0x00, 0x0E, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x03, // char '3' (0x0033/51)
    0x80, 0xE0, 0x30, 0x18, 0x06, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x1F, 0x1F, 0x03, 0x03, // char '4' (0x0034/52)
    0x70, 0x7F, 0x21, 0x31, 0x31, 0x31, 0x61, 0xE1, 0x80, 0x0C, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x03, // char '5' (0x0035/53)
    0xF8, 0xFE, 0x63, 0x21, 0x21, 0x21, 0x63, 0xC6, 0x80, 0x07, 0x0F, 0x18, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, // char '6' (0x0036/54)
    0x01, 0x01, 0x01, 0x81, 0xE1, 0x31, 0x0D, 0x07, 0x01, 0x00, 0x00, 0x1C, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0x9C, 0xFF, 0x63, 0x61, 0x61, 0x61, 0xF7, 0x9E, 0x00, 0x0F, 0x1D, 0x18, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, // char '8' (0x0038/56)
    0x7E, 0xE7, 0xC3, 0x81, 0x81, 0xC1, 0x63, 0xFE, 0xF0, 0x0C, 0x1C, 0x18, 0x10, 0x10, 0x18, 0x1C, 0x07, 0x00, // char '9' (0x0039/57)
    0x18, 0x18, 0x18, 0x18, // char ':' (0x003A/58)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 232 BYTES
};

In the code above 0 has width

But you can try to add -fw argument and it will generate the font with fixed width 9 (0x00, 0x00, 0x09, 0x0D), and 1 has width 5 (0x00, 0x12, 0x05, 0x0D).

// ---@@@@@-----------------@@--------------------@@@@@---
// --@@---@@---------------@@@-------------------@@---@@--
// -@@----@@-------------@@@@@------------------@@----@@--
// -@@-----@--@@------------@@------@@----------@@-----@--
// -@@-----@@-@@------------@@------@@----------@@-----@@-
// -@@-----@@---------------@@------------------@@-----@@-
// -@@-----@@---------------@@------------------@@-----@@-
// -@@-----@@---------------@@------------------@@-----@@-
// -@@-----@@---------------@@------------------@@-----@@-
// -@@-----@----------------@@------------------@@-----@--
// -@@----@@----------------@@------------------@@----@@--
// --@@---@@--@@------------@@------@@-----------@@---@@--
// ---@@@@@---@@------------@@------@@------------@@@@@---

If that's not acceptable, then the only way to solve the problem is to update fontgenerator with new option.

lexus2k avatar Jun 09 '21 00:06 lexus2k

First let me tell you that I'm really impressed by your library, in particular how little storage it requires. I am currently in the process of migrating my home and garden bus to CAN. For one of my devices (an Atmega328P collecting temp and humidity, calculating dewpoint, displaying the data on a little 160x128 TFT, and controlling two fans in the workshop) I was using Adafruit_GFX. After adding CAN support I ran out of flash storage. I've now migrated to your ssd1306 library and, wow, flash footprint of the application (there is also a CAN bootloader of 4k) goes down from 28k to 22k, giving me back plenty of breathing space!

Now to the issue at hand. I'm displaying prominently and large the current time on the TFT, and I don't want the text to jitter depending on the digit displayed. What I would have expected is that the 0 glyph and the 1 glyph, as they have the same width in FreeSans.ttf (and in most fonts) are rendered with the same width by the library, but that the : glyph is rendered with a smaller width also as defined in the font. Basically I expect glyphs to be rendered with white space before the bitmap as determined by the font definition, and with an advance to the next glyph also as determined by the font definition.

But what the ssd1306 code does, if I understood it well, is that it renders the bitmap without any leading white space and then advances exactly 1 pixel (except for the space "glyph" with its empty bitmap where it simply advances half the maximum glyph width of the font), disregarding the font-defined advances before and after the glyph itself.

I've tried -SB and -fw and both don't achieve what I need.

Now, when I add the following to TTFSource.__add_char after load_char:

        print('%s: Hori %d (%d-%d-%d)'%(ch,
                round(self.face.glyph.advance.x/64),
                self.face.glyph.bitmap_left,self.face.glyph.bitmap.width,
                round(self.face.glyph.advance.x/64)-self.face.glyph.bitmap_left-self.face.glyph.bitmap.width
        ))

then what I get is the following:

C:\Users\Hobbyraum\Documents\Arduino\libraries\ssd1306\tools>c:\python27\python.exe fontgenerator.py --ttf c:\Users\Hobbyraum\Desktop\FreeSans.ttf -s 14 -g 0 : -f new --demo-only -t 0:1:0
0: Hori 11 (1-9-1)
1: Hori 11 (2-5-4)
2: Hori 11 (1-9-1)
3: Hori 11 (1-9-1)
4: Hori 11 (1-9-1)
5: Hori 11 (1-9-1)
6: Hori 11 (1-9-1)
7: Hori 11 (1-9-1)
8: Hori 11 (1-9-1)
9: Hori 11 (1-9-1)
:: Hori 5 (2-2-1)
// --@@@@----------@@------@@@@----
// -@@@@@@--------@@@-----@@@@@@---
// -@@---@@-----@@@@@-----@@---@@--
// @@----@@-----@@@@@----@@----@@--
// @@-----@--@@----@@-@@-@@-----@--
// @@-----@@-@@----@@-@@-@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@@-------@@----@@-----@@-
// @@-----@--------@@----@@-----@--
// @@----@@--------@@----@@----@@--
// -@@---@@--------@@-----@@---@@--
// -@@@@@@---@@----@@-@@--@@@@@@---
// --@@@@----@@----@@-@@---@@@@----

So according to the font definition of FreeSans.ttf at size -s 14

  • the 0 glyph should advance by 1 pixel column of white space before the character bitmap, then should render 9 columns of bitmap, and then advance by another 1 pixel column of white space before rendering the next glyph. This makes a box 11 pixels wide.
  • the 1 glyph should advance by 2 pixel columns of white space before the character bitmap, then should render 5 columns of bitmap, and then advance by another 4 pixel columns of white space to the next glyph. This also makes a box of 11 pixels wide.
  • the :glyph should advance by 2 pixel columns of white space before the character bitmap, then should render 2 columns of bitmap, and then advance by another 1 pixel column of white space, making a box of 5 pixels wide

My plan of action is to try to add a '-fontwidth' option to the fontgenerator which would encode the font advances into the bitmap definitions. I'm aware that this increases the space consumption of the font in flash memory, but for my time display I only need the digits and the colon, and any new glyph storage format that holds those advances in the per-glyph jump table together with the additional code to handle it will also consume some space ...

Kind regards, Sebastian

wangnick avatar Jun 09 '21 09:06 wangnick

Ok, so I added an option -fa (for font advance) to fontgenerator.py and the respective expansion of the bitmap to ttfsource.py. Result is as I need:

C:\Users\Hobbyraum\Documents\Arduino\libraries\ssd1306\tools>c:\python27\python.exe fontgenerator.py --ttf c:\Users\Hobbyraum\Desktop\FreeSans.ttf -s 14 -g 0 : -f new -t 0:1:0 -d -fa
// 0: Hori 11 (1-9-1) Vert 0 (14-14)
// 1: Hori 11 (2-5-4) Vert 0 (14-14)
// 2: Hori 11 (1-9-1) Vert 0 (14-14)
// 3: Hori 11 (1-9-1) Vert 0 (14-14)
// 4: Hori 11 (1-9-1) Vert 0 (14-14)
// 5: Hori 11 (1-9-1) Vert 0 (14-14)
// 6: Hori 11 (1-9-1) Vert 0 (14-14)
// 7: Hori 11 (1-9-1) Vert 0 (14-14)
// 8: Hori 11 (1-9-1) Vert 0 (14-14)
// 9: Hori 11 (1-9-1) Vert 0 (14-14)
// :: Hori 5 (2-2-1) Vert 0 (10-10)
// ---@@@@----|-----|-----@@----|-----|---@@@@----
// --@@@@@@---|-----|----@@@----|-----|--@@@@@@---
// --@@---@@--|-----|--@@@@@----|-----|--@@---@@--
// -@@----@@--|-----|--@@@@@----|-----|-@@----@@--
// -@@-----@--|--@@-|-----@@----|--@@-|-@@-----@--
// -@@-----@@-|--@@-|-----@@----|--@@-|-@@-----@@-
// -@@-----@@-|-----|-----@@----|-----|-@@-----@@-
// -@@-----@@-|-----|-----@@----|-----|-@@-----@@-
// -@@-----@@-|-----|-----@@----|-----|-@@-----@@-
// -@@-----@--|-----|-----@@----|-----|-@@-----@--
// -@@----@@--|-----|-----@@----|-----|-@@----@@--
// --@@---@@--|-----|-----@@----|-----|--@@---@@--
// --@@@@@@---|--@@-|-----@@----|--@@-|--@@@@@@---
// ---@@@@----|--@@-|-----@@----|--@@-|---@@@@----
extern const uint8_t free_FreeSans11x14[] PROGMEM;
const uint8_t free_FreeSans11x14[] PROGMEM =
{
//  type|width|height|first char
    0x02, 0x0B, 0x0E, 0x00,
// GROUP first '0' total 11 chars
//  unicode(LSB,MSB)|count
    0x00, 0x30, 0x0B, // unicode record
    0x00, 0x00, 0x0A, 0x0E, // char '0' (0x0030/48)
    0x00, 0x14, 0x0A, 0x0E, // char '1' (0x0031/49)
    0x00, 0x28, 0x0A, 0x0E, // char '2' (0x0032/50)
    0x00, 0x3C, 0x0A, 0x0E, // char '3' (0x0033/51)
    0x00, 0x50, 0x0A, 0x0E, // char '4' (0x0034/52)
    0x00, 0x64, 0x0A, 0x0E, // char '5' (0x0035/53)
    0x00, 0x78, 0x0A, 0x0E, // char '6' (0x0036/54)
    0x00, 0x8C, 0x0A, 0x0E, // char '7' (0x0037/55)
    0x00, 0xA0, 0x0A, 0x0E, // char '8' (0x0038/56)
    0x00, 0xB4, 0x0A, 0x0E, // char '9' (0x0039/57)
    0x00, 0xC8, 0x04, 0x0E, // char ':' (0x003A/58)
    0x00, 0xD0,
    0x00, 0xF8, 0xFE, 0x07, 0x03, 0x03, 0x03, 0x0E, 0xFC, 0xE0, 0x00, 0x07, 0x1F, 0x38, 0x30, 0x30, 0x30, 0x1C, 0x0F, 0x01, // char '0' (0x0030/48)
    0x00, 0x00, 0x0C, 0x0C, 0x0E, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x00, 0x00, 0x00, // char '1' (0x0031/49)
    0x00, 0x0C, 0x0E, 0x03, 0x03, 0x83, 0x83, 0xC7, 0x7E, 0x38, 0x00, 0x38, 0x3E, 0x32, 0x33, 0x31, 0x31, 0x30, 0x30, 0x30, // char '2' (0x0032/50)
    0x00, 0x0C, 0x0E, 0x03, 0x63, 0x63, 0x63, 0xFE, 0x9E, 0x00, 0x00, 0x1C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x18, 0x1F, 0x07, // char '3' (0x0033/51)
    0x00, 0x80, 0xC0, 0x70, 0x18, 0x0C, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0x06, 0x06, 0x06, 0x06, 0x3F, 0x3F, 0x06, 0x06, // char '4' (0x0034/52)
    0x00, 0x70, 0x7F, 0x23, 0x33, 0x33, 0x33, 0x63, 0xE3, 0x80, 0x00, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x18, 0x1F, 0x07, // char '5' (0x0035/53)
    0x00, 0xF8, 0xFE, 0xC6, 0x63, 0x63, 0x63, 0xE7, 0xCE, 0x00, 0x00, 0x0F, 0x1F, 0x38, 0x30, 0x30, 0x30, 0x18, 0x1F, 0x07, // char '6' (0x0036/54)
    0x00, 0x03, 0x03, 0x03, 0x03, 0xC3, 0x73, 0x1B, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x38, 0x1F, 0x03, 0x00, 0x00, 0x00, 0x00, // char '7' (0x0037/55)
    0x00, 0x9C, 0xFE, 0x63, 0x63, 0x63, 0x63, 0xF6, 0x9E, 0x00, 0x00, 0x0F, 0x1D, 0x30, 0x30, 0x30, 0x30, 0x18, 0x1F, 0x07, // char '8' (0x0038/56)
    0x00, 0x7C, 0xEE, 0x83, 0x83, 0x83, 0x83, 0xC6, 0xFC, 0xF0, 0x00, 0x08, 0x18, 0x31, 0x31, 0x31, 0x39, 0x1C, 0x0F, 0x01, // char '9' (0x0039/57)
    0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, // char ':' (0x003A/58)
    0x00, 0x00, 0x00, // end of unicode tables
    // FONT REQUIRES 264 BYTES
};

For my case of the FreeSans digits and colon at -s 27 the font definition grows from 712 by 136 to 848 bytes. Encoding bitmap_left and the total X advance into the jump table would cost 22 bytes more, and the code to handle it also a couple of bytes, but would probably all-in-all be less space consuming. Maybe I'll still give that a try as well.

Kind regards, Sebastian fontgenerator.zip

wangnick avatar Jun 09 '21 12:06 wangnick

But what the ssd1306 code does, if I understood it well, is that it renders the bitmap without any leading white space and then advances exactly 1 pixel (except for the space "glyph" with its empty bitmap where it simply advances half the maximum glyph width of the font), disregarding the font-defined advances before and after the glyph itself.

That happens in the code loading the font:

            info->width = glyph_width;
            info->height = glyph_height;
            info->spacing = glyph_width ? 1 : (s_fixedFont.h.width >> 1); // <<<<
            info->glyph = data + (r.count - unicode) * 4 + 2 + offset;

Initial idea was to reduce font size in flash, and make the printed text to look readable (without 1 pixel advance it looked awful) If glyph width is zero, that's the case for space char.

lexus2k avatar Jun 09 '21 23:06 lexus2k

Alexey, FYI, I've committed my initial set of changes to https://github.com/wangnick/ssd1306/tree/fontadvance.

Btw, would you recommend to use lcdgfx instead of ssd1306? I do not fully understand the relationship between those two libraries ...

Kind regards, Sebastian

wangnick avatar Jun 10 '21 12:06 wangnick

Sebastian

Nice fix.

Regarding ssd1306 vs lcdgfx.

  1. ssd1306 is C -library while lcdgfx is C++ library (Many people like C++, but not C)
  2. lcdgfx based on C++ templates, and should be faster, while ssd1306 uses pointers to display-specific functions.
  3. lcdgfx supports more display types
  4. Theoretically, lcdgfx should support multiple displays connected to the same MCU (but I didn't test this feature), while ssd1306 supports only one and only one display attached to MCU.
  5. Both libraries use the same font format.
  6. Both libraries have similar footprint (I had the document with measures before).
  7. The code for the displays is auto generated based on display description templates (I'm not still sure if it is good or bad)
  8. ssd1306 can be built as kernel module, while lcdgfx cannot (because of C++).
  9. But ssd1306 has more stars :) Maybe because of its name.

lexus2k avatar Jun 10 '21 22:06 lexus2k

Regarding ssd1306 vs lcdgfx. 1. ssd1306 is C -library while lcdgfx is C++ library (Many people like C++, but not C) 2. lcdgfx based on C++ templates, and should be faster, while ssd1306 uses pointers to display-specific functions. 3. lcdgfx supports more display types 4. Theoretically, lcdgfx should support multiple displays connected to the same MCU (but I didn't test this feature), while ssd1306 supports only one and only one display attached to MCU. 5. Both libraries use the same font format. 6. Both libraries have similar footprint (I had the document with measures before). 7. The code for the displays is auto generated based on display description templates (I'm not still sure if it is good or bad) 8. ssd1306 can be built as kernel module, while lcdgfx cannot (because of C++). 9. But ssd1306 has more stars :) Maybe because of its name.

That explains, thanks. Maybe capture some of this in library.properties, so that Arduino users can see the difference more easily ... ?

wangnick avatar Jun 11 '21 14:06 wangnick