diff --git a/python/bitmapfont.py b/python/bitmapfont.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cd5a042ea45e6c824961f000996db3bd1323157
--- /dev/null
+++ b/python/bitmapfont.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+
+# 5x7 font from http://www.hwsw.no/snippets/5x7_LCD_font.php
+# every byte contains pixel data for one column, LSB is on top, MSB is ignored.
+FONT = {
+    " ":  [0x00,0x00,0x00,0x00,0x00],
+    "!":  [0x00,0x00,0x4F,0x00,0x00],
+    "\"": [0x00,0x07,0x00,0x07,0x00],
+    "#":  [0x14,0x7F,0x14,0x7F,0x14],
+    "$":  [0x24,0x2A,0x7F,0x2A,0x12],
+    "%":  [0x23,0x13,0x08,0x64,0x62],
+    "&":  [0x36,0x49,0x55,0x22,0x50],
+    "'":  [0x00,0x05,0x03,0x00,0x00],
+    "(":  [0x00,0x1C,0x22,0x41,0x00],
+    ")":  [0x00,0x41,0x22,0x1C,0x00],
+    "*":  [0x14,0x08,0x3E,0x08,0x14],
+    "+":  [0x08,0x08,0x3E,0x08,0x08],
+    ",":  [0x00,0x50,0x30,0x00,0x00],
+    "-":  [0x08,0x08,0x08,0x08,0x08],
+    ".":  [0x00,0x60,0x60,0x00,0x00],
+    "/":  [0x20,0x10,0x08,0x04,0x02],
+
+    "0":  [0x3E,0x51,0x49,0x45,0x3E],
+    "1":  [0x00,0x42,0x7F,0x40,0x00],
+    "2":  [0x42,0x61,0x51,0x49,0x46],
+    "3":  [0x21,0x41,0x45,0x4B,0x31],
+    "4":  [0x18,0x14,0x12,0x7F,0x10],
+    "5":  [0x27,0x45,0x45,0x45,0x39],
+    "6":  [0x3C,0x4A,0x49,0x49,0x30],
+    "7":  [0x01,0x71,0x09,0x05,0x03],
+    "8":  [0x36,0x49,0x49,0x49,0x36],
+    "9":  [0x06,0x49,0x49,0x29,0x1E],
+    ":":  [0x36,0x36,0x00,0x00,0x00],
+    ";":  [0x56,0x36,0x00,0x00,0x00],
+    "<":  [0x08,0x14,0x22,0x41,0x00],
+    "=":  [0x14,0x14,0x14,0x14,0x14],
+    ">":  [0x00,0x41,0x22,0x14,0x08],
+    "?":  [0x02,0x01,0x51,0x09,0x06],
+    
+    "@":  [0x30,0x49,0x79,0x41,0x3E],
+    "A":  [0x7E,0x11,0x11,0x11,0x7E],
+    "B":  [0x7F,0x49,0x49,0x49,0x36],
+    "C":  [0x3E,0x41,0x41,0x41,0x22],
+    "D":  [0x7F,0x41,0x41,0x22,0x1C],
+    "E":  [0x7F,0x49,0x49,0x49,0x41],
+    "F":  [0x7F,0x09,0x09,0x09,0x01],
+    "G":  [0x3E,0x41,0x49,0x49,0x7A],
+    "H":  [0x7F,0x08,0x08,0x08,0x7F],
+    "I":  [0x00,0x41,0x7F,0x41,0x00],
+    "J":  [0x20,0x40,0x41,0x3F,0x01],
+    "K":  [0x7F,0x08,0x14,0x22,0x41],
+    "L":  [0x7F,0x40,0x40,0x40,0x40],
+    "M":  [0x7F,0x02,0x0C,0x02,0x7F],
+    "N":  [0x7F,0x04,0x08,0x10,0x7F],
+    "O":  [0x3E,0x41,0x41,0x41,0x3E],
+    
+    "P":  [0x7F,0x09,0x09,0x09,0x06],
+    "Q":  [0x3E,0x41,0x51,0x21,0x5E],
+    "R":  [0x7F,0x09,0x19,0x29,0x46],
+    "S":  [0x46,0x49,0x49,0x49,0x31],
+    "T":  [0x01,0x01,0x7F,0x01,0x01],
+    "U":  [0x3F,0x40,0x40,0x40,0x3F],
+    "V":  [0x1F,0x20,0x40,0x20,0x1F],
+    "W":  [0x3F,0x40,0x30,0x40,0x3F],
+    "X":  [0x63,0x14,0x08,0x14,0x63],
+    "Y":  [0x07,0x08,0x70,0x08,0x07],
+    "Z":  [0x61,0x51,0x49,0x45,0x43],
+    "[":  [0x00,0x7F,0x41,0x41,0x00],
+    "\\": [0x02,0x04,0x08,0x10,0x20],
+    "]":  [0x00,0x41,0x41,0x7F,0x00],
+    "^":  [0x04,0x02,0x01,0x02,0x04],
+    "_":  [0x40,0x40,0x40,0x40,0x40],
+    
+    "`":  [0x00,0x01,0x02,0x04,0x00],
+    "a":  [0x20,0x54,0x54,0x54,0x78],
+    "b":  [0x7F,0x50,0x48,0x48,0x30],
+    "c":  [0x38,0x44,0x44,0x44,0x20],
+    "d":  [0x38,0x44,0x44,0x48,0x7F],
+    "e":  [0x38,0x54,0x54,0x54,0x18],
+    "f":  [0x08,0x7E,0x09,0x01,0x02],
+    "g":  [0x0C,0x52,0x52,0x52,0x3E],
+    "h":  [0x7F,0x08,0x04,0x04,0x78],
+    "i":  [0x00,0x44,0x7D,0x40,0x00],
+    "j":  [0x20,0x40,0x44,0x3D,0x00],
+    "k":  [0x7F,0x10,0x28,0x44,0x00],
+    "l":  [0x00,0x41,0x7F,0x40,0x00],
+    "m":  [0x78,0x04,0x58,0x44,0x78],
+    "n":  [0x7C,0x08,0x04,0x04,0x78],
+    "o":  [0x38,0x44,0x44,0x44,0x38],
+    
+    "p":  [0x7C,0x14,0x14,0x14,0x08],
+    "q":  [0x08,0x14,0x14,0x18,0x7C],
+    "r":  [0x7C,0x08,0x04,0x04,0x08],
+    "s":  [0x48,0x54,0x54,0x54,0x20],
+    "t":  [0x04,0x3F,0x44,0x40,0x20],
+    "u":  [0x3C,0x40,0x40,0x20,0x7C],
+    "v":  [0x1C,0x20,0x40,0x20,0x1C],
+    "w":  [0x3C,0x40,0x30,0x40,0x3C],
+    "x":  [0x44,0x28,0x10,0x28,0x44],
+    "y":  [0x0C,0x50,0x50,0x50,0x3C],
+    "z":  [0x44,0x64,0x54,0x4C,0x44],
+    "{":  [0x00,0x08,0x36,0x41,0x00],
+    "|":  [0x00,0x00,0x7F,0x00,0x00],
+    "}":  [0x00,0x41,0x36,0x08,0x00],
+    "~":  [0x0C,0x02,0x0C,0x10,0x0C],
+    "\x7f":  [0x55,0xAA,0x55,0xAA,0x55],
+}
+
+import client
+import clearscreen
+
+def test():
+    clearscreen.clear()
+    client.write(0,0,"Hello World!")
+    x = 0
+    y = 1
+    for c in sorted(FONT.keys()):
+        client.blit(x*client.PWIDTH, y*client.PHEIGHT,
+            client.PWIDTH, client.PHEIGHT, client.char_to_pixel_segment(c))
+        x += 1
+        if(x > 15):
+            x = 0
+            y += 1
diff --git a/python/client.py b/python/client.py
index 4a6b93014f7b34a755b40743d4f4d75eeb92f0d6..f294592b8605bc2cd7229510f345c5053dfcbbd8 100644
--- a/python/client.py
+++ b/python/client.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 
 import os
