Each sprite is nominally 6×7. The characters themselves occupy a 5×6 area, and reserve a 1px margin on the right and the bottom for occasional use by descenders (g, j, p, q, ƒ, Ç, φ, џ, comma, semicolon, etc.), box-drawing characters, and other symbols that just wouldn't consistently compress below 6×7 (↔, ↨, √, etc.).

I have crafted thus far all characters in IBM 437, Windows-1252, and the Macintosh Cyrillic codepage.

(Original work; hereby published under a dual license CC0+WTFPL.)

Could be useful for very small LCD displays, or for fitting an entire movie script on your monitor at once.


It is not finished yet; my next goal is to figure out how to create one of these so I can actually use the thing… then I'll worry about adding more characters. (Fontforge is waay too sophisticated for this use-case, though, so it has ended up screwing up the font every time I tried to render an .obt file: the letters all had "shaved tops", and the UI element to set their height manually was greyed-out. Hmm…)


Appendices

Compiler

from PIL import Image
import struct, csv, gzip

# USAGE:
#	python3 make_psf.py 6x7 cp437_tinyfont.gif unicode_cp437.tsv 437-Handmade5x6.psf2.gz && setfont -v 437-Handmade5x6.psf2.gz
#	python3 make_psf.py 6x7 cp437_tinyfont.gif unicode_x1252.tsv x1252-Handmade5x6.psf2.gz && setfont -v x1252-Handmade5x6.psf2.gz


PSF2_MAGIC = b'\x72\xb5\x4a\x86'
PSF2_HAS_UNICODE_TABLE = 0b1
PSF2_MAXVERSION = 0
PSF2_SEPARATOR = b'\xFF'
PSF2_STARTSEQ = b'\xFE'

psf2_header = struct.Struct('<4sIiIII2I')

# PCF IS WIP NOT YET IMPLEMENTED #
# https://fontforge.org/docs/techref/pcf-format.html
PCF_MAGIC = b'\x01FCP'

PCF_PROPERTIES = 0b000000001
PCF_ACCELERATORS = 0b000000010
PCF_METRICS = 0b000000100
PCF_BITMAPS = 0b000001000
PCF_INK_METRICS = 0b000010000
PCF_BDF_ENCODINGS = 0b000100000
PCF_SWIDTHS = 0b001000000
PCF_GLYPH_NAMES = 0b010000000
PCF_BDF_ACCELERATORS = 0b100000000

PCF_DEFAULT_FORMAT = 0x000
PCF_INKBOUNDS = 0x200
PCF_ACCEL_W_INKBOUNDS = 0x100
PCF_COMPRESSED_METRICS = 0x100

PCF_GLYPH_PAD_MASK = 0b000011
PCF_BYTE_MASK = 0b000100
PCF_BIT_MASK = 0b001000
PCF_SCAN_UNIT_MANK = 0b110000

pcf_header_1 = struct.Struct('<4sI')
pcf_header_2 = struct.Struct('<IIII')

def im2glyphs(im, w, h):
	for y in range(0, im.height, h):
		for x in range(0, im.width, w):
			yield im.crop((x, y, x+w, y+h))

