Parola A to Z – Managing Fonts

azparola_iconManaging fonts in the is a key factor in the Parola/MD_MAX72xx libraries to being able to support multiple languages and diverse alphabets.

In the first part we looked at how fonts are defined and the tools used to create the bitmaps for each character. In this part we will look at the Parola and MD_MAX72xx library methods that access and manage font data in the library code.

In the first instance, it is useful to philosophically separate the font handling requirements between MD_MAX72xx and MD_Parola.

The Parola library is designed to efficiently manage text displayed on a LED matrix display. On the other hand, the MD_MAX72xx library provides the hardware management primitives to control the hardware. This means that the MD_MAX72xx library should naturally contain the most basic methods for managing fonts and Parola provides ‘higher order’ methods.

Fonts in MD_MAX72xx

Any library system managing specific components needs to provide a distinct set of low level functions that are similar for all libraries – initialize, open/create, close/delete, read, write. All other primitives and higher order functions can generally be written in terms of these ‘object’ primitives.

Font data is stored in PROGMEM, so it cannot be actually be ‘closed’ or ‘deleted’. However, to allow font substitution, the libraries store a separate pointer to the current font table. This pointer can be changed to point to different tables in a very dynamic manner, enabling fonts to be efficiently changed during code execution. A NULL pointer resets the font to the system default – as no font definition would cause execution issues, this is co-opted to mean the default font.

bool MD_MAX72XX::setFont(fontType_t * f)
{
  _fontData = (f == NULL ? _sysfont_var : f);
  buildFontIndex();

  return(true);
}

If the FONT_INDEX option is enabled, the font index needs to be recreated for each new font file. The code to do this scans the data table and creates a second table, dynamically allocated during library initialization, where each entry holds the offset to the corresponding ASCII character index. In practical testing, the speed increase was not ‘measurable enough’ to warrant the extra memory overhead, especially on smaller processors.

void MD_MAX72XX::buildFontIndex(void)
{
  uint16_t offset = 0;

  if (_fontIndex == NULL)
    return;

  for (int16_t i=0; i<FONT_INDEX_SIZE; i++)
  {
    _fontIndex[i] = offset;
    offset += pgm_read_byte(_fontData+offset);
    offset++;
  }
}

A useful library function is to obtain the offset for a specific character. If the table is indexed, this is a trivial lookup. However, if the table is not indexed, the library needs to be linearly scanned to get to the right position.

uint16_t MD_MAX72XX::getFontCharOffset(uint8_t c)
{
  if (_fontIndex != NULL)
  {
    return(_fontIndex[c]);
  }
  else
  {
    uint16_t offset = 0;

    for (uint8_t i=0; i<c; i++)
    {
      offset += pgm_read_byte(_fontData+offset);
      offset++; // skip size byte we used above
    }
    return(offset);
  }
}

A ‘read’ primitive for a character bitmap should simply return the bitmap for the specified character and now many columns it occupies. The library loads the data into a user specified buffer and checks not to overwrite memory outside of the buffer. For this type of library it is better to return a ‘malformed’ bitmap, and keep the program executing, rather than corrupt memory and cause errors that are difficult to trace.

uint8_t MD_MAX72XX::getChar(uint8_t c, uint8_t size, uint8_t *buf)
{
  if (buf == NULL) 
    return(0);

  uint16_t offset = getFontCharOffset(c);
  size = min(size, pgm_read_byte(_fontData+offset));

  offset++; // skip the size byte

  for (uint8_t i=0; i<size; i++) 
    *buf++ = pgm_read_byte(_fontData+offset+i);

  return(size);
}

As PROGMEM is read only memory, the ‘write’ primitive cannot be implemented at the hardware level.

Fonts in MD_Parola

Font parameters can be set individually for each zone or for the whole display. The discussion below is for the zone functions. To extend the same parameters into the whole display, analogous methods set identical parameters for all zones.

The Parola library uses the MD_MAX72xx primitives to display standard fonts and also adds a few functions that extend the functionality of fonts.

Specifying a new font simply stores a pointer to the required table for later setting in the MD_MAX72xx. As each zone can have a different font, the font table needs to be swapped just-in-time as the messages for each zone and displayed – MD_MAX72xx only knows about the ‘current’ font. The specified font needs to be defined in the user application and conform to the font table format described in Part 1.

 void setZoneFont(MD_MAX72XX::fontType_t *fontDef) 
{
  _fontDef = fontDef;
};

Parola extends the functionality of fonts by allowing individual character in a font to be substituted at run time. This is useful when specific characters (for example, a combined ‘°C’ character) are needed but the majority of the standard font is suitable. An interesting side note is that the character with ASCII code 0 (‘\0’ in C format) can never be processed by the library as it denotes the end of a string, not a displayable character.

The replacement characters are stored in a linked list per zone with memory allocated from the heap. To minimize heap fragmentation the memory for deleted characters is retained for re-use at the next definition. In practice, once characters are defined they are not usually deleted. Also, to minimize RAM requirements, the library does not copy the in the data in the data definition but only retains a pointer to the data, so any changes to, or release of, the data storage in the calling program will be reflected in the library.

The character data must be the same format as those in the font table. If a character is specified with a code the same as an existing character the existing data is substituted for the new data.

bool MD_PZone::addChar(uint8_t code, uint8_t *data)
{
  charDef *pcd;

  if (code == 0)
    return(false);

  // first see if we have the code in our list
  pcd = _userChars;
  while (pcd != NULL)
  {
    if (pcd->code == code)
    {
      pcd->data = data;
      return(true);
    }
    pcd = pcd->next;
  }

  // Now see if we have an empty slot in our list
  pcd = _userChars;
  while (pcd != NULL)
  {
    if (pcd->code == 0)
    {
      pcd->code = code;
      pcd->data = data;
      return(true);
    }
    pcd = pcd->next;
 }

  // default is to add a new node to the front of the list
  if ((pcd = new charDef) != NULL)
  {
    pcd->code = code;
    pcd->data = data;
    pcd->next = _userChars;
    _userChars = pcd;
  }

  return(pcd != NULL);
}

There is a corresponding function to mark a defined character as deleted.

bool MD_PZone::delChar(uint8_t code)
{
  charDef *pcd = _userChars;

  if (code == 0)
    return(false);

  // Scan down the linked list
  while (pcd != NULL)
  {
    if (pcd->code == code)
    {
      pcd->code = 0;
      pcd->data = NULL;
      break;
    }
    pcd = pcd->next;
  }

  return(pcd != NULL);
}

Clearly, once we have the ability to define a character in the Parola library, any search for a specific character needs to check the local tables before it defaults to the specified font file. As for the MD_MAX72xx libraries, the data is loaded into a user buffer and the length in columns is returned from the function.

uint8_t MD_PZone::findChar(uint8_t code, uint8_t size, uint8_t *cBuf)
{
  charDef *pcd = _userChars;
  uint8_t len;

  // check local list first
  while (pcd != NULL)
  {
    if (pcd->code == code) // found it
    {
      len = min(size, pcd->data[0]);
      memcpy(cBuf, &pcd->data[1], len);
      return(len);
    }
    pcd = pcd->next;
  }

  // get it from the default font
  _MX->setFont(_fontDef); // change to the font for this zone
  len = _MX->getChar(code, size, cBuf);

  return(len);
}

So there we have it. The management of fonts is not complex for this application once the font data is defined in a manageable and extensible format.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s