+import bitmapfont
 
 SPLIT_N = 1
 SPLIT_I = 0
@@ -34,6 +35,10 @@ context = zmq.Context()
 socket = context.socket(zmq.REQ)
 socket.connect(SERVER)
 
+################################################################################
+# RAW PIXEL HANDLING
+################################################################################
+
 def set_pixel(x, y, v):
   x += WOFFSET
   tx = struct.pack('<BiiB', 0, x, y, v)
@@ -56,3 +61,30 @@ def blit(x, y, w, h, pixels):
   socket.send_multipart([msg, b''])
   rx = socket.recv()
 
+################################################################################
+# TEXT RENDERING WITH BITMAP FONT
+################################################################################
+
+# return array of size PWIDTH * PHEIGHT (indexed by row, then column)
+def char_to_pixel_segment(c):
+    pixels = [0] * PWIDTH * PHEIGHT
+    if(c in bitmapfont.FONT.keys()):
+        for x in xrange(0, PWIDTH):
+            for y in xrange(0, PHEIGHT):
+                pix = (bitmapfont.FONT[c][x] & (1<<y)) >> y
+                pixels[y * PWIDTH + x] = pix
+    return pixels
+
+# write string, starting at segment x,y. no boundary checks are done, text may
+# be clipped at the border, in this case False is returned.
+def write(x, y, string):
+    for c in string:
+        blit(x*PWIDTH, y*PHEIGHT, PWIDTH, PHEIGHT, char_to_pixel_segment(c))
+        x += 1
+        if(x > NUM_SEG_X):
+            return False
+
+# write text at beginning of line and clear remaining horizontal space
+def writeline(y, string):
+    write(0, y, string)
+    write(len(string), y, ' ' * (NUM_SEG_X-len(string)))