#! /usr/bin/env python
#
#    TTFont - font wrapper to pygame.font.Font which uses ttmkfdir info to
#             provide a Family / Width / Slant / Size interface to fonts
#    Copyright (C) 2003  Michael Urman
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

import pickle, os

class TTFont(object):

    info_cache = "ttf.cache"

    _fonts_cache = {}

    def get_info(*args):
        try:
            return TTFont._info
        except AttributeError:
            if TTFont.info_cache:
                try:
                    TTFont._info = pickle.load(
                            file(TTFont.info_cache, 'r'))
                except (IOError, EOFError):
                    TTFont._info = TTFont._read_info()
                    pickle.dump(TTFont._info,
                            file(TTFont.info_cache, 'w'))
            else:
                TTFont._info = TTFont._read_info()
            return TTFont._info
    get_info = classmethod(get_info)
    info = property(get_info)

    def _read_info(self):
        """TTFont._read_info()

        Collect the available font information by using ttmkfdir on all paths in
        the X font path.  Return a dictionary hierarchy
        Family:Weight:Slant:File"""

        xset = os.popen("xset q 2> /dev/null")
        while not xset.readline().startswith("Font Path:"):
            pass
        dirlist = xset.readline().strip()
        dirs = dirlist.split(',')
        xset.close()

        _info = {}
        for fdir in dirs:
            self._add_dir(fdir, _info)

        return _info
    _read_info = classmethod(_read_info)

    def _add_dir(self, fdir, _info):
        mkfdir = os.popen("ttmkfdir -d %s 2> /dev/null" % fdir)
        for line in mkfdir:
            try:
                ttf, xfld = line.split(None, 1)
                xfld = xfld.lstrip('-')
            except ValueError:
                continue
            foundry, family, weight, slant, width, style, pixel, point, resx, resy, space, avgwidth, charset = xfld.split('-', 12)
            ttffile = os.path.join(fdir, ttf)
            _info.setdefault(family, {}).setdefault(weight, {}).setdefault(slant, {}).setdefault(ttffile, 0)
            _info[family][weight][slant][ttffile] += 1
    _add_dir = classmethod(_add_dir)

    def add_fontdir(self, fdir):
        self._add_dir(fdir, self.get_info())
    add_fontdir = classmethod(add_fontdir)

    def get_summary(self):
        try:
            return self._summary
        except AttributeError:
            TTFont._summary = _summary = {}
            for family, v1 in self.get_info().items():
                _summary[family] = ([], [])
                for weight, v2 in v1.items():
                    if _summary[family][0].count(weight) == 0:
                        _summary[family][0].append(weight)
                    for slant in v2.keys():
                        if _summary[family][1].count(slant) == 0:
                            _summary[family][1].append(slant)
        return _summary
    get_summary = classmethod(get_summary)
    summary = property(get_summary)

    def families(self):
        return self.get_info().keys()
    def weights(self):
        wdict = {}
        for alist in self.get_summary().values():
            for w in alist[0]:
                wdict[w] = 1
        return wdict.keys()
    def slants(self):
        sdict = {}
        for alist in self.get_summary().values():
            for s in alist[1]:
                sdict[s] = 1
        return sdict.keys()
    families = classmethod(families)
    weights = classmethod(weights)
    slants = classmethod(slants)

    def __init__(self, *args, **kvargs):
        TTFont.get_info()
        self.set_style(*args, **kvargs)

    def set_style(self, family=None, size=None, weight=None, slant=None):
        self.family = family or 'Arial'
        self.size = size or 12
        self.weight = weight or 'medium'
        self.slant = slant or 'r'

    def _get_file(self, ignore_errors=None):
        info = TTFont.get_info()
        if not info.has_key(self.family):
            if ignore_errors:
                family = info[ info.keys()[0] ]
            else:
                raise ValueError("-%s not available" % self.family)
        else:
            family = info[self.family]

        if not family.has_key(self.weight):
            if ignore_errors:
                weight = family[ family.keys()[0] ]
            else:
                raise ValueError("-%s-%s not available" % (self.family, self.weight))
        else:
            weight = family[self.weight]

        if not weight.has_key(self.slant):
            if ignore_errors:
                slant = weight[ weight.keys()[0] ]
            else:
                raise ValueError("-%s-%s-%s not available" % (self.family, self.weight, self.slant))
        else:
            slant = weight[self.slant]

        return slant.keys()[0]

    def get_family(self):
        return self._family
    def set_family(self, family):
        if family in self.get_info().keys():
            self._family = family
        else:
            raise ValueError("Family %s not available" % family)
    family = property(get_family, set_family)

    def get_weight(self):
        return self._weight
    def set_weight(self, weight):
        if weight in self.weights():
            self._weight = weight
        elif weight is None:
            self._weight = 'medium'
        else:
            raise ValueError("Weight %s not available" % weight)
    weight = property(get_weight, set_weight)

    def get_bold(self):
        return self.weight == 'bold'
    def set_bold(self, bold):
        if bold: self.weight = 'bold'
        else: self.weight = 'medium'
    bold = property(get_bold, set_bold)

    def get_slant(self):
        return self._slant
    def set_slant(self, slant):
        if slant in self.slants():
            self._slant = slant
        elif slant is None:
            self._slant = 'r'
        elif slant.lower() == 'italic':
            self._slant = 'i'
        else:
            raise ValueError("Slant %s not available" % slant)
    slant = property(get_slant, set_slant)

    def get_italic(self):
        return self._slant == 'i'
    def set_italic(self, italic):
        if italic: self.slant = 'i'
        else: self.slant = 'r'
    italic = property(get_italic, set_italic)

    # TODO: some form of underline?  what a pain due to the state machine
    # properties of the underlying fonts and the fact i'm using N of them

    def _get_font(self):
        import pygame
        desc = ':'.join((self.family, self.weight, self.slant, str(self.size)))
        try:
            font = TTFont._fonts_cache[desc]
        except KeyError:
            font = pygame.font.Font(self._get_file(), self.size)
            TTFont._fonts_cache[desc] = font
        return font
    _font = property(_get_font)

    def get_ascent(self):
        return self._font.get_ascent()
    ascent = property(get_ascent)
    def get_descent(self):
        return self._font.get_descent()
    descent = property(get_descent)
    def get_height(self):
        return self._font.get_height()
    height = property(get_height)
    def get_linesize(self):
        return self._font.get_linesize()
    linesize = property(get_linesize)

    def size(self, *args, **kvargs):
        return self._font.size(*args, **kvargs)

    def render(self, *args, **kvargs):
        return self._font.render(*args, **kvargs)

