Python file-like object for use with print in pygame

| tags: programming

I wanted my print output to show up on pygame display. This python code implements a simple file-like object that handles multiple lines and wrapping.


'''Make print work on a pygame Surface'''

import pygame

class pygfile(object):
    def __init__(self, font=None):
        if font is None:
            font = pygame.font.Font('freesansbold.ttf', 16)
        self.font = font
        self.buff = []
    
    def write(self, text):
        self.buff.append(text)

    def _splitline(self, text, maxwidth):
        '''A helper to split lines so they will fit'''
        w,h = self.font.size(text)
        if w < maxwidth:
            return [ text ]
        n0 = 0
        n1 = len(text)
        while True:
            ng = (n0 + n1) / 2
            if ng == n0:
                break
            w,h = self.font.size(text[0:ng])
            if w < maxwidth:
                n0 = ng
            else:
                n1 = ng
        return [ text[0:ng] ] + self._splitline(text[ng:], maxwidth)
        

    def display(self, surf):
        '''Show the printed output on the given surface'''
        # get the size of the target surface
        w,h = surf.get_size()
        # join all the writes together into one string
        text = ''.join(self.buff)
        # and split it into lines
        lines = text.split('\n')
        # bust up any long lines into pieces that fit
        slines = []
        for line in lines:
            slines = slines + self._splitline(line, w)
        lines = slines
        # get the vertical space between lines
        ls = self.font.get_linesize()
        # compute the maximum number of lines that will fit
        nlines = h / ls
        # throw away lines that have scrolled off the display
        lines = lines[-nlines:]
        # render them to the surface
        sy = 0
        for line in lines:
            ts = self.font.render(line, True, (255,255,255), (0,0,0))
            surf.blit(ts, (0, sy))
            sy += ls

    def clear(self):
        '''Clear the buffer (and thus the display)'''
        self.buff = []


if __name__ == '__main__':
    import sys
    pygame.init()
    
    sys.stdout = fp = pygfile()

    screen = pygame.display.set_mode((640,480))
    msg = '1234567890'
    i = 0
    while True:
        # watch for QUIT events
        event = pygame.event.poll()
        if event.type == pygame.QUIT:
            break
        # clear the image to black
        screen.fill((0,0,0))
        # something simple to print that demonstrates the line breaking
        i += 1
        print i
        print i * msg

        # show it on the screen
        fp.display(screen)

        pygame.display.flip()

        # slow things down so I can watch it scroll
        pygame.time.wait(100)