Commits

Anonymous committed a2c775e

Made freetype module Python 3.x interpreter-safe.
Fixed SDL dependency in freetype module.
Rewrote pygame2.font to take different font filetypes into account, too.

Comments (0)

Files changed (8)

 * Add palette color support to sdlext.transform (trunk rev. 2242).
 * Check trunk rev. 1918, 1921, 1922, 1933, 1953 (blit blend operations).
 * Check trunk rev. 1937, 1947 (blit blend for self).
-* Add FT_GLYPH_BBOX_XXX exports in a  freetype.constants module ->
-  freetype.Font.get_metrics (text, ptsize, FT_GLYPH_XXX)
 * Mark methods, which are limited to certain bpp ranges.
 * Add doc notes about quit() behaviour of the various modules.
 * add pgcompat.h to *mod.h internal headers.

config/config_generic.py

         if not self.configured:
             module.canbuild = optional
             return
-        
+
         # update compiler/linker args for the module
         module.cflags += self.cflags
         module.lflags += self.lflags
 ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 ##
 
-"font, used to find system fonts"
+import glob, os, sys, subprocess
 
-import os, sys
+#
+# font styles consist of (fullname, filetype, bold, italic)
+#
 
-Sysfonts = {}
-Sysalias = {}
+_fonts = {}
+_aliases = {}
+_families = {}
 
