I am using a 3.5: TFT LCD display with an Arduino Uno and the library from the manufacturer, the KeDei TFT library. The library came with a bitmap font table that is huge for the small amount of memory of an Arduino Uno so I've been looking for alternatives.
What I am running into is that there doesn't seem to be a standard representation and some of the bitmap font tables I've found work fine and others display as strange doodles and marks or they display upside down or they display with letters flipped. After writing a simple application to display some of the characters, I finally realized that different bitmaps use different character orientations.
My question
What are the rules or standards or expected representations for the bit data for bitmap fonts? Why do there seem to be several different text character orientations used with bitmap fonts?
Thoughts about the question
Are these due to different target devices such as a Windows display driver or a Linux display driver versus a bare metal Arduino TFT LCD display driver?
What is the criteria used to determine a particular bitmap font representation as a series of unsigned char values? Are different types of raster devices such as a TFT LCD display and its controller have a different sequence of bits when drawing on the display surface by setting pixel colors?
What other possible bitmap font representations requiring a transformation which my version of the library currently doesn't offer, are there?
Is there some method other than the approach I'm using to determine what transformation is needed? I currently plug the bitmap font table into a test program and print out a set of characters to see how it looks and then fine tune the transformation by testing with the Arduino and the TFT LCD screen.
My experience thus far
The KeDei TFT library came with an a bitmap font table that was defined as
const unsigned char font_table_16_col[96][16] = {
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },/*" ",0*/
{ 0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x38,0x38,0x00,0x00 },/*"!",1*/
{ 0x00,0xD8,0xFC,0x6C,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },/*""",2*/
{ 0x00,0x00,0x00,0x6C,0x6C,0x6C,0xFF,0x36,0x36,0x36,0xFF,0x36,0x36,0x36,0x00,0x00 },/*"#",3*/
{ 0x00,0x00,0x18,0x3C,0x7E,0x7E,0x1E,0x1C,0x38,0x78,0x78,0x7E,0x7E,0x3C,0x18,0x18 },/*"$",4*/
{ 0x00,0x00,0x00,0x66,0x6F,0x3F,0x3F,0x3F,0x7E,0xF8,0xFC,0xFC,0xFC,0x66,0x00,0x00 },/*"%",5*/
...
I'm not fully conversant with the standard descriptions of bitmap fonts however I think of this as being an 8x16 bitmap font in which each character is 8 pixels wide and 16 pixels in height or an 8x16 bitmap font.
With the size of this table and the small amount of memory on the Arduino Uno, I started hunting for other bitmap fonts that would be legible while also taking up less memory. See reducing memory required for KeDei TFT library used with 3.5" TFT display with Arduino
What I hoped to find was something around a 6x6 bitmap font so that the definition of the bitmap font table would change from const unsigned char font_table_16_col[96][16] = {
to const unsigned char font_table_16_col[96][6] = {
which would free up a significant amount of memory. And experiments with cutting the table down by removing lower case letters showed that helped as well.
Finding alternative bitmap fonts has been more difficult than I thought, envisioning someone with the motherlode of bitmap fonts in a GitHub repository somewhere, easily found with a search or two.
What I have run into is that while I have found several different examples of bitmap fonts not all seem to be compatible with my specific 3.5" TFT LCD display.
For instance here are representations of four different bitmap fonts showing the bits of the bitmaps for two characters, the exclamation point (!) and the double quote ("). The 5x8 seems to be rotated to the clockwise by 90 degrees. The 8x8 and the 16x8 seem to be oriented correctly and the 13x8 seems to be upside down.
Generating the above bitmap representation
The bitmap font representations in the image above, showing the differences in text character orientation, were generated by a simple Windows GUI and displayed with a dash (-) representing a bit value of zero and an asterisk (*) representing a bit value of 1. This is the output of a Microsoft Windows GUI application whose WM_PAINT
message handler which draws the displayed image is as follows:
int paintFontDisplay(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
SetTextAlign(hdc, TA_CENTER);
RECT rect;
GetClientRect(hWnd, &rect);
//Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
//The width, when set to 0, will cause the font mapper to choose the closest matching value.
//The font face name will be Impact.
HFONT hFont = CreateFont(24, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH, TEXT("Courier"));
SelectObject(hdc, hFont);
// TODO: Add any drawing code that uses hdc here...
int iFirst = 0;
int iLast = 10;
POINT outPoint;
outPoint.x = rect.left + 80;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 5; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_5_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 11;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 8; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_8_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 8;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 13; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_13_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20 + 20 * 3;
}
outPoint.x = outPoint.x + 200;
outPoint.y = rect.top + 20;
for (int i = iFirst; i < iLast; i++) {
for (int j = 0; j < 16; j++) {
std::wstring charRep;
for (unsigned char k = 0x80; k; k >>= 1) {
if (font_table_16_col[i][j] & k) {
charRep += '*';
}
else {
charRep += '-';
}
}
TextOut(hdc, outPoint.x, outPoint.y, charRep.c_str(), charRep.length());
outPoint.y += 20;
}
outPoint.y += 20;
}
EndPaint(hWnd, &ps);
return 0;
}
The first few lines of the bitmap font tables are as follows:
// following table from URL https://forum.arduino.cc/t/font-generation-for-bitmaps/161582/11
const unsigned char font_table_5_col[96][5] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00 } // 20
,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
// See https://github.com/dhepper/font8x8
const unsigned char font_table_8_col[96][8] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 (space)
{ 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 }, // U+0021 (!)
{ 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0022 (")
{ 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 }, // U+0023 (#)
{ 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 }, // U+0024 ($)
const unsigned char font_table_13_col[96][13] = {
// from URL https://courses.cs.washington.edu/courses/cse457/98a/tech/OpenGL/font.c
// GLubyte rasters[][13] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },/*" ",0*/
{ 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 },/*"!",1*/
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36 },/*""",2*/
{ 0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00, 0x00 },/*"#",3*/
{ 0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18 },/*"$",4*/
Transforming font bitmaps to display properly
I have modified the code that displays text using the bitmap fonts so that for a particular bit map the character drawing logic will perform several different kinds of translations between the bitmap font representation as a series of hexadecimal digits and how the series of digits are used to determine which pixels to turn on and which to turn off.
The code for drawing a single line of a character is as follows. The outline of this function is to provide to the LCD controller a rectangle specifying the region of the display to be modified followed by a series of two 8 bit writes to set the two byte RGB565 color value of each of the pixels in the region.
static bool TFTLCD::draw_glyph(unsigned short x0, unsigned short y0, TftColor fg_color, TftColor bg_color, unsigned char bitMap, unsigned char bmWidth, unsigned char flags)
{
// we will fill a single row of 8 pixels by iterating over
// a bitmap font map of which pixels to set to the foreground
// color and which pixels to set to the background color for this
// part of the character to display.
// first determine whether we are scaling the default width by a multiplier
// of 2 or 3 times the default size. this allows us to have different sizes
// of text using the same bitmap font.
if (flags & 0x01)
set_area(x0, y0, x0 + bmWidth * 2 - 1, y0); // scale the default width to double wide
else if (flags & 0x02)
set_area(x0, y0, x0 + bmWidth * 3 - 1, y0); // scale the default width to tripple wide
else
set_area(x0, y0, x0 + bmWidth - 1, y0); // default width and size with no scaling
if (flags & 0x20) { // Font::font_flags & FontTable::Flags_InvertBitOrder
// inverting the order of painting the bits. means the bitmap of the
// font would display the text with each character flipped if we did not do this.
for (unsigned char char_n = 0x80; char_n; char_n >>= 1)
{
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x03) { // double wide or triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x02) { // triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
}
}
}
}
else {
for (unsigned char char_n = 1; char_n; char_n <<= 1)
{
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x03) { // double wide or triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
if (flags & 0x02) { // triple wide
if (bitMap & char_n)
{
w_data(fg_color >> 8);
w_data(fg_color);
}
else {
w_data(bg_color >> 8);
w_data(bg_color);
}
}
}
}
}
return 1;
and the source code that uses the above function for drawing a complete characters is as follows. This code uses the drawGlyph()
function to draw a series of slices of the text character from top to bottom. When a bitmap transformation is done depends on the bitmap representation.
unsigned char glyphFlags = ((Font::font_flags & FontTable::Flags_DoubleWide) ? 1 : 0) | ((Font::font_flags & FontTable::Flags_TripleWide) ? 2 : 0);
if (Font::font_flags & FontTable::Flags_InvertBitOrder) {
glyphFlags |= 0x20;
for (signed char char_m = Font::font_table.nCols - 1; char_m >= 0; char_m--)
{
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
else if (Font::font_flags & FontTable::Flags_RotateBits) {
for (unsigned char char_m = 0; char_m < 8; char_m++)
{
unsigned char rotatedMap = 0;
for (unsigned char char_x = 0; char_x < Font::font_table.nCols; char_x++) {
rotatedMap |= ((Font::font_table.table[char_i_x + char_x] & (1 << char_m)) ? 1 : 0) << char_x;
}
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, rotatedMap, 8, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
else {
for (unsigned char char_m = 0; char_m < Font::font_table.nCols; char_m++)
{
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
if (font_flags & (FontTable::Flags_DoubleHigh | FontTable::Flags_TripleHigh)) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
if (font_flags & FontTable::Flags_TripleHigh) {
TFTLCD::draw_glyph(Font::now_x, Font::now_y, Font::font_color, Font::txt_backcolor, Font::font_table.table[char_i_x + char_m], Font::font_table.nCols, glyphFlags);
// shift down to the next row of pixels for the character
Font::now_y++;
}
}
}
Raster or Bitmap Font specifications
There are a number of font specifications including rasterized bitmap type fonts. These specifications do not necessarily describe the glyph bitmaps used in application such as the KeDei TFT library but rather provide a device independent description of a bitmap font format.
Glyph Bitmap Distribution Format
- Wikipedia topic https://en.wikipedia.org/wiki/Glyph_Bitmap_Distribution_Format
"BITMAP" begins the bitmap for the current glyph. This line must be followed by one line per pixel on the Y-axis. In this example the glyph is 16 pixels tall, so 16 lines follow. Each line contains the hexadecimal representation of pixels in a row. A "1" bit indicates a rendered pixel. Each line is rounded to an 8 bit (one byte) boundary, padded with zeroes on the right. In this example, the glyph is exactly 8 pixels wide, and so occupies exactly 8 bits (one byte) per line so that there is no padding. The most significant bit of a line of raster data represents the leftmost pixel.
Oracle in Solarix X Window System Developer's Guide, Chapter 4 Font Support at https://docs.oracle.com/cd/E19253-01/816-0279/6m6pd1cvk/index.html has a table listing several different bitmap font formats and has this to say:
As illustrated in Table 4–4, many bitmap font file formats are architecture-dependent binary files. They cannot be shared between machines of different architectures (for example, between SPARC and IA).
- Bitmap distribution format, .bdf file, not binary, not architecture specific
- Portable compiled format, .pcf file, binary, not architecture specific
- Little Endian prebuilt format, binary, architecture specific
- Big Endian prebuilt format, binary, architecture specific
PSF (PC Screen Font), a binary specification, described at URL https://www.win.tue.nl/~aeb/linux/kbd/font-formats-1.html
PSF stands for PC Screen Font. The psf1 format without Unicode map was designed by H. Peter Anvin in 1989 or so for his DOS screen font editor FONTEDIT.EXE. In Oct 1994 he added the Unicode map and the programs psfaddtable, psfgettable, psfstriptable to manipulate it - see kbd-0.90. Andries Brouwer added support for sequences of Unicode values and the psf2 format in Sep 1999 in order to handle Tibetan - see kbd-1.00.
Microsoft Q65123 from "An Archive of Early Microsoft KnowledgeBase Articles" https://jeffpar.github.io/kbarchive/kb/065/Q65123/
Formats for Microsoft Windows font files are defined for both raster and vector fonts. These formats can be used by smart text generators in some GDI support modules. The vector formats, in particular, are more frequently used by GDI itself than by support modules.
Metagraphics .fnt Font File Specification https://www.metagraphics.com/metawindow/fonts/fnt-specs.htm
Microchip Graphics library, AN1182 Fonts in the Microchip Graphics Library (PDF)
See also
Where I can find .fon format specification?
This File Formats web site has descriptions of several different font specifications. https://docs.fileformat.com/font/fnt/
Raster or bitmap fonts are represented in a number of different ways and there are bitmap font file standards that have been developed for both Linux and Windows. However raw data representation of bitmap fonts in programming language source code seems to vary depending on:
A brief overview of bitmap fonts
A generic bitmap is a block of data in which individual bits are used to indicate a state of either on or off. One use of a bitmap is to store image data. Character glyphs can be created and stored as a collection of images, one for each character in the character set, so using a bitmap to encode and store each character image is a natural fit.
Bitmap fonts are bitmaps used to indicate how to display or print characters by turning on or off pixels or printing or not printing dots on a page. See Wikipedia Bitmap fonts
A brief history of using bitmap fonts
The earliest user interface terminals such as teletype terminals used dot matrix printer mechanisms to print on rolls of paper. With the development of Cathode Ray Tube terminals bitmap fonts were readily transferable to that technology as dots of luminescence turned on and off by a scanning electron gun.
Earliest bitmap fonts were of a fixed height and width with the bitmap acting as a kind of stamp or pattern to print characters on the output medium, paper or display tube, with a fixed line height and a fixed line width such as the 80 columns and 24 lines of the DEC VT-100 terminal.
With increasing processing power, a more sophisticated typographical approach became available with vector fonts used to improve displayed text quality and provide improved scaling while also reducing memory required to describe the character glyphs.
In addition, while a matrix of dots or pixels worked fairly well for languages such as English, written languages with complex glyph forms were poorly served by bitmap fonts.
Representation of bitmap fonts in source code
There are a number of bitmap font file formats which provide a way to represent a bitmap font in a device independent description. For an example see Wikipedia topic - Glyph Bitmap Distribution Format
Other bitmap standards such as XBM, Wikipedia topic - X BitMap, or XPM, Wikipedia topic - X PixMap, are source code components that describe bitmaps however many of these are not meant for bitmap fonts specifically but rather other graphical images such as icons, cursors, etc.
As bitmap fonts are an older format many times bitmap fonts are wrapped within another font standard such as TrueType in order to be compatible with the standard font subsystems of modern operating systems such as Linux and Windows.
However embedded systems that are running on the bare metal or using an RTOS will normally need the raw bitmap character image data in the form similar to the XBM format. See Encyclopedia of Graphics File Formats which has this example:
The order of traversing the bits of each piece of data in the bitmap font is important to achieving the desired results.
While this description seems good enough, the definition of "high bit of the first byte" varies depending on the machine architecture, Big-endian versus Little-endian. The following image plotted using the above XBM description of the X logo with the left showing traversing the bits of each bitmap data byte on an Intel i7-7900 CPU from Most Significant Bit to Least Significant Bit and on the right doing the reverse.
Considerations for bitmap fonts
Bitmap fonts have a cell size or character height and width in pixels or dots. A line of text is a series of these cells stamped or drawn across the display, pixel by pixel.
Since a bitmap font's bitmap is not device independent, being a series of digits, the raw data describing a bitmap font's character image and how that data is stored in memory and accessed by the CPU is device dependent. The bitmap data may also be transformed in order to use machine resources more efficiently.
A bitmap font's bitmaps may be stored in such a fashion that they economize on memory used while requiring additional processing to properly place pixels on the drawing surface. So a font that is 5 pixels wide by 8 pixels high may be stored height first rather than width first in order to use the 8 bits of an unsigned char fully.
In addition to rotating the bitmap to use the bits in a byte more efficiently, compression algorithms may be used to reduce the amount of memory required for a bitmap font table. For a discussion on methods see Lightweight (de)compression algorithm for embedded use.
The machine architecture may physically access the individual bits representing pixels in different ways. A bitmap font's raw data laid out for processing by a Little-endian machine will be different than how that raw data would be laid out for a Big-endian machine. See this article from IBM Writing endian-independent code in C.
Having a tool to visualize a bitmap font and allow various transformations to the bitmap font data to explore what changes may be needed to display the bitmap characters is helpful. For example here is a Windows GUI application I've used for experimenting with bitmap fonts, https://github.com/RichardChambers/utilities_tools/tree/main/fontshow