ENG | Getting 'Retro' Bitmap Fonts in Python
How to get MS-DOS era fonts for microcontroller projects.
This post is a follow-up to my earlier article about using PCD8544 SPI display on Raspberry Pi Pico. After getting the display working, I needed fonts - and it came to my mind that I want something like 8x8 fonts used in late 80s era of MS-DOS and CGA displays or even smaller and these font tables were accessible in BIOS memory.
PCD8544 display used in Nokia 5110 phone has 84x48 resolution, which gives us:
Font Size | Characters Per Row | Rows Per Display |
---|---|---|
8x16 | 10 | 3 |
8x8 | 10 | 6 |
6x8 | 14 | 6 |
4x6 | 21 | 8 |
Font 5x3 pixels in 6x4 grid needs to be drawn by writing pixels to framebuffer, whereas 8 pixels tall font can be directly send to display.
The Search for Font
After some googling I came to The Ultimate Oldschool PC Font Pack and found nice 6x8 pixel font used on HP 100LX Palmtop. There is download section. Great, easy! Or … wait, there are fonts in OTB
, FON
, TTF
, WOFF
formats. The problem? Standard font formats aren’t directly usable in embedded systems and they require complex libraries to reador render. My goal was simple: a quick, no-frills solution to extract a usable bitmap font for my project.
Converting Font Files
I asked ChatGPT and it suggested using FreeType library in Python, after few partially successful iterations, I found Dan Bader -=- Monochrome font rendering with FreeType and Python which explores decoding and rendering of fonts into depth and I used few lines from his example to fix my code. Later I added upper half of ASCII table and got happy.
I experimented with various outputs:
- Bytearray - Direct use in Python code
- Image - Useful for debugging (removed later)
- Raw binary file - Ideal for microcontroller
By the way - because I’m not sure if FreeType library works on Windows, I used this script on Linux machine. You may try using Windows Subsystem for Linux (WSL).
Python Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/env python3
import freetype
import numpy
import codecs
# WARNING: program works for 6x8
font_file = 'Bm437_HP_100LX_6x8.otb'
font_width = 6
font_height = 8
chars_to_extract = 256
# Load font and set pixel size
face = freetype.Face(font_file)
face.set_pixel_sizes(0, font_height)
# Prepare output image
image = numpy.zeros((font_height, chars_to_extract*font_width), dtype=numpy.uint8)
for i in range(0, chars_to_extract):
# Get source bitmap of character
unicode_index = i
if (i > 127):
unicode_index = codecs.decode(bytes([i]), 'cp437')
face.load_char(unicode_index, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_MONO)
bitmap = face.glyph.bitmap
# Create target grayscale bitmap
target = numpy.zeros((font_height,font_width), dtype=numpy.uint8)
# Bitmap seems valid
if (bitmap.width == font_width and bitmap.rows == font_height):
# Go through scan lines
for src_row in range(0, font_height):
byte_value = bitmap.buffer[src_row]
# Go through bits and unpack them
for bit_index in range(font_width):
bit = byte_value & (1 << (7 - bit_index))
target[src_row, bit_index] = 255 if bit else 0
# Copy target (image of character) to image of whole character map
image[0:8, i*6:i*6+6] = target
print("font_data = bytearray( \\")
raw_data = bytearray()
# Go through image columns and encode them to bits again -> this matches framebuffer layout of PCD8544 display
for column in range(image.shape[1]):
value = 0
for bit_index in range(0,8):
bit_value = 1 if image[bit_index, column] != 0 else 0
bit_value = bit_value << bit_index
value |= bit_value
if (column % 6 == 0 and column != 0):
print("' \\")
if (column % 6 == 0):
print(" b'", end='')
print(f"\\x{value:02X}", end='')
raw_data.append(value)
print("')")
# Save the font file
with open(f'font-{font_width}x{font_height}.bin', 'wb') as file:
file.write(raw_data)
Practical Consideration & Conclusion
This script extracts a retro bitmap font with minimum overhead. Error handling? Minimal. Flexibility? Limited. It’s not an universal tool, but it gets the job done - at least for me. For those wanting a more robust solution, I recommend starting with Dan’s Bader article.
Since there are BIOS fonts available in more reasonable formats, I won’t probably use this script again, but who knows.
Also there are likely some libraries which already make using framebuffer or display easier (actually MicroPython’s framebuffer library likely)
Links
- Dan Bader -=- Monochrome font rendering with FreeType and Python
- Using PCD8544 SPI display on Raspberry Pi Pico - here it’s already mentioned how to use extracted font.
Font sites
- The Ultimate Oldschool PC Font Pack
- BIOS ROM Fonts - 8x8, 8x14, 8x16 BIOS fonts
- A ‘packed’ 5x7 Font Table (for Nokia 5110) 5x7 and 10x14 fonts
- 5x3 font
- Nokia cellphone font
Converted font file
- Bm437 HP 100LX Font file - be careful about licence.