-def _simplename(name):
+# Fonts on windows
+# Info taken from:
+# http://www.microsoft.com/typography/fonts/winxp.htm
+# with extra files added from:
+# http://www.ampsoft.net/webdesign-l/windows-fonts-by-version.html
+# File name, family, (Bold, Italic)
+_win32_fontfiles = [
+    ('ahronbd.ttf', 'Aharoni', True, False),
+    ('andlso.ttf', 'Andalus', False, False),
+    ('angsa.ttf', 'Angsana New', False, False),
+    ('angsab.ttf', 'Angsana New', True, False),
+    ('angsai.ttf', 'Angsana New', False, True),
+    ('angsaz.ttf', 'Angsana New', True, True),
+    ('angsau.ttf', 'AngsanaUPC', False, False),
+    ('angsaub.ttf', 'AngsanaUPC', True, False),
+    ('angsaui.ttf', 'AngsanaUPC', False, True),
+    ('angsauz.ttf', 'AngsanaUPC', True, True),
+    ('artro.ttf', 'Arabic Transparent', False, False),
+    ('artrbdo.ttf', 'Arabic Transparent', True, False),
+    ('agatha.ttf', 'Agatha', False, False),
+    ('arial.ttf', 'Arial', False, False),
+    ('arialbd.ttf', 'Arial', True, False),
+    ('ariali.ttf', 'Arial', False, True),
+    ('arialbi.ttf', 'Arial', True, True),
+    ('ariblk.ttf', 'Arial Black', False, False),
+    ('browa.ttf', 'Browallia New', False, False),
+    ('browab.ttf', 'Browallia New', True, False),
+    ('browai.ttf', 'Browallia New', False, True),
+    ('browaz.ttf', 'Browallia New', True, True),
+    ('browau.ttf', 'BrowalliaUPC', False, False),
+    ('browaub.ttf', 'BrowalliaUPC', True, False),
+    ('browaui.ttf', 'BrowalliaUPC', False, True),
+    ('browauz.ttf', 'BrowalliaUPC', True, True),
+    ('comic.ttf', 'Comic Sans MS', False, False),
+    ('comicbd.ttf', 'Comic Sans MS', True, False),
+    ('cordia.ttf', 'Cordia New', False, False),
+    ('cordiab.ttf', 'Cordia New', True, False),
+    ('cordiai.ttf', 'Cordia New', False, True),
+    ('cordiaz.ttf', 'Cordia New', True, True),
+    ('cordiau.ttf', 'CordiaUPC', False, False),
+    ('cordiaub.ttf', 'CordiaUPC', True, False),
+    ('cordiaui.ttf', 'CordiaUPC', False, True),
+    ('cordiauz.ttf', 'CordiaUPC', True, True),
+    ('cour.ttf', 'Courier New', False, False),
+    ('courbd.ttf', 'Courier New', True, False),
+    ('couri.ttf', 'Courier New', False, True),
+    ('courbi.ttf', 'Courier New', True, True),
+    ('david.ttf', 'David', False, False),
+    ('davidbd.ttf', 'David', True, False),
+    ('davidtr.ttf', 'David Transparent', False, False),
+    ('upcdl.ttf', 'DilleniaUPC', False, False),
+    ('upcdb.ttf', 'DilleniaUPC', True, False),
+    ('upcdi.ttf', 'DilleniaUPC', False, True),
+    ('upcdbi.ttf', 'DilleniaUPC', True, True),
+    ('estre.ttf', 'Estrangelo Edessa', False, False),
+    ('upcel.ttf', 'EucrosialUPC', False, False),
+    ('upceb.ttf', 'EucrosialUPC', True, False),
+    ('upcei.ttf', 'EucrosialUPC', False, True),
+    ('upcebi.ttf', 'EucrosialUPC', True, True),
+    ('mriamfx.ttf', 'Fixed Miriam Transparent', False, False),
+    ('framd.ttf', 'Franklin Gothic Medium', False, False),
+    ('framdit.ttf', 'Franklin Gothic Medium', False, True),
+    ('frank.ttf', 'FrankRuehl', False, False),
+    ('upcfl.ttf', 'FreesialUPC', False, False),
+    ('upcfb.ttf', 'FreesialUPC', True, False),
+    ('upcfi.ttf', 'FreesialUPC', False, True),
+    ('upcfbi.ttf', 'FreesialUPC', True, True),
+    ('gautami.ttf', 'Gautami', False, False),
+    ('georgia.ttf', 'Georgia', False, False),
+    ('georgiab.ttf', 'Georgia', True, False),
+    ('georgiai.ttf', 'Georgia', False, True),
+    ('georgiaz.ttf', 'Georgia', True, True),
+    ('impact.ttf', 'Impact', False, False),
+    ('upcil.ttf', 'IrisUPC', False, False),
+    ('upcib.ttf', 'IrisUPC', True, False),
+    ('upcii.ttf', 'IrisUPC', False, True),
+    ('upcibi.ttf', 'IrisUPC', True, True),
+    ('upcjl.ttf', 'JasmineUPC', False, False),
+    ('upcjb.ttf', 'JasmineUPC', True, False),
+    ('upcji.ttf', 'JasmineUPC', False, True),
+    ('upcjbi.ttf', 'JasmineUPC', True, True),
+    ('upckl.ttf', 'KodchiangUPC', False, False),
+    ('upckb.ttf', 'KodchiangUPC', True, False),
+    ('upcki.ttf', 'KodchiangUPC', False, True),
+    ('upckbi.ttf', 'KodchiangUPC', True, True),
+    ('latha.ttf', 'Latha', False, False),
+    ('lvnm.ttf', 'Levenim MT', False, False),
+    ('lvnmbd.ttf', 'Levenim MT', True, False),
+    ('upcll.ttf', 'LilyUPC', False, False),
+    ('upclb.ttf', 'LilyUPC', True, False),
+    ('upcli.ttf', 'LilyUPC', False, True),
+    ('upclbi.ttf', 'LilyUPC', True, True),
+    ('lucon.ttf', 'Lucida Console', False, False),
+    ('l_10646.ttf', 'Lucida Sans Unicode', False, False),
+    ('mangal.ttf', 'Mangal', False, False),
+    ('marlett.ttf', 'Marlett', False, False),
+    ('micross.ttf', 'Microsoft Sans Serif', False, False),
+    ('mriam.ttf', 'Miriam', False, False),
+    ('mriamc.ttf', 'Miriam Fixed', False, False),
+    ('mriamtr.ttf', 'Miriam Transparent', False, False),
+    ('mvboli.ttf', 'MV Boli', False, False),
+    ('nrkis.ttf', 'Narkisim', False, False),
+    ('pala.ttf', 'Falatino Linotype', False, False),
+    ('palab.ttf', 'Falatino Linotype', True, False),
+    ('palai.ttf', 'Falatino Linotype', False, True),
+    ('palabi.ttf', 'Falatino Linotype', True, True),
+    ('raavi.ttf', 'Raavi', False, False),
+    ('rod.ttf', 'Rod', False, False),
+    ('rodtr.ttf', 'Rod Transparent', False, False),
+    ('shruti.ttf', 'Shruti', False, False),
+    ('simpo.ttf', 'Simplified Arabic', False, False),
+    ('simpbdo.ttf', 'Simplified Arabic', True, False),
+    ('simpfxo.ttf', 'Simplified Arabic Fixed', False, False),
+    ('sylfaen.ttf', 'Sylfaen', False, False),
+    ('symbol.ttf', 'Symbol', False, False),
+    ('tahoma.ttf', 'Tahoma', False, False),
+    ('tahomabd.ttf', 'Tahoma', True, False),
+    ('times.ttf', 'Times New Roman', False, False),
+    ('timesbd.ttf', 'Times New Roman', True, False),
+    ('timesi.ttf', 'Times New Roman', False, True),
+    ('timesbi.ttf', 'Times New Roman', True, True),
+    ('trado.ttf', 'Traditional Arabic', False, False),
+    ('tradbdo.ttf', 'Traditional Arabic', True, False),
+    ('Trebuc.ttf', 'Trebuchet MS', False, False),
+    ('Trebucbd.ttf', 'Trebuchet MS', True, False),
+    ('Trebucit.ttf', 'Trebuchet MS', False, True),
+    ('Trebucbi.ttf', 'Trebuchet MS', True, True),
+    ('tunga.ttf', 'Tunga', False, False),
+    ('verdana.ttf', 'Verdana', False, False),
+    ('verdanab.ttf', 'Verdana', True, False),
+    ('verdanai.ttf', 'Verdana', False, True),
+    ('verdanaz.ttf', 'Verdana', True, True),
+    ('webdings.ttf', 'Webdings', False, False),
+    ('wingding.ttf', 'Wingdings', False, False),
+    ('simhei.ttf', 'SimHei', False, False),
+    ('simfang.ttf', 'FangSong_GB2312', False, False),
+    ('kaiu.ttf', 'DFKai-SB', False, False),
+    ('simkai.ttf', 'KaiTi_GB2312', False, False),
+    ('msgothic.ttc', 'MS Gothic', False, False),
+    ('msmincho.ttc', 'MS Mincho', False, False),
+    ('gulim.ttc', 'Gulim', False, False),
+    ('mingliu.ttc', 'Mingliu', False, False),
+    ('simsun.ttc', 'Simsun', False, False),
+    ('batang.ttc', 'Batang', False, False),
+    ]
+
+def _simplename (name):
+    """_simplename (name) -> str
+
+    Simplifies a font name, removing any characters not being alphanumeric.
     """
-    Create simple version of the font name
+    return ''.join ([c.lower () for c in name if c.isalnum ()])
+
+def _addfont (filename, name, ftype, family, bold, italic):
+    """_addfont (filename, name, ftype, family, bold, italic) -> None
+
+    Adds a font and its family to the internal font file caches.
     """
-    for char in '_ -':
-        name = name.replace(char, '')
-    name = name.lower()
-    name = name.replace('-', '')
-    name = name.replace("'", '')
-    return name
+    family = _simplename (family)
+    if family not in _families:
+        _families[family] = []
+    if name in _fonts:
+        print ("*** Font %s will be overwritten" % name)
+    _fonts[filename] = (name, ftype, bold, italic)
+    _families[family].append (filename)
 
