#! /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()