def _showsome():
    import pygame
    pygame.display.init()
    pygame.font.init()

    screen = pygame.display.set_mode((640,480))
    screen.fill((0,0,0))

    ttf = TTFont('Verdana', 48, None, 'italic')
    screen.blit(ttf.render('some fun text', 1, (120,255,120)), (0,0))

    ttf.family = 'Trebuchet MS'
    ttf.size = 32
    ttf.weight = 'bold'
    ttf.slant = None
    screen.blit(ttf.render('more fun text', 1, (120,120,255)), (0,100))

    ttf.set_style('Arial', 72)
    screen.blit(ttf.render('final fun text!', 1, (255,120,120)), (0,200))

    ttf._font.set_bold(1) # cheat and use old set_bold()
    screen.blit(ttf.render('in fake bold!', 1, (255,120,120)), (0,260))
    ttf._font.set_bold(0)

    ttf.weight = 'bold'
    screen.blit(ttf.render('in real bold!', 1, (255,120,120)), (0,320))

    pygame.display.flip()
    while not pygame.event.wait().type in (pygame.QUIT, pygame.KEYDOWN): pass

def _showall():
    import pygame
    pygame.display.init()
    pygame.font.init()

    TTFont.add_fontdir('/home/mu/fonts/lll')
    imgs = []
    ttf = TTFont()
    sum = TTFont.get_info()
    for font, v1 in sum.items():
        for w, v2 in v1.items():
            for s in v2.keys():
                ttf.set_style(font, 24, w, s)
                try:
                    imgs.append(ttf.render('Testing font ' + font + '    ' + w + ' ' + s, 1, (120,120,255)))
                except RuntimeError:
                    continue
    w = 0
    h = 0
    for img in imgs:
        h += img.get_height()
        w = max(w, img.get_width())
    screen = pygame.display.set_mode((w,h))
    screen.fill((0,0,0))
    h = 0
    for img in imgs:
        screen.blit(img, (0,h))
        h += img.get_height()

    pygame.display.flip()
    while not pygame.event.wait().type in (pygame.QUIT, pygame.KEYDOWN): pass

if __name__=='__main__':
    print "Your system appears to have the following truetype fonts:"
    print '  ', ', '.join(TTFont.families())
    print "They are available in the following weights:"
    print '  ', ', '.join(TTFont.weights())
    print "They are available in the following slants:"
    print '  ', ', '.join(TTFont.slants())

    try: _showsome()
    except: pass
    _showall()