-def _addfont(name, bold, italic, font, fontdict):
+def _gettype (filename):
+    """_gettype (filename) -> str
+
+    Gets the type of the font file, based on its file extension.
     """
-    insert a font and style into the font dictionary
+    ftype = os.path.splitext (filename)[1].lstrip (".")
+    if filename.endswith (".pcf.gz"):
+        ftype = "pcf"
+    return ftype.lower ()
+
+def _getstyle (style):
+    """_getstyle (style) -> bool, bool
+
+    Gets the bold and italic style properties from the passed style.
     """
-    if not fontdict.has_key(name):
-        fontdict[name] = {}
-    fontdict[name][bold, italic] = font
+    bold = italic = oblique = False
+    if sys.platform == "win32":
+        pass
+    else:
+        bold = style.find ("Bold") >= 0
+        italic = style.find ("Italic") >= 0
+        oblique = style.find ("Oblique") >= 0
+    return bold, italic or oblique
 
+def _initwin32 ():
+    """_initwin32 () -> None
+    
+    Initializes the win32-based font cache.
+    """
+    fontdir = "C:\\Windows\\Fonts"
+    if "WINDIR" in os.environ:
+        fontdir = os.path.join (os.environ["WINDIR"], "Fonts")
+    elif "windir" in os.environ:
+        fontdir = os.path.join (os.environ["windir"], "Fonts")
+    
+    # Build the lookup table.
+    lookups = dict ([(fname.lower (), (_simplename(name), bold, italic))
+                     for fname, name, bold, italic in _win32_fontfiles])
+    
+    # Walk over the windows font file directory first.
+    files = glob.glob (os.path.join (fontdir, "*"))
+    for font in files:
+        font = font.lower ()
+        filename = os.path.basename (font)
+        name = os.path.splitext (filename)[0]
+        ftype = _gettype (filename)
+        family = ""
+        bold = italic = False
+        if filename in lookups:
+            name, bold, italic = lookups[filename]
+        _addfont (font, name, ftype, family, bold, italic)
 
-def _initsysfonts_win32():
-    """
-    read the fonts on windows
-    """
-    import _winreg
-    fonts = {}
-    mods = 'demibold', 'narrow', 'light', 'unicode', 'bt', 'mt'
-    fontdir = os.path.join(os.environ['WINDIR'], "Fonts")
+    # Lookup any mappings existing in the registry.
+    try:
+        import _winreg
+    except ImportError:
+        try:
+            import winreg as _winreg
+        except ImportError:
+            return
 
-    #this is a list of registry keys containing information
-    #about fonts installed on the system.
-    keys = []
-
-    #find valid registry keys containing font information.
     possible_keys = [
         r"SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts",
-        r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"
+        r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts",
+        r"SOFTWARE\Microsoft\Windows[NT]\CurrentVersion\Fonts",
         ]
-
+    keys = []
     for key_name in possible_keys:
         try:
-            key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, key_name)
-            keys.append(key)
+            key = _winreg.OpenKey (_winreg.HKEY_LOCAL_MACHINE, key_name)
+            keys.append (key)
         except WindowsError:
             pass
 
     for key in keys:
         fontdict = {}
-        for i in range(_winreg.QueryInfoKey(key)[1]):
-            try: name, font, t = _winreg.EnumValue(key,i)
-            except EnvironmentError: break
+        for i in range (_winreg.QueryInfoKey (key)[1]):
+            name, font, t = None, None, None
+            try:
+                name, font, t = _winreg.EnumValue (key, i)
+            except EnvironmentError:
+                break
 
             # try and handle windows unicode strings for some file names.
             
             # http://www.python.org/peps/pep-0277.html
             # https://www.microsoft.com/technet/archive/interopmigration/linux/mvc/lintowin.mspx#ECAA
             try:
-                font = str(font)
+                font = str (font)
             except UnicodeEncodeError:
                 # MBCS is the windows encoding for unicode file names.
                 try:
-                    font = font.encode('MBCS')
+                    font = font.encode ('MBCS')
                 except:
                     # no goodness with str or MBCS encoding... skip this font.
                     continue
-   
-            if font[-4:].lower() not in [".ttf", ".ttc"]:
-                continue
-            if os.sep not in font:
-                font = os.path.join(fontdir, font)
+            if os.path.sep not in font:
+                font = os.path.join (fontdir, font)
+            font = font.lower ()
+            if font in _fonts:
+                continue # Skip the font, if already detected.
+            if name.find ("(") != -1:
+                name = name[:name.find ("(")].rstrip ()
+            name = name.lower ()
 
-            if name[-10:] == '(TrueType)':
-                name = name[:-11]
-            name = name.lower().split()
+def _initunix ():
+    """_initunix () -> None
 
-            bold = italic = False
-            for m in mods:
-                if m in name:
-                    name.remove(m)
-            if 'bold' in name:
-                name.remove('bold')
-                bold = True
-            if 'italic' in name:
-                name.remove('italic')
-                italic = True
-            name = ''.join(name)
-
-            name=_simplename(name)
-
-            _addfont(name, bold, italic, font, fonts)
-    return fonts
-
-def _initsysfonts_darwin():
+    Initializes the unix-based font cache using the fontconfig utilities.
     """