def _makeheader(glyphs, *, ver=PSF2_MAXVERSION, flags=0):
	n = len(glyphs)
	assert len(set(glyph.size for glyph in glyphs)) == 1
	w, h = glyphs[0].size
	glyphsize = h * ((w + 7) // 8)
	#glyphsize = -(w * h // -8)
	return psf2_header.pack(PSF2_MAGIC, ver, psf2_header.size, flags, n, glyphsize, h, w)

def _makebody(glyphs):
	assert len(set(glyph.size for glyph in glyphs)) == 1
	for glyph in glyphs:
		yield glyph.convert('1').tobytes()

def _makeuctable(unicodemap):
	for position in unicodemap:
		for sequence in position:
			yield PSF2_STARTSEQ
			yield sequence.encode("utf-8")
		yield PSF2_SEPARATOR

def makepsf2(size, im, out, unicodemap=None):
	global glyphs # for debugging, run python -i 
	glyphs = list(im2glyphs(im, *size))
	flags = 0
	if unicodemap is not None:
		flags |= PSF2_HAS_UNICODE_TABLE
	else:
		assert len(glyphs) == 256
	out.write(_makeheader(glyphs, flags=flags))
	for chunk in _makebody(glyphs):
		out.write(chunk)
	if unicodemap is not None:
		for chunk in _makeuctable(unicodemap):
			out.write(chunk)

if __name__ == '__main__':
	import sys
	size = map(int, sys.argv[1].split('x'))
	im = Image.open(sys.argv[2])
	with open(sys.argv[3], 'r') as f:
		r = csv.reader(f, delimiter='\t')
		unicodemap = list(r)
		if unicodemap[0][0] == '':
			unicodemap[0][0] = '\u0000'
		if unicodemap[255][0] == '\x20' and unicodemap[0x20][0] == '\x20':
			unicodemap[255][0] = '\u00A0'
	with gzip.open(sys.argv[4], 'wb') as f:
		makepsf2(size, im, f, unicodemap)

datafiles

CP437 Unicode Table

""
☺
☻
♥
♦
♣
♠
•
◘
○
◙
♂
♀
♪	𝅘𝅥𝅮
♫
☼
►	▶	▸
◄
↕	ᛨ
‼
¶
§
▬
↨
↑	ᛏ
↓
→
←
∟
↔
▲
▼
" "
!	ǃ	ⵑ
""""
#
$
%	٪	⁒
&
'	ʻ	ʹ	ˈ	ʹ	ᑊ	ꞌ
(	❲
)	❳
*	∗	𐌟
+
,	¸	؍	‚	ꓹ
-	˗	‐	‑
.	܁	܂	․	ꓸ	𝅭
/	᜵	∕	〳	𝈺
0
1
2
3	З	Ӡ
4
5
6	б
7
8
9
:	ː	˸	։	׃	܃	܄	ः	ઃ	᛬	᠃	᠉	⁚	∶	ꓽ	꞉	︰	:
;	;
<	ᐸ	𝈶
=
>	ᐳ	𖼿	𝈷
?
@
A	Α	А
B	Β	В
C	Ϲ	С
D
E	Ε	Е
F
G
H	Η	Н
I	Ι	І	Ӏ
J	Ј
K	Κ	К
L
M	Μ	М
N	Ν
O	Ο	О
P	Ρ	Р
Q	Ԛ
R
S	Ѕ	Ꚃ
T	Τ	Т
U
V
W	Ԝ
X	Χ	Х
Y	Υ	Ү
Z	Ζ
[
\
]
^	˄	ˆ
_
`
a	а
b
c	с
d
e	е
f
g
h	һ
i	і
j	ј
k	κ
l
m
n
o	ο	о
p	р
q	ԛ
r
s	ѕ
t
u
v
w	ԝ
x	х
y	γ	у	ү
z
{
|	ӏ
}
~
⌂	🏠
Ç
ü
é
â
ä	ӓ
à
å
ç	ς	ҫ
ê
ë	ё
è	ѐ
ï	ϊ	ї
î
ì
Ä	Ӓ
Å	Å
É
æ	ӕ
Æ	Ӕ
ô
ö	ӧ
ò
û
ù
ÿ	ӱ
Ö	Ӧ
Ü
¢
£
¥
₧
ƒ
á
í
ó
ú
ñ
Ñ
ª
º
¿
⌐
¬
½
¼
¡
«
»
░
▒
▓
│
┤
╡
╢
╖
╕
╣
║
╗
╝
╜
╛
┐
└
┴
┬
├
─
┼
╞
╟
╚
╔
╩
╦
╠
═
╬
╧
╨
╤
╥
╙
╘
╒
╓
╫
╪
┘
┌
█
▄
▌
▐
▀
α
ß	β
Γ	Г	Г
π
Σ
σ
µ	μ
τ	ꚍ
Φ	Ф
Θ	Ѳ	Ө
Ω	Ω
δ
∞	ꚙ
φ
ε	є	ɛ
∩
≡	Ξ
±
≥
≤
⌠
⌡
÷
≈
°
∙
·	𐩐	٠	۰
√
ⁿ
²
■
" "

x1252 Unicode Table

○
■
↑	ᛏ
↓
→
←
║
═
╔
╗
╚
╝
░
▒
►	▶	▸
◄
│
─
┌
┐
└
┘
├
┤
┴
┬
♦
┼
█
▄
▀
▬
" "
!	ǃ	ⵑ
""""
#
$
%	٪	⁒
&
'	ʻ	ʹ	ˈ	ʹ	ᑊ	ꞌ
(	❲
)	❳
*	∗	𐌟
+
,	¸	؍	ꓹ
-	˗	‐	‑
.	܁	܂	․	ꓸ	𝅭
/	᜵	∕	〳	𝈺
0
1
2
3	З	Ӡ
4
5
6	б
7
8
9
:	ː	˸	։	׃	܃	܄	ः	ઃ	᛬	᠃	᠉	⁚	∶	ꓽ	꞉	︰	:
;	;
<	ᐸ	𝈶
=
>	ᐳ	𖼿	𝈷
?
@
A	Α	А
B	Β	В
C	Ϲ	С
D
E	Ε	Е
F
G
H	Η	Н
I	Ι	І	Ӏ
J	Ј
K	Κ	К
L
M	Μ	М
N	Ν
O	Ο	О
P	Ρ	Р
Q	Ԛ
R
S	Ѕ	Ꚃ
T	Τ	Т
U
V
W	Ԝ
X	Χ	Х
Y	Υ	Ү
Z	Ζ
[
\
]
^
_
`
a	а
b
c	с
d
e	е
f
g
h	һ
i	і
j	ј
k	κ
l
m
n
o	ο	о
p	р
q	ԛ
r
s	ѕ
t
u
v
w	ԝ
x	х
y	γ	у	ү
z
{
|	ӏ
}
~

€

‚
ƒ
„
…
†
‡
ˆ	˄
‰
Š
‹
Œ

Ž


‘
’
“
”
•
–
—
˜
™
š
›
œ

ž
Ÿ

¡
¢
£
¤
¥
¦
§
¨
©
ª
«
¬
"­"
®
¯
°
±
²
³
´
µ	μ
¶
·
¸
¹
º
»
¼
½
¾
¿
À
Á
Â
Ã
Ä	Ӓ
Å	Å
Æ	Ӕ
Ç
È
É
Ê
Ë
Ì
Í
Î
Ï
Đ
Ñ
Ò
Ó
Ô
Õ
Ö
×
Ø
Ù
Ú
Û
Ü
Ý
Þ
ß
à
á
â
ã
å
ä	ӓ
æ	ӕ
ç	ς	ҫ
è	ѐ
é
ê
ë
ì
í
î
ï
ð	∂
ñ
ò
ó
ô
õ
ö
÷
ø
ù
ú
û
ü
ý
þ
ÿ

One thought on “A very tiny bitmap font

Leave a Reply

Your email address will not be published.

Warning: This site uses Akismet to filter spam. Until or unless I can find a suitable replacement anti-spam solution, this means that (per their indemnification document) all commenters' IP addresses will be sent to Automattic, Inc., who may choose to share such with 3rd parties.
If this is unacceptable to you, I highly recommend using an anonymous proxy or public Wi-Fi connection when commenting.