0

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.

character representation of bitmap font samples showing differences

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

"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/

Richard Chambers
  • 14,509
  • 3
  • 62
  • 86
  • are you really expecting anyone to read through this monstrosity? your "my question" section alone containls like 7 separate questions. – Piglet May 10 '21 at 09:53
  • 1
    @Piglet Yes, I think that someone will and perhaps that someone will have an answer. There is really only one question, "What are the rules or standards or expected representations for the bit data for bitmap fonts?". The other "questions" are rhetorical questions to provide a better feel for some of the issues surrounding the actual question. The central image comparing the various bitmaps illustrates the problem prompting the question. The rest is just documentation, "showing the code" for (1) the central image generator and (2) my work around for the problem prompting the question. – Richard Chambers May 10 '21 at 11:25
  • 1
    @Piglet I went to this effort because I've seen bits and pieces of solutions and I wanted to consolidate some of that into a single place. My hope is the next person working on this problem will be helped by this post to see what I have done and hopefully extend it. That does seem to be what people do here. My next task is to winnow through SO posts to find others that have some piece of the problem I'm facing and link them together. – Richard Chambers May 10 '21 at 11:29
  • 1
    @Piglet I, for one, am refreshed by the insight and quality of the questions OP has posted lately. They are deep and far more interesting than the typical "how do I wire my stepper motor" or "I copied this code off the internet and it doesn't work" off-topic stuff some users mindlessly post here. – TomServo May 11 '21 at 10:29
  • @TomServo yeah the quality of this post is outstanding. I just wish it was a little shorter. my comment probably sounded too harsh. sorry for that. – Piglet May 11 '21 at 11:04
  • 1
    @RichardChambers One thing I'll note: I've created raster fonts before for similar uses. Most of the utilities I've used take some font, usually a TrueType, and some specifications like height and width (including) descenders, and then rasterize into that space. In every case, the output has been in row-major order as you showed above, earlier. Results usually not too good, but rasterizing the very small rendition of the TrueType usually worked a lot better than rasterizing a larger one, because lots of artifacts from shrinking. – TomServo May 11 '21 at 14:10
  • @TomServo In my literature review on this topic, I've seen something like what you describe. There appear to be several open source utilities for rasterizing non-bitmap fonts such as TrueType. Thank you for letting me know the results you've had. I did take a look at one called `fontedit` which creates a .fnt file however there is the step of exporting a .fnt file as a C style array bitmap font table. What have you used that generates C/C++ source code containing a bitmap font table? – Richard Chambers May 11 '21 at 15:18
  • Programmer's Guide to the EGA, VGA, and Super VGA Cards, Third Edition https://archive.org/details/programmersguidetotheegavgaandsupervgacardsbyrichardf.ferraro19943rdedition/page/n193/mode/2up?ref=ol&view=theater on page 170 (PDF page 194 of 1612) Alphanumeric Processing, section 6.2.2 Representing Characters in Memory. There are several related sections in section 6.2 about character representation by a raster font bitmap. – Richard Chambers May 13 '21 at 03:39
  • A collection of raster font bitmaps from the ROM of various devices such as IBM PC. https://github.com/viler-int10h/vga-text-mode-fonts most of these seem to be of a similar size of 8 pixels wide by either 14 pixels or 16 pixels in height. These appeared to have been stored at a particular offset in the ROM with the raster bitmaps represented by a series of hex digits. The ones I've looked at use one byte per text character line so an 8 by 16 requires 16 bytes per character. Bitmaps are stored in row major order, most significant bit is left most pixel. – Richard Chambers May 13 '21 at 04:00
  • An article documenting pulling fonts from a video card ROM and replacing them. It's interesting to see some of the techniques like using a mercury vapor bulb to erase a UV erasable EPROM http://www.alexandrugroza.ro/microelectronics/essays-research/vga-rom-fonts/index.html and see this document , CRT Controller handbook https://archive.org/details/crt_controller_handbook/page/n13/mode/2up as well as the drivers/video section of https://www.xml.com/ldd/chapter/book/ch16.html – Richard Chambers May 13 '21 at 16:18

1 Answers1

0

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:

  • the memory architecture of the target computer,
  • the architecture and communication pathways to the display controller,
  • character glyph height and width in pixels and
  • the amount of memory for bitmap storage and what measures are taken to make that as small as possible.

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 bitmap font is one that stores each glyph as an array of pixels (that is, a bitmap). It is less commonly known as a raster font or a pixel font. Bitmap fonts are simply collections of raster images of glyphs. For each variant of the font, there is a complete set of glyph images, with each set containing an image for each character. For example, if a font has three sizes, and any combination of bold and italic, then there must be 12 complete sets of images.

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

The Glyph Bitmap Distribution Format (BDF) by Adobe is a file format for storing bitmap fonts. The content takes the form of a text file intended to be human- and computer-readable. BDF is typically used in Unix X Window environments. It has largely been replaced by the PCF font format which is somewhat more efficient, and by scalable fonts such as OpenType and TrueType fonts.

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:

Following is an example of a 16x16 bitmap stored using both its X10 and X11 variations. Note that each array contains exactly the same data, but is stored using different data word types:

/* XBM X10 format */
#define xlogo16_width 16
#define xlogo16_height 16

static unsigned short xlogo16_bits[] = {
  0x0f80, 0x1e80, 0x3c40, 0x7820, 0x7810, 0xf008, 0xe009, 0xc005,
  0xc002, 0x4007, 0x200f, 0x201e, 0x101e, 0x083c, 0x0478, 0x02f0};

/* XBM X11 format */
#define xlogo16_width 16
#define xlogo16_height 16

static unsigned char xlogo16_bits[] = {
    0x0f, 0x80, 0x1e, 0x80, 0x3c, 0x40, 0x78, 0x20, 0x78, 0x10,
    0xf0, 0x08, 0xe0, 0x09, 0xc0, 0x05, 0xc0, 0x02, 0x40, 0x07,
    0x20, 0x0f, 0x20, 0x1e, 0x10, 0x1e, 0x08, 0x3c, 0x04, 0x78,
    0x02, 0xf0};

The order of traversing the bits of each piece of data in the bitmap font is important to achieving the desired results.

Because each pixel is only one bit in size, each byte in the array contains the information for eight pixels, with the first pixel in the bitmap (at position 0,0) represented by the high bit of the first byte in the array. If the image width is not a multiple of eight, the extra bits in the last byte of each row are not used and are ignored.

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.

Displayed XBM 16x16 image Big-endian versus Little-endian

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.

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.

Richard Chambers
  • 14,509
  • 3
  • 62
  • 86
  • The web page https://jared.geek.nz/2014/jan/custom-fonts-for-microcontrollers has a downloadable .zip file containing a variety of fonts that have been converted to raw data as C arrays of hex digits. – Richard Chambers May 28 '21 at 21:34