-    read of the fonts on osx (fill me in!)
-    """
-    paths = ['/Library/Fonts',
-             '~/Library/Fonts',
-             '/Local/Library/Fonts',
-             '/Network/Library/Fonts']
-    fonts = {}
-    for p in paths:
-        if os.path.isdir(p):
-            pass
-            #os.path.walk(p, _fontwalk, fonts)
-    return fonts
-
-def _initsysfonts_unix():
-    """
-    read the fonts on unix
-    """
-    import subprocess
-    fonts = {}
-
-    # we use the fc-list from fontconfig to get a list of fonts.
+    output = ""
+    try:
+        p = subprocess.Popen \
+		("fc-list : file family style fullname fullnamelang",
+                 shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        output = p.communicate()[0]
+        retcode = p.returncode
+        if sys.version_info[0] >= 3:
+            output = str (output, "ascii")
+    except OSError, msg:
+        _fonts[None] = None
 
     try:
-        flout, flerr = subprocess.Popen('fc-list : file family style', shell=True,
-                                        stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                                        close_fds=True).communicate()
-    except Exception:
-        return fonts
+        for line in output.split ("\n"):
+            if line.strip () == "":
+               continue 
+            values = line.split (":")
+            fullnames, fulllang, name = None, None, None
+            filename, family, style = values[0:3]
+            if len (values) > 3:
+                pos = -1
+                fullnames, fulllang = values[3:]
+                langs = fulllang.split (",")
+                if "fullnamelang=en" in langs:
+                    pos = langs.index ("fullnamelang=en")
+                else:
+                    pos = langs.index ("en")
+                if pos != -1:
+                    name = fullnames.split (",")[pos].lstrip ("fullname=")
+                    
+            else:
+                name = os.path.splitext (os.path.basename (filename))[0]
+                if name.endswith (".pcf"):
+                    name = name[:-4]
+                name = _simplename (name)
+            ftype = _gettype (filename)
+            bold, italic = _getstyle (style)
+            _addfont (filename, name, ftype, family, bold, italic)
+    except Exception, msg:
+        _fonts[None] = None
 
-    try:
-        for line in flout:
-            try:
-                filename, family, style = line.split(':', 2)
-                if filename[-4:].lower() in ['.ttf', '.ttc']:
-                    bold = style.find('Bold') >= 0
-                    italic = style.find('Italic') >= 0
-                    oblique = style.find('Oblique') >= 0
-                    _addfont(_simplename(family), bold, italic or \
-                             oblique, filename, fonts)
-            except:
-                # try the next one.
-                pass
-    except:
+def _initfonts ():
+    """_initfonts () -> None
+
+    Initializes the internal font caches.
+    """
+    if _fonts:
+        return
+    if sys.platform == "win32":
+        _initwin32 ()
+    elif sys.platform == "darwin":
+        # TODO
         pass
+    else:
+        _initunix ()
 
-    return fonts
+def get_families ():
+    """get_families () -> [str, str, str, ...]
 
