I won't ruin the surprise about what feature I'm working on right now for VGATonic (it's a crazy one), but I needed to add a font. Let me rephrase - a tiny font. Let me be more specific - the most space efficient font possible.
My searching first led me to the 4x6 font here: http://robey.lag.net/2010/01/23/tiny-monospace-font.html made with an assist by 'Robey' by Brian Swetland of Palm Pilot fame (I've got a working Palm Pilot with a minor crack in the screen in the office). Further clicking led me here, where "Dr_Acula" had already encoded it into 96x3 bytes: http://forums.parallax.com/discussion/comment/1073601#Comment_1073601
We can do better, though - you see, it's really a "3x5" font, with whitespaces.
3x5 = 15, which is less than 16 (one word, or 2 8 bit bytes). You guessed it - I wrote up some python to make it even smaller, and jammed each character into 2 bytes. It's so small, I'm going to post it in its entirety here (in C, for AVR - easy enough to convert for your favorite language):
// Font Definition
const uint8_t font4x6 [96][2] PROGMEM = {
{ 0x00 , 0x00 }, /*SPACE*/
{ 0x49 , 0x08 }, /*'!'*/
{ 0xb4 , 0x00 }, /*'"'*/
{ 0xbe , 0xf6 }, /*'#'*/
{ 0x7b , 0x7a }, /*'$'*/
{ 0xa5 , 0x94 }, /*'%'*/
{ 0x55 , 0xb8 }, /*'&'*/
{ 0x48 , 0x00 }, /*'''*/
{ 0x29 , 0x44 }, /*'('*/
{ 0x44 , 0x2a }, /*')'*/
{ 0x15 , 0xa0 }, /*'*'*/
{ 0x0b , 0x42 }, /*'+'*/
{ 0x00 , 0x50 }, /*','*/
{ 0x03 , 0x02 }, /*'-'*/
{ 0x00 , 0x08 }, /*'.'*/
{ 0x25 , 0x90 }, /*'/'*/
{ 0x76 , 0xba }, /*'0'*/
{ 0x59 , 0x5c }, /*'1'*/
{ 0xc5 , 0x9e }, /*'2'*/
{ 0xc5 , 0x38 }, /*'3'*/
{ 0x92 , 0xe6 }, /*'4'*/
{ 0xf3 , 0x3a }, /*'5'*/
{ 0x73 , 0xba }, /*'6'*/
{ 0xe5 , 0x90 }, /*'7'*/
{ 0x77 , 0xba }, /*'8'*/
{ 0x77 , 0x3a }, /*'9'*/
{ 0x08 , 0x40 }, /*':'*/
{ 0x08 , 0x50 }, /*';'*/
{ 0x2a , 0x44 }, /*'<'*/
{ 0x1c , 0xe0 }, /*'='*/
{ 0x88 , 0x52 }, /*'>'*/
{ 0xe5 , 0x08 }, /*'?'*/
{ 0x56 , 0x8e }, /*'@'*/
{ 0x77 , 0xb6 }, /*'A'*/
{ 0x77 , 0xb8 }, /*'B'*/
{ 0x72 , 0x8c }, /*'C'*/
{ 0xd6 , 0xba }, /*'D'*/
{ 0x73 , 0x9e }, /*'E'*/
{ 0x73 , 0x92 }, /*'F'*/
{ 0x72 , 0xae }, /*'G'*/
{ 0xb7 , 0xb6 }, /*'H'*/
{ 0xe9 , 0x5c }, /*'I'*/
{ 0x64 , 0xaa }, /*'J'*/
{ 0xb7 , 0xb4 }, /*'K'*/
{ 0x92 , 0x9c }, /*'L'*/
{ 0xbe , 0xb6 }, /*'M'*/
{ 0xd6 , 0xb6 }, /*'N'*/
{ 0x56 , 0xaa }, /*'O'*/
{ 0xd7 , 0x92 }, /*'P'*/
{ 0x76 , 0xee }, /*'Q'*/
{ 0x77 , 0xb4 }, /*'R'*/
{ 0x71 , 0x38 }, /*'S'*/
{ 0xe9 , 0x48 }, /*'T'*/
{ 0xb6 , 0xae }, /*'U'*/
{ 0xb6 , 0xaa }, /*'V'*/
{ 0xb6 , 0xf6 }, /*'W'*/
{ 0xb5 , 0xb4 }, /*'X'*/
{ 0xb5 , 0x48 }, /*'Y'*/
{ 0xe5 , 0x9c }, /*'Z'*/
{ 0x69 , 0x4c }, /*'['*/
{ 0x91 , 0x24 }, /*'\'*/
{ 0x64 , 0x2e }, /*']'*/
{ 0x54 , 0x00 }, /*'^'*/
{ 0x00 , 0x1c }, /*'_'*/
{ 0x44 , 0x00 }, /*'`'*/
{ 0x0e , 0xae }, /*'a'*/
{ 0x9a , 0xba }, /*'b'*/
{ 0x0e , 0x8c }, /*'c'*/
{ 0x2e , 0xae }, /*'d'*/
{ 0x0e , 0xce }, /*'e'*/
{ 0x56 , 0xd0 }, /*'f'*/
{ 0x55 , 0x3B }, /*'g'*/
{ 0x93 , 0xb4 }, /*'h'*/
{ 0x41 , 0x44 }, /*'i'*/
{ 0x41 , 0x51 }, /*'j'*/
{ 0x97 , 0xb4 }, /*'k'*/
{ 0x49 , 0x44 }, /*'l'*/
{ 0x17 , 0xb6 }, /*'m'*/
{ 0x1a , 0xb6 }, /*'n'*/
{ 0x0a , 0xaa }, /*'o'*/
{ 0xd6 , 0xd3 }, /*'p'*/
{ 0x76 , 0x67 }, /*'q'*/
{ 0x17 , 0x90 }, /*'r'*/
{ 0x0f , 0x38 }, /*'s'*/
{ 0x9a , 0x8c }, /*'t'*/
{ 0x16 , 0xae }, /*'u'*/
{ 0x16 , 0xba }, /*'v'*/
{ 0x16 , 0xf6 }, /*'w'*/
{ 0x15 , 0xb4 }, /*'x'*/
{ 0xb5 , 0x2b }, /*'y'*/
{ 0x1c , 0x5e }, /*'z'*/
{ 0x6b , 0x4c }, /*'{'*/
{ 0x49 , 0x48 }, /*'|'*/
{ 0xc9 , 0x5a }, /*'}'*/
{ 0x54 , 0x00 }, /*'~'*/
{ 0x56 , 0xe2 } /*''*/
};
// Font retreival function - ugly, but needed.
unsigned char getFontLine(unsigned char data, int line_num) {
const uint8_t index = (data-32);
unsigned char pixel = 0;
if (pgm_read_byte(&font4x6[index][1]) & 1 == 1) line_num -= 1;
if (line_num == 0) {
pixel = (pgm_read_byte(&font4x6[index][0])) >> 4;
} else if (line_num == 1) {
pixel = (pgm_read_byte(&font4x6[index][0])) >> 1;
} else if (line_num == 2) {
// Split over 2 bytes
return (((pgm_read_byte(&font4x6[index][0])) & 0x03) << 2) | (((pgm_read_byte(&font4x6[index][1])) & 0x02));
} else if (line_num == 3) {
pixel = (pgm_read_byte(&font4x6[index][1])) >> 4;
} else if (line_num == 4) {
pixel = (pgm_read_byte(&font4x6[index][1])) >> 1;
}
return pixel & 0xE;
}
To decode, you pass in the character you want (ASCII, so say 'c' or 0x63) and a line number. Line 0 through 4 will be the magic lines, so set up your loop accordingly.
And the Descenders? ('g', 'j', 'p', 'q', 'y')
I manually changed the descenders to have a '1' in the LSB of the second byte. That's the second line of the decoder program - if there is a one in that spot, I shift the whole thing down a line. (Why waste a bit, right? I think the CPLD work in those tight conditions is in my head still).
I manually edited the 'j' and the 'z', but before you ask - it can stay as the MIT license. Enjoy, and please let me know it you use it!
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
Numbers?
Are you sure? yes | no
i used your font as is to enable serial output to a 32x64 led panel with an arduino nano
https://github.com/CamelCaseName/HUB75nano
its really working great
Are you sure? yes | no
I made a take on this targeting writing in page mode (such as for an SSD1306 screen) which required me to rotate the bytes used in the font for ease of converting into binary output.
Here's a link to it as it is now for anyone interested: https://pastebin.com/tphFLG8j
It's in C# for .Net Core, and currently works with a byte array, but it should be easy enough to convert to your chosen language and demonstrates how you can write this cool tiny font in paged mode.
Also, thanks to VS's new delimited binary notation, it's super easy to see and edit the contents of the font bytes.
Enjoy!
R
Are you sure? yes | no
I am confused by the bit-packing. Reading the code, if I number bits from 1 (lowest) to 8 (highest), it feels like line 0 = byte0, bits 5-7; line 1 = byte0, bits 2-4; line 2= byte0, bits 1&2 and byte1, bit 2; line 3=byte1, bits 5-7, line 4=byte 1, bits 2-4; and the flag is byte1, bit 1. So bit 8 of both bytes is unused, and bit 2 of both bytes is used in two different lines. Line 2 looks like it has no low bit, and its middle bit is overwritten. What'm I missing?
Are you sure? yes | no
I guess you missed the bitwise & 0xE in the return statement, clearing the LSB (bit 1 by your numbering) in each returned bit-pattern; this extends the 3-column glyphs by a 4th blank column, which creates the spacing between characters.
So lines 0 / 3 consist of bits 6-8 of byte 0 / 1, while lines 1 / 4 are encoded in bits 3-5; so bits 1-2 of both bytes remaining, which encode the middle row (byte 0, bits 1-2 and byte 1 bit 2) and the flag in byte 1, bit 1.
Are you sure? yes | no
Man, thank you! It's just what I've been looking for!
Are you sure? yes | no
Very nice! I'll make sure to use it the next time I need some text.
Are you sure? yes | no
Hey this is great! I might try and add it to my nokia 1100 serial lcd
Are you sure? yes | no
Keep me posted on how it looks on a small screen - when blown up on a monitor it's not that hard to read.
Are you sure? yes | no
Ha that is tiny!
Are you sure? yes | no
Haha - I wish it was even tinier! Wait until you see how I jammed it into the code, I'm proud/embarrassed about it working.
Are you sure? yes | no