-def _create_aliases():
+    Gets the list of available font families.
     """
-    create alias entries
+    if not _fonts:
+        _initfonts ()
+    return list (_families.keys ())
+
+def find_font (name, bold=False, italic=False, ftype=None):
+    """find_font (name, bold=False, italic=False, ftype=None) -> str, bool, bool
+
+    Finds a font matching a certain family or font filename.
+
+    Tries to find a font that matches the passed requirements best. The
+    *name* argument denotes a specific font or font family name. If
+    multiple fonts match that name, the *bold* and *italic* arguments
+    are used to find a font that matches the requirements best. *ftype*
+    is an optional font filetype argument to request specific font file
+    types, such as bdf or ttf fonts.
     """
-    aliases = (
-        ('monospace', 'misc-fixed', 'courier', 'couriernew', 'console',
-         'fixed', 'mono', 'freemono', 'bitstreamverasansmono',
-         'verasansmono', 'monotype', 'lucidaconsole'),
-        ('sans', 'arial', 'helvetica', 'swiss', 'freesans',
-         'bitstreamverasans', 'verasans', 'verdana', 'tahoma'),
-        ('serif', 'times', 'freeserif', 'bitstreamveraserif', 'roman',
-         'timesroman', 'timesnewroman', 'dutch', 'veraserif',
-         'georgia'),
-        ('wingdings', 'wingbats'),
-    )
-    for set in aliases:
-        found = None
-        fname = None
-        for name in set:
-            if Sysfonts.has_key(name):
-                found = Sysfonts[name]
-                fname = name
-                break
-        if not found:
+    if not _fonts:
+        _initfonts ()
+
+    if ftype:
+        ftype = ftype.lower ()
+
+    candidates = []
+    #
+    # font styles consist of (fullname, filetype, bold, italic)
+    #
+    for fname in _families.get (name, []):
+        fullname, filetype, fbold, fitalic = _fonts[fname]
+        if ftype:
+            if ftype == filetype:
+                # The user requires a certain font filetype.
+                candidates.append ((fname, fbold, fitalic, 0))
+        else:
+            if bold == fbold and italic == fitalic:
+                # Exact style match
+                candidates.append ((fname, fbold, fitalic, 0))
+            elif italic and italic == fitalic:
+                # Italic matches
+                candidates.append ((fname, fbold, fitalic, 1))
+            elif bold and bold == fbold:
+                # Bold matches
+                candidates.append ((fname, fbold, fitalic, 2))
+            else:
+                # None matches
+                candidates.append ((fname, fbold, fitalic, 3))
+    if candidates:
+        candidates.sort(key=lambda x: x[3])
+        return candidates[0][0], candidates[0][1], candidates[0][2]
+
+    for items in _fonts.items():
+        fname = items[0]
+        fullname, filetype, fbold, fitalic = items[1]
+        if fullname != name and name not in fname:
             continue
-        for name in set:
-            if not Sysfonts.has_key(name):
-                Sysalias[name] = found
-
-def _initsysfonts():
-    """
-    initialize it all, called once
-    """
-    if sys.platform == 'win32':
-        fonts = _initsysfonts_win32()
-    elif sys.platform == 'darwin':
-        fonts = _initsysfonts_darwin()
-    else:
-        fonts = _initsysfonts_unix()
-    Sysfonts.update(fonts)
-    _create_aliases()
-    if not Sysfonts: #dummy so we don't try to reinit
-        Sysfonts[None] = None
-
-#the exported functions
-
-def get_fonts():
-    """
-    get_fonts () -> list
-    
-    Get a list of system font names.
-
-    Returns the list of all found system fonts. Note that the names of the
-    fonts will be all lowercase with spaces removed. This is how pygame2
-    internally stores the font names for matching.
-    """
-    if not Sysfonts:
-        _initsysfonts()
-    return Sysfonts.keys()
-
-def find_font(name, bold=False, italic=False):
-    """
-    find_font (name, bold=False, italic=False) -> str, bool, bool
-    
-    Find the filename for the named system font.
-
-    This performs a font search and it returns the path to the TTF file.
-    The font name can be a comma separated list of font names to try.
-    If no match is found, None is returned as fontname.
-    """
-    if not Sysfonts:
-        _initsysfonts()
-
-    fontname = None
-    allnames = name
-    for name in allnames.split(','):
-        name = _simplename(name)
-        styles = Sysfonts.get(name)
-        if not styles:
-            styles = Sysalias.get(name)
-        if styles:
-            while not fontname:
-                fontname = styles.get((bold, italic))
-                if italic:
-                    italic = False
-                elif bold:
-                    bold = False
-                elif not fontname:
-                    fontname = styles.values()[0]
-        if fontname:
-            break
-    return fontname, italic, bold
-
-__all__ = [ "find_font", "get_fonts" ]
+        if ftype:
+            if ftype == filetype:
+                # The user requires a certain font filetype.
+                candidates.append ((fname, fbold, fitalic, 0))
+        else:
+            if bold == fbold and italic == fitalic:
+                # Exact style match
+                candidates.append ((fname, fbold, fitalic, 0))
+            elif italic and italic == fitalic:
+                # Italic matches
+                candidates.append ((fname, fbold, fitalic, 1))
+            elif bold and bold == fbold:
+                # Bold matches
+                candidates.append ((fname, fbold, fitalic, 2))
+            else:
+                # None matches
+                candidates.append ((fname, fbold, fitalic, 3))
+    if candidates:
+        candidates.sort(key=lambda x: x[3])
+        return candidates[0][0], candidates[0][1], candidates[0][2]
+    return None
         self.depends = list (depends or [])
         self.optional_dep = list(optional_dep or [])
 
+    def __repr__ (self):
+        return "<Module '%s'>" % self.name
+
 modules = [
 
     Module ("base",
 
     Module ("freetype.constants",
         sources = [ "src/freetype/ft_constants.c" ],
-        depends = ['freetype']),
+        depends = ['freetype'],
+        optional_dep = ['SDL']),
 
     ]
 

src/freetype/ft_font.c

 {
     /* Always try to unload the font even if we cannot grab
      * a freetype instance. */
-    PGFT_UnloadFont(_get_freetype(), self);
+    PGFT_UnloadFont(FREETYPE_STATE->freetype, self);
 
     ((PyObject*)self)->ob_type->tp_free((PyObject *)self);
 }

src/freetype/ft_mod.c

 #include "pgfreetype.h"
 #include "freetypebase_doc.h"
 
-static FreeTypeInstance *__freetype = NULL;
-
-static int _init(void);
-static void _quit(void);
+static int _ft_traverse (PyObject *mod, visitproc visit, void *arg);
+static int _ft_clear (PyObject *mod);
 
 static PyObject *_ft_quit(PyObject *self);
 static PyObject *_ft_init(PyObject *self);
     { NULL, NULL, 0, NULL },
 };
 
-/*
- * Get a pointer to the active FT library
- *
- * TODO: Someday this should automatically handle returning
- * libraries based on the active thread to prevent multi-
- * threading issues.
- */
-FreeTypeInstance *
-_get_freetype(void)
-{
-    return __freetype;
-}
-
-/*
- * Deinitialize the FreeType library.
- */
-static void
-_quit(void)
-{
-    if (__freetype)
-    {
-        PGFT_Quit(__freetype);
-        __freetype = NULL;
-    }
-}
-
-/*
- * Initialize the FreeType library.
- */
-static int
-_init(void)
-{
-    FT_Error error;
-
-    if (__freetype)
-        return 0;
-
-    error = PGFT_Init(&__freetype);
-
-    return (error);
-}
-
 /***************************************************************
  *
  * Bindings for initialization/cleanup functions
 static PyObject *
 _ft_quit(PyObject *self)
 {
-    _quit();
+    if (FREETYPE_MOD_STATE (self)->freetype)
+    {
+        PGFT_Quit(FREETYPE_MOD_STATE (self)->freetype);
+        FREETYPE_MOD_STATE (self)->freetype = NULL;
+    }
     Py_RETURN_NONE;
 }
 
 static PyObject *
 _ft_init(PyObject *self)
 {
-    if (_init() != 0)
+    FT_Error error;
+
+    if (FREETYPE_MOD_STATE (self)->freetype)
+        Py_RETURN_NONE;
+
+    error = PGFT_Init(&(FREETYPE_MOD_STATE (self)->freetype));
+    if (error != 0)
     {
         /* TODO: More accurate error message */
         PyErr_SetString(PyExc_PyGameError, 
 static PyObject *
 _ft_was_init(PyObject *self)
 {
-    return PyBool_FromLong((__freetype != 0));
+    return PyBool_FromLong((FREETYPE_MOD_STATE (self)->freetype != NULL));
 }
 
+
+static int
+_ft_traverse (PyObject *mod, visitproc visit, void *arg)
+{
+    return 0;
+}
+
+static int
+_ft_clear (PyObject *mod)
+{
+    if (FREETYPE_MOD_STATE(mod)->freetype)
+    {
+        PGFT_Quit(FREETYPE_MOD_STATE(mod)->freetype);
+        FREETYPE_MOD_STATE(mod)->freetype = NULL;
+    }
+    return 0;
+}
+
+#ifdef IS_PYTHON_3
+struct PyModuleDef _freetypemodule = {
+    PyModuleDef_HEAD_INIT,
+    "base",
+    DOC_BASE, 
+    sizeof (_FreeTypeState),
+    _ft_methods,
+    NULL,
+    _ft_traverse,
+    _ft_clear,
+    NULL
+};
+#else
+_FreeTypeState _modstate;
+#endif
+
 PyMODINIT_FUNC
 #ifdef IS_PYTHON_3
     PyInit_base(void)
     static void *c_api[PYGAME_FREETYPE_SLOTS];
 
 #ifdef IS_PYTHON_3
-
-    /* Python 3 module initialization needs a PyModuleDef struct */
-    static struct PyModuleDef _module = {
-        PyModuleDef_HEAD_INIT,
-        "base",
-        DOC_BASE,
-        -1,
-        _ft_methods,
-        NULL, NULL, NULL, NULL
-    };
-
-    mod = PyModule_Create(&_module);
-
+    mod = PyModule_Create(&_freetypemodule);
 #else
-
     /* Standard init for 2.x module */
     mod = Py_InitModule3 ("base", _ft_methods, DOC_BASE);
-
 #endif
 
     if (!mod)
         goto fail;
-
+    FREETYPE_MOD_STATE(mod)->freetype = NULL;
 
     /* Import Pygame2 Base API to access PyFont_Type */
     if (import_pygame2_base() < 0)
     if (c_api_obj)
         PyModule_AddObject(mod, PYGAME_FREETYPE_ENTRY, c_api_obj);    
 
+#ifdef HAVE_PYGAME_SDL_VIDEO
+    if (import_pygame2_sdl_base () < 0)
+        goto fail;
+    if (import_pygame2_sdl_video () < 0)
+        goto fail;
+#endif /* HAVE_PYGAME_SDL_VIDEO */
+
     MODINIT_RETURN(mod);
 
 fail:

src/freetype/ft_wrap.c

 
 #define PYGAME_FREETYPE_INTERNAL
 
-#ifdef HAVE_PYGAME_SDL_VIDEO
-#   include <SDL.h>
-#   include "pgsdl.h"
-#endif
-
 #include "ft_mod.h"
 #include "ft_wrap.h"
 #include "pgfreetype.h"
 FT_Face _PGFT_GetFaceSized(FreeTypeInstance *, PyFreeTypeFont *, int);
 void    _PGFT_BuildScaler(PyFreeTypeFont *, FTC_Scaler, int);
 int     _PGFT_LoadGlyph(FreeTypeInstance *, PyFreeTypeFont *, int,
-                        FTC_Scaler, int, FT_Glyph *, FT_UInt32 *);
+    FTC_Scaler, int, FT_Glyph *, FT_UInt32 *);
 void    _PGFT_GetMetrics_INTERNAL(FT_Glyph, FT_UInt, int *, int *, int *, int *, int *);
 int     _PGFT_Render_INTERNAL(FreeTypeInstance *ft, PyFreeTypeFont *font, 
-            const FT_UInt16 *text, int font_size, 
-            FT_Byte *_buffer, int width, int height, int pitch);
+    const FT_UInt16 *text, int font_size, 
+    FT_Byte *_buffer, int width, int height, int pitch);
 
 
 static FT_Error
     FT_Error error;
     
     Py_BEGIN_ALLOW_THREADS;
-        error = FT_Open_Face(library, &id->open_args, id->face_index, aface);
+    error = FT_Open_Face(library, &id->open_args, id->face_index, aface);
     Py_END_ALLOW_THREADS;
 
     return error;
         strcpy(ft->_error_msg, error_msg);
 }
 
-
-#ifdef HAVE_SDL_VIDEO
-PyObject *
-PGFT_BuildSDLSurface(FT_Byte *buffer, int width, int height)
-{
-    SDL_Surface *surf;
-
-    surf = 
-
-    return PySDLSurface_NewFromSDLSurface(surf);
-}
-#endif
-
-
 FT_UInt16 *
 PGFT_BuildUnicodeString(PyObject *obj, int *must_free)
 {
     } else
 #endif
 
-    /*
-     * If we pass a Bytes array, we assume it's standard
-     * text encoded in Latin1 (SDL_TTF does the same).
-     * We need to expand each character into 2 bytes because
-     * FreeType expects UTF16 encodings.
-     *
-     * TODO: What happens if the user passes a byte array
-     * representing e.g. a UTF8 string? He would be mostly
-     * stupid, yes, but we should probably handle it.
-     */
-    if (Bytes_Check(obj))
-    {
-        const char *latin1_buffer;
-        size_t i, len;
+        /*
+         * If we pass a Bytes array, we assume it's standard
+         * text encoded in Latin1 (SDL_TTF does the same).
+         * We need to expand each character into 2 bytes because
+         * FreeType expects UTF16 encodings.
+         *
+         * TODO: What happens if the user passes a byte array
+         * representing e.g. a UTF8 string? He would be mostly
+         * stupid, yes, but we should probably handle it.
+         */
+        if (Bytes_Check(obj))
+        {
+            const char *latin1_buffer;
+            size_t i, len;
 
-        latin1_buffer = (const char *)Bytes_AsString(obj);
-        len = strlen(latin1_buffer);
+            latin1_buffer = (const char *)Bytes_AsString(obj);
+            len = strlen(latin1_buffer);
 
-        utf16_buffer = malloc((len + 1) * sizeof(FT_UInt16));
-        if (!utf16_buffer)
-            return NULL;
+            utf16_buffer = malloc((len + 1) * sizeof(FT_UInt16));
+            if (!utf16_buffer)
+                return NULL;
 
-        for (i = 0; i < len; ++i)
-            utf16_buffer[i] = (FT_UInt16)latin1_buffer[i];
+            for (i = 0; i < len; ++i)
+                utf16_buffer[i] = (FT_UInt16)latin1_buffer[i];
 
-        utf16_buffer[i] = 0;
-        *must_free = 1;
-    }
+            utf16_buffer[i] = 0;
+            *must_free = 1;
+        }
 
     return utf16_buffer;
 }
 
 FT_Face
 _PGFT_GetFace(FreeTypeInstance *ft,
-        PyFreeTypeFont *font)
+    PyFreeTypeFont *font)
 {
     FT_Error error;
     FT_Face face;
 
     error = FTC_Manager_LookupFace(ft->cache_manager,
-            (FTC_FaceID)(&font->id),
-            &face);
+        (FTC_FaceID)(&font->id),
+        &face);
 
     if (error)
     {
 
 FT_Face
 _PGFT_GetFaceSized(FreeTypeInstance *ft,
-        PyFreeTypeFont *font,
-        int face_size)
+    PyFreeTypeFont *font,
+    int face_size)
 {
     FT_Error error;
     FTC_ScalerRec scale;
      */
 
     error = FTC_Manager_LookupSize(ft->cache_manager, 
-            &scale, &_fts);
+        &scale, &_fts);
 
     if (error)
     {
 
 int
 _PGFT_LoadGlyph(FreeTypeInstance *ft, 
-        PyFreeTypeFont *font,
-        int do_render,
-        FTC_Scaler scale, 
-        int character, 
-        FT_Glyph *glyph, 
-        FT_UInt32 *_index)
+    PyFreeTypeFont *font,
+    int do_render,
+    FTC_Scaler scale, 
+    int character, 
+    FT_Glyph *glyph, 
+    FT_UInt32 *_index)
 {
     FT_Error error = 0;
     FT_UInt32 char_index;
         (FT_ULong)FT_LOAD_DEFAULT;
 
     char_index = FTC_CMapCache_Lookup(
-            ft->cache_charmap, 
+        ft->cache_charmap, 
             (FTC_FaceID)(&font->id),
             -1, (FT_UInt32)character);
 
     if (glyph)
     {
         error = FTC_ImageCache_LookupScaler(
-                ft->cache_img,
+            ft->cache_img,
                 scale,
                 render_mode,
                 char_index,
 }
 
 void _PGFT_GetMetrics_INTERNAL(FT_Glyph glyph, FT_UInt bbmode,
-        int *minx, int *maxx, int *miny, int *maxy, int *advance)
+    int *minx, int *maxx, int *miny, int *maxy, int *advance)
 {
     FT_BBox box;
     FT_Glyph_Get_CBox(glyph, bbmode, &box);
 
 
 int PGFT_GetMetrics(FreeTypeInstance *ft, PyFreeTypeFont *font,
-        int character, int font_size, int bbmode,
-        void *minx, void *maxx, void *miny, void *maxy, void *advance)
+    int character, int font_size, int bbmode,
+    void *minx, void *maxx, void *miny, void *maxy, void *advance)
 {
     FT_Error error;
     FTC_ScalerRec scale;
 
 int
 PGFT_GetTextSize(FreeTypeInstance *ft, PyFreeTypeFont *font,
-        const FT_UInt16 *text, int font_size, int *w, int *h)
+    const FT_UInt16 *text, int font_size, int *w, int *h)
 {
     const FT_UInt16 *ch;
     int swapped, use_kerning;
             continue;
 
         _PGFT_GetMetrics_INTERNAL(glyph, FT_GLYPH_BBOX_PIXELS,
-                &gl_minx, &gl_maxx, &gl_miny, &gl_maxy, &gl_advance);
+            &gl_minx, &gl_maxx, &gl_miny, &gl_maxy, &gl_advance);
 
         if (use_kerning && prev_index)
         {
-			FT_Vector delta;
-			FT_Get_Kerning(face, prev_index, cur_index, ft_kerning_default, &delta); 
-			x += delta.x >> 6;
-		}
+            FT_Vector delta;
+            FT_Get_Kerning(face, prev_index, cur_index, ft_kerning_default, &delta); 
+            x += delta.x >> 6;
+        }
 
         z = x + gl_minx;
-		if (minx > z)
-			minx = z;
+        if (minx > z)
+            minx = z;
 		
         /* TODO: Handle bold fonts */
 
         z = x + MAX(gl_maxx, gl_advance);
-		if (maxx < z)
-			maxx = z;
+        if (maxx < z)
+            maxx = z;
 
 #ifndef METRICS_RETURN_AVERAGED_HEIGHT
         miny = MIN(gl_miny, miny);
         maxy = MAX(gl_maxy, maxy);
 #endif
 
-		x += gl_advance;
+        x += gl_advance;
         prev_index = cur_index;
     }
 
 
 #ifdef HAVE_PYGAME_SDL_VIDEO
 PyObject *PGFT_Render_NewSurface(FreeTypeInstance *ft, PyFreeTypeFont *font,
-        const FT_UInt16 *text, int font_size, int *_width, int *_height)
+    const FT_UInt16 *text, int font_size, int *_width, int *_height)
 {
-    int width, height;
+    int width, height, locked = 0;
     SDL_Surface *surface = NULL;
 
     if (PGFT_GetTextSize(ft, font, text, font_size, &width, &height) != 0 ||
     }
 
     surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
-				    width, height, 8, 0, 0, 0, 0);
+        width, height, 8, 0, 0, 0, 0);
 
     if (!surface)
+    {
+        _PGFT_SetError(ft, SDL_GetError (), 0);
         return NULL;
+    }
+
+    if (SDL_MUSTLOCK (surface))
+    {
+        if (SDL_LockSurface (surface) == -1)
+        {
+            _PGFT_SetError(ft, SDL_GetError (), 0);
+            SDL_FreeSurface (surface);
+            return NULL;
+        }
+        locked = 1;
+    }
 
     if (_PGFT_Render_INTERNAL(ft, font, text, font_size, 
-                surface->pixels,
-                surface->w, surface->h, surface->pitch) != 0)
+            surface->pixels,
+            surface->w, surface->h, surface->pitch) != 0)
     {
         _PGFT_SetError(ft, "Failed to render text", 0);
         SDL_FreeSurface(surface);
     *_width = width;
     *_height = height;
 
+    if (locked)
+        SDL_UnlockSurface (surface);
+
     return PySDLSurface_NewFromSDLSurface(surface);
 }
 #endif
 
 PyObject *PGFT_Render_PixelArray(FreeTypeInstance *ft, PyFreeTypeFont *font,
-        const FT_UInt16 *text, int font_size, int *_width, int *_height)
+    const FT_UInt16 *text, int font_size, int *_width, int *_height)
 {
     int width, height;
     FT_Byte *buffer = NULL;
         goto cleanup;
     }
 
-	buffer = calloc((size_t)(width * height), sizeof(FT_Byte));
-	if (!buffer)
-		goto cleanup;
+    buffer = calloc((size_t)(width * height), sizeof(FT_Byte));
+    if (!buffer)
+    {
+        _PGFT_SetError(ft, "Could not allocate memory", 0);
+        goto cleanup;
+    }
 
     if (_PGFT_Render_INTERNAL(ft, font, text, 
-                font_size, buffer, width, height, width) != 0)
+            font_size, buffer, width, height, width) != 0)
     {
         _PGFT_SetError(ft, "Failed to render text", 0);
         goto cleanup;
     array = Bytes_FromStringAndSize(buffer, width * height);
 
 cleanup:
-    free(buffer);
+    if (buffer)
+        free(buffer);
 
     return array;
 }
 
 int _PGFT_Render_INTERNAL(FreeTypeInstance *ft, PyFreeTypeFont *font, 
-        const FT_UInt16 *text, int font_size, 
-        FT_Byte *_buffer, int width, int height, int pitch)
+    const FT_UInt16 *text, int font_size, 
+    FT_Byte *_buffer, int width, int height, int pitch)
 {
     const FT_UInt16 *ch;
 
     FT_UInt32 prev_index, cur_index;
 
     int swapped, use_kerning;
-	int pen_x, pen_y;
+    int pen_x, pen_y;
     int x_advance;
 
     FT_Byte *_buffer_cap;
         return -1;
     }
 
-	_buffer_cap = _buffer + (width * height);
-	use_kerning = FT_HAS_KERNING(face);
+    _buffer_cap = _buffer + (width * height);
+    use_kerning = FT_HAS_KERNING(face);
     prev_index = 0;
 
     /* FIXME: Some way to set the system's default ? */
 
         if (use_kerning && prev_index)
         {
-			FT_Vector delta;
-			FT_Get_Kerning(face, prev_index, cur_index, ft_kerning_default, &delta); 
-			pen_x += delta.x >> 6;
-		}
+            FT_Vector delta;
+            FT_Get_Kerning(face, prev_index, cur_index, ft_kerning_default, &delta); 
+            pen_x += delta.x >> 6;
+        }
 
         x_advance = (glyph->advance.x + 0x8000) >> 16;
 
                 if (dst < _buffer || dst + bitmap->width > _buffer_cap)
                 {
                     fprintf(stderr, 
-                            "HEAP CORRUPTION rendering '%c': "
-                            "text size(%d, %d) | left %d | top %d | pen_x %d | pen_y %d |\n",
-                            c, width, height, left, top, pen_x, pen_y);
+                        "HEAP CORRUPTION rendering '%c': "
+                        "text size(%d, %d) | left %d | top %d | pen_x %d | pen_y %d |\n",
+                        c, width, height, left, top, pen_x, pen_y);
                     continue;
                 }
 
 
 
         pen_x += x_advance + 1;
-		prev_index = cur_index;
+        prev_index = cur_index;
     }
 
     return 0;
 
 int
 PGFT_TryLoadFont_Filename(FreeTypeInstance *ft, 
-        PyFreeTypeFont *font, 
-        const char *filename, 
-        int face_index)
+    PyFreeTypeFont *font, 
+    const char *filename, 
+    int face_index)
 {
     char *filename_alloc;
     size_t file_len;
 
     file_len = strlen(filename);
     filename_alloc = malloc(file_len + 1);
+    if (!filename_alloc)
+    {
+        _PGFT_SetError(ft, "Could not allocate memory", 0);
+        return -1;
+    }
 
     strcpy(filename_alloc, filename);
     filename_alloc[file_len] = 0;

src/freetype/ft_wrap.h

 #define PYGAME_FREETYPE_INTERNAL
 #include "pgfreetype.h"
 
+#ifdef HAVE_PYGAME_SDL_VIDEO
+#   include "pgsdl.h"
+#endif
+
 typedef struct
 {
     FT_Library library;
     char *_error_msg;
 } FreeTypeInstance;
 
-FreeTypeInstance *_get_freetype(void);
+
+typedef struct {
+    FreeTypeInstance *freetype;
+} _FreeTypeState;
+
+#ifdef IS_PYTHON_3
+extern struct PyModuleDef _freetypemodule;
+#define FREETYPE_MOD_STATE(mod) ((_FreeTypeState*)PyModule_GetState(mod))
+#define FREETYPE_STATE FREETYPE_MOD_STATE(PyState_FindModule(&_freetypemodule))
+#else
+extern _FreeTypeState _modstate;
+#define FREETYPE_MOD_STATE(mod) (&_modstate)
+#define FREETYPE_STATE FREETYPE_MOD_STATE(NULL)
+#endif
 
 #define ASSERT_GRAB_FREETYPE(ft_ptr, rvalue)                    \
-    ft_ptr = _get_freetype();                                   \
+    ft_ptr = FREETYPE_STATE->freetype;                          \
     if (ft_ptr == NULL)                                         \
     {                                                           \
         PyErr_SetString(PyExc_PyGameError,                      \