Revision: 6342
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6342&view=rev
Author:   mdboom
Date:     2008-10-28 18:10:51 +0000 (Tue, 28 Oct 2008)

Log Message:
-----------
Add nearest neighbor search for fonts.

Modified Paths:
--------------
    trunk/matplotlib/examples/pylab_examples/fonts_demo.py
    trunk/matplotlib/examples/pylab_examples/fonts_demo_kw.py
    trunk/matplotlib/lib/matplotlib/font_manager.py
    trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py
    trunk/matplotlib/lib/matplotlib/text.py

Modified: trunk/matplotlib/examples/pylab_examples/fonts_demo.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/fonts_demo.py      2008-10-28 
12:51:39 UTC (rev 6341)
+++ trunk/matplotlib/examples/pylab_examples/fonts_demo.py      2008-10-28 
18:10:51 UTC (rev 6342)
@@ -11,7 +11,7 @@
 subplot(111, axisbg='w')
 
 font0 = FontProperties()
-alignment = {'horizontalalignment':'center', 'verticalalignment':'center'}
+alignment = {'horizontalalignment':'center', 'verticalalignment':'baseline'}
 ###  Show family options
 
 family = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace']
@@ -53,7 +53,7 @@
 t = text(0.0, 0.9, 'variant', fontproperties=font1,
          **alignment)
 
-for k in range(1):
+for k in range(2):
     font = font0.copy()
     font.set_family('serif')
     font.set_variant(variant[k])

Modified: trunk/matplotlib/examples/pylab_examples/fonts_demo_kw.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/fonts_demo_kw.py   2008-10-28 
12:51:39 UTC (rev 6341)
+++ trunk/matplotlib/examples/pylab_examples/fonts_demo_kw.py   2008-10-28 
18:10:51 UTC (rev 6342)
@@ -8,7 +8,7 @@
 from pylab import *
 
 subplot(111, axisbg='w')
-alignment = {'horizontalalignment':'center', 'verticalalignment':'center'}
+alignment = {'horizontalalignment':'center', 'verticalalignment':'baseline'}
 ###  Show family options
 
 family = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace']
@@ -40,7 +40,7 @@
 
 t = text(0.0, 0.9, 'variant', **alignment)
 
-for k in range(1):
+for k in range(2):
     t = text( 0.0, yp[k], variant[k], family='serif', variant=variant[k],
               **alignment)
 

Modified: trunk/matplotlib/lib/matplotlib/font_manager.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/font_manager.py     2008-10-28 12:51:39 UTC 
(rev 6341)
+++ trunk/matplotlib/lib/matplotlib/font_manager.py     2008-10-28 18:10:51 UTC 
(rev 6342)
@@ -4,9 +4,9 @@
 This module provides a single :class:`FontManager` instance that can
 be shared across backends and platforms.  The :func:`findfont`
 function returns the best TrueType (TTF) font file in the local or
-system font path that matches the specified FontProperties.  The
-FontManager also handles Adobe Font Metrics (AFM) font files for use
-by the PostScript backend.
+system font path that matches the specified :class:`FontProperties`
+instance.  The :class:`FontManager` also handles Adobe Font Metrics
+(AFM) font files for use by the PostScript backend.
 
 The design is based on the `W3C Cascading Style Sheet, Level 1 (CSS1)
 font specification <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_.
@@ -64,15 +64,53 @@
 
 verbose = matplotlib.verbose
 
-font_scalings = {'xx-small': 0.579, 'x-small': 0.694, 'small': 0.833,
-                 'medium': 1.0, 'large': 1.200, 'x-large': 1.440,
-                 'xx-large': 1.728, 'larger': 1.2, 'smaller': 0.833}
+font_scalings = {
+    'xx-small' : 0.579,
+    'x-small'  : 0.694,
+    'small'    : 0.833,
+    'medium'   : 1.0,
+    'large'    : 1.200,
+    'x-large'  : 1.440,
+    'xx-large' : 1.728,
+    'larger'   : 1.2,
+    'smaller'  : 0.833,
+    None       : 1.0}
 
-weight_dict = {'light': 200, 'normal': 400, 'regular': 400, 'book': 400,
-               'medium': 500, 'roman': 500, 'semibold': 600, 'demibold': 600,
-               'demi': 600, 'bold': 700, 'heavy': 800, 'extra bold': 800,
-               'black': 900}
+stretch_dict = {
+    'ultra-condensed' : 100,
+    'extra-condensed' : 200,
+    'condensed'       : 300,
+    'semi-condensed'  : 400,
+    'normal'          : 500,
+    'semi-expanded'   : 600,
+    'expanded'        : 700,
+    'extra-expanded'  : 800,
+    'ultra-expanded'  : 900}
 
+weight_dict = {
+    'ultralight' : 100,
+    'light'      : 200,
+    'normal'     : 400,
+    'regular'    : 400,
+    'book'       : 400,
+    'medium'     : 500,
+    'roman'      : 500,
+    'semibold'   : 600,
+    'demibold'   : 600,
+    'demi'       : 600,
+    'bold'       : 700,
+    'heavy'      : 800,
+    'extra bold' : 800,
+    'black'      : 900}
+
+font_family_aliases = set([
+        'serif',
+        'sans-serif',
+        'cursive',
+        'fantasy',
+        'monospace',
+        'sans'])
+
 #  OS Font paths
 MSFolders = \
     r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
@@ -117,12 +155,14 @@
             'afm': ('afm',)}[fontext]
 
 def win32FontDirectory():
-    """Return the user-specified font directory for Win32.  This is
+    """
+    Return the user-specified font directory for Win32.  This is
     looked up from the registry key::
 
       
\\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell 
Folders\Fonts
 
-    If the key is not found, $WINDIR/Fonts will be returned."""
+    If the key is not found, $WINDIR/Fonts will be returned.
+    """
     try:
         import _winreg
     except ImportError:
@@ -331,19 +371,21 @@
     return weight
 
 
-class FontKey(object):
+class FontEntry(object):
     """
     A class for storing Font properties.  It is used when populating
     the font lookup dictionary.
     """
     def __init__(self,
+                 fname  ='',
                  name   ='',
                  style  ='normal',
                  variant='normal',
                  weight ='normal',
                  stretch='normal',
-                 size   ='medium'
+                 size   ='medium',
                  ):
+        self.fname   = fname
         self.name    = name
         self.style   = style
         self.variant = variant
@@ -355,12 +397,12 @@
             self.size = size
 
 
-def ttfFontProperty(font):
+def ttfFontProperty(fontpath, font):
     """
     A function for populating the :class:`FontKey` by extracting
     information from the TrueType font file.
 
-    *font* is an :class:`FT2Font` instance.
+    *font* is a :class:`FT2Font` instance.
     """
     name = font.family_name
 
@@ -447,10 +489,10 @@
     #  !!!!  Incomplete
     size_adjust = None
 
-    return FontKey(name, style, variant, weight, stretch, size)
+    return FontEntry(fontpath, name, style, variant, weight, stretch, size)
 
 
-def afmFontProperty(font):
+def afmFontProperty(fontpath, font):
     """
     A function for populating a :class:`FontKey` instance by
     extracting information from the AFM font file.
@@ -507,37 +549,17 @@
     # !!!!  Incomplete
     size_adjust = None
 
-    return FontKey(name, style, variant, weight, stretch, size)
+    return FontEntry(fontpath, name, style, variant, weight, stretch, size)
 
 
-def add_filename(fontdict, prop, fname):
+def createFontList(fontfiles, fontext='ttf'):
     """
-    A function to add a font file name to the font dictionary using
-    the :class:`FontKey` properties.  If a font property has no
-    dictionary, then create it.
+    A function to create a font lookup list.  The default is to create
+    a list of TrueType fonts.  An AFM font list can optionally be
+    created.
     """
-    try:
-        size = str(float(prop.size))
-    except ValueError:
-        size = prop.size
 
-    d = fontdict.                    \
-        setdefault(prop.name,    {}).\
-        setdefault(prop.style,   {}).\
-        setdefault(prop.variant, {}).\
-        setdefault(prop.weight,  {}).\
-        setdefault(prop.stretch, {})
-    d[size] = fname
-
-
-def createFontDict(fontfiles, fontext='ttf'):
-    """
-    A function to create a font lookup dictionary.  The default is to
-    create a dictionary for TrueType fonts.  An AFM font dictionary
-    can optionally be created.
-    """
-
-    fontdict = {}
+    fontlist = []
     #  Add fonts from list of known font files.
     seen = {}
     for fpath in fontfiles:
@@ -559,7 +581,7 @@
             except RuntimeError:
                 verbose.report("Could not parse font file %s"%fpath)
                 continue
-            prop = afmFontProperty(font)
+            prop = afmFontProperty(fpath, font)
         else:
             try:
                 font = ft2font.FT2Font(str(fpath))
@@ -570,59 +592,12 @@
                 verbose.report("Cannot handle unicode filenames")
                 #print >> sys.stderr, 'Bad file is', fpath
                 continue
-            try: prop = ttfFontProperty(font)
+            try: prop = ttfFontProperty(fpath, font)
             except: continue
 
-        add_filename(fontdict, prop, fpath)
-    return fontdict
+        fontlist.append(prop)
+    return fontlist
 
-def setWeights(font):
-    """
-    A function to populate missing values in a font weight
-    dictionary.  This proceedure is necessary since the font finding
-    algorithm always matches on the weight property.
-    """
-
-    # !!!!  Not completely correct
-    temp = font.copy()
-    if len(temp) == 1:
-        wgt = temp.keys()[0]
-        for j in range(100, 1000, 100):
-            font[j] = temp[wgt]
-
-    if 400 in temp:
-        for j in range(100, 1000, 100):
-            font[j] = temp[400]
-    if 500 in temp:
-        if 400 in temp:
-            for j in range(500, 1000, 100):
-                font[j] = temp[500]
-        else:
-            for j in range(100, 1000, 100):
-                font[j] = temp[500]
-
-    if 300 in temp:
-        for j in [100, 200, 300]:
-            font[j] = temp[300]
-    if 200 in temp:
-        if 300 in temp:
-            for j in [100, 200]:
-                font[j] = temp[200]
-        else:
-            for j in [100, 200, 300]:
-                font[j] = temp[200]
-
-    if 800 in temp:
-        for j in [600, 700, 800, 900]:
-            font[j] = temp[800]
-    if 700 in temp:
-        if 800 in temp:
-            for j in [600, 700]:
-                font[j] = temp[700]
-        else:
-            for j in [600, 700, 800, 900]:
-                font[j] = temp[700]
-
 class FontProperties(object):
     """
     A class for storing and manipulating font properties.
@@ -633,31 +608,33 @@
     specification.  The six properties are:
 
       - family: A list of font names in decreasing order of priority.
-        The last item is the default font name and is given the name
-        of the font family, either 'serif', 'sans-serif', 'cursive',
-        'fantasy', or 'monospace'.
+        The items may include a generic font family name, either
+        'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace'.
+        In that case, the actual font to be used will be looked up
+        from the associated rcParam in :file:`matplotlibrc`.
 
       - style: Either 'normal', 'italic' or 'oblique'.
 
       - variant: Either 'normal' or 'small-caps'.
 
-      - stretch: Either an absolute value of 'ultra-condensed',
-        'extra- condensed', 'condensed', 'semi-condensed', 'normal',
-        'semi-expanded', 'expanded', 'extra-expanded' or
-        'ultra-expanded'; or a relative value of narrower or wider.
-        This property is currently not implemented and is set to
-        normal.
+      - stretch: A numeric value in the range 0-1000 or one of
+        'ultra-condensed', 'extra-condensed', 'condensed',
+        'semi-condensed', 'normal', 'semi-expanded', 'expanded',
+        'extra-expanded' or 'ultra-expanded'
 
-      - weight: A numeric value in the range 100, 200, 300, ..., 900.
+      - weight: A numeric value in the range 0-1000 or one of
+        'ultralight', 'light', 'normal', 'regular', 'book', 'medium',
+        'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy',
+        'extra bold', 'black'
 
-      - size: Either an absolute value of 'xx-small', 'x-small', 'small',
-        'medium', 'large', 'x-large', 'xx-large'; or a relative value
-        of smaller or larger; or an absolute font size, e.g. 12;
-        or 'scalable'.
+      - size: Either an relative value of 'xx-small', 'x-small',
+        'small', 'medium', 'large', 'x-large', 'xx-large' or an
+        absolute font size, e.g. 12
 
-    The default font property for TrueType fonts is::
+    The default font property for TrueType fonts (as specified in the
+    default :file:`matplotlibrc` file) is::
 
-      sans-serif, normal, normal, normal, 400, scalable.
+      sans-serif, normal, normal, normal, normal, scalable.
 
     Alternatively, a font may be specified using an absolute path to a
     .ttf file, by using the *fname* kwarg.
@@ -672,9 +649,8 @@
     <http://www.fontconfig.org/>`_ pattern, if it is the only argument
     provided.  See the documentation on `fontconfig patterns
     <http://www.fontconfig.org/fontconfig-user.html>`_.  This support
-    does not require fontconfig to be installed or support for it to
-    be enabled.  We are merely borrowing its pattern syntax for use
-    here.
+    does not require fontconfig to be installed.  We are merely
+    borrowing its pattern syntax for use here.
 
     Note that matplotlib's internal font manager and fontconfig use a
     different algorithm to lookup fonts, so the results of the same pattern
@@ -760,6 +736,7 @@
         if self._slant is None:
             return rcParams['font.style']
         return self._slant
+    get_slant = get_style
 
     def get_variant(self):
         """
@@ -772,7 +749,10 @@
 
     def get_weight(self):
         """
-        Return the font weight.
+        Set the font weight.  Options are: A numeric value in the
+        range 0-1000 or one of 'light', 'normal', 'regular', 'book',
+        'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold',
+        'heavy', 'extra bold', 'black'
         """
         if self._weight is None:
             return rcParams['font.weight']
@@ -780,8 +760,9 @@
 
     def get_stretch(self):
         """
-        Return the font stretch or width.  Options are: 'normal',
-        'narrow', 'condensed', or 'wide'.
+        Return the font stretch or width.  Options are: 'ultra-condensed',
+        'extra-condensed', 'condensed', 'semi-condensed', 'normal',
+        'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'.
         """
         if self._stretch is None:
             return rcParams['font.stretch']
@@ -793,8 +774,17 @@
         """
         if self._size is None:
             return rcParams['font.size']
-        return float(self._size)
+        return self._size
 
+    def get_size_in_points(self):
+        if self._size is not None:
+            try:
+                return float(self._size)
+            except ValueError:
+                pass
+        default_size = fontManager.get_default_size()
+        return default_size * font_scalings.get(self._size)
+
     def get_file(self):
         """
         Return the filename of the associated font.
@@ -849,46 +839,51 @@
 
     def set_weight(self, weight):
         """
-        Set the font weight.
+        Set the font weight.  May be either a numeric value in the
+        range 0-1000 or one of 'ultralight', 'light', 'normal',
+        'regular', 'book', 'medium', 'roman', 'semibold', 'demibold',
+        'demi', 'bold', 'heavy', 'extra bold', 'black'
         """
-        if (weight is not None and
-            weight not in weight_dict and
-            weight not in weight_dict.keys()):
-            raise ValueError("weight is invalid")
+        if weight is not None:
+            try:
+                weight = int(weight)
+                if weight < 0 or weight > 1000:
+                    raise ValueError()
+            except ValueError:
+                if weight not in weight_dict:
+                    raise ValueError("weight is invalid")
         self._weight = weight
 
     def set_stretch(self, stretch):
         """
-        Set the font stretch or width.  Options are: 'normal', 'narrow',
-        'condensed', or 'wide'.
+        Set the font stretch or width.  Options are: 'ultra-condensed',
+        'extra-condensed', 'condensed', 'semi-condensed', 'normal',
+        'semi-expanded', 'expanded', 'extra-expanded' or
+        'ultra-expanded', or a numeric value in the range 0-1000.
         """
-        if stretch not in ('normal', 'narrow', 'condensed', 'wide', None):
-            raise ValueError("stretch is invalid")
+        if stretch is not None:
+            try:
+                stretch = int(stretch)
+                if stretch < 0 or stretch > 1000:
+                    raise ValueError()
+            except ValueError:
+                if stretch not in stretch_dict:
+                    raise ValueError("stretch is invalid")
         self._stretch = stretch
 
     def set_size(self, size):
         """
-        Set the font size.  Either an absolute value of 'xx-small',
-        'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large';
-        or a relative value of smaller or larger; or an absolute font
-        size, e.g. 12; or 'scalable'.
+        Set the font size.  Either an relative value of 'xx-small',
+        'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'
+        or an absolute font size, e.g. 12.
         """
-        if size is None:
-            self._size = None
-        else:
-            if is_string_like(size):
-                parent_size = fontManager.get_default_size()
-                scaling = font_scalings.get(size)
-                if scaling is not None:
-                    size = parent_size * scaling
-                else:
-                    try:
-                        size = float(size)
-                    except ValueError:
-                        size = parent_size
-            assert(type(size) in (int, float))
-            self._size = size
-    get_size_in_points = get_size
+        if size is not None:
+            try:
+                size = float(size)
+            except ValueError:
+                if size is not None and size not in font_scalings:
+                    raise ValueError("size is invalid")
+        self._size = size
 
     def set_file(self, file):
         """
@@ -958,24 +953,18 @@
 class FontManager:
     """
     On import, the :class:`FontManager` singleton instance creates a
-    dictionary of TrueType fonts based on the font properties: name,
-    style, variant, weight, stretch, and size.  The :meth:`findfont`
-    method searches this dictionary for a font file name that exactly
-    matches the font properties of the specified text.  If none is
-    found, a default font is returned.  By updating the dictionary
-    with the properties of the found font, the font dictionary can act
-    like a font cache.
-
-    .. note:: The font lookup mechanism is very exact and brittle.  If
-       the *exact* font specified is not found, a default font is
-       always returned.  This should be improved in the future.
+    list of TrueType fonts based on the font properties: name, style,
+    variant, weight, stretch, and size.  The :meth:`findfont` method
+    does a nearest neighbor search to find the font that most closely
+    matches the specification.  If no good enough match is found, a
+    default font is returned.
     """
     def __init__(self, size=None, weight='normal'):
         self.__default_weight = weight
         self.default_size = size
 
-        paths = [os.path.join(rcParams['datapath'],'fonts','ttf'),
-                 os.path.join(rcParams['datapath'],'fonts','afm')]
+        paths = [os.path.join(rcParams['datapath'], 'fonts', 'ttf'),
+                 os.path.join(rcParams['datapath'], 'fonts', 'afm')]
 
         #  Create list of font paths
         for pathname in ['TTFPATH', 'AFMPATH']:
@@ -1002,7 +991,7 @@
             # use anything
             self.defaultFont = self.ttffiles[0]
 
-        self.ttfdict = createFontDict(self.ttffiles)
+        self.ttflist = createFontList(self.ttffiles)
 
         if rcParams['pdf.use14corefonts']:
             # Load only the 14 PDF core fonts. These fonts do not need to be
@@ -1013,12 +1002,15 @@
             # ZapfDingbats.
             afmpath = os.path.join(rcParams['datapath'],'fonts','pdfcorefonts')
             afmfiles = findSystemFonts(afmpath, fontext='afm')
-            self.afmdict = createFontDict(afmfiles, fontext='afm')
+            self.afmlist = createFontList(afmfiles, fontext='afm')
         else:
             self.afmfiles = findSystemFonts(paths, fontext='afm') + \
                             findSystemFonts(fontext='afm')
-            self.afmdict = createFontDict(self.afmfiles, fontext='afm')
+            self.afmlist = createFontList(self.afmfiles, fontext='afm')
 
+        self.ttf_lookup_cache = {}
+        self.afm_lookup_cache = {}
+
     def get_default_weight(self):
         """
         Return the default font weight.
@@ -1054,17 +1046,133 @@
         #  !!!!  Needs implementing
         raise NotImplementedError
 
+    # Each of the scoring functions below should return a value between
+    # 0.0 (perfect match) and 1.0 (terrible match)
+    def score_family(self, families, family2):
+        """
+        Returns a match score between the list of font families in
+        *families* and the font family name *family2*.
+
+        An exact match anywhere in the list returns 0.0.
+
+        A match by generic font name will return 0.1.
+
+        No match will return 1.0.
+        """
+        for i, family1 in enumerate(families):
+            if family1.lower() in font_family_aliases:
+                if family1 == 'sans':
+                    family1 == 'sans-serif'
+                options = rcParams['font.' + family1]
+                if family2 in options:
+                    idx = options.index(family2)
+                    return 0.1
+            elif family1.lower() == family2.lower():
+                return 0.0
+        return 1.0
+
+    def score_style(self, style1, style2):
+        """
+        Returns a match score between *style1* and *style2*.
+
+        An exact match returns 0.0.
+
+        A match between 'italic' and 'oblique' returns 0.1.
+
+        No match returns 1.0.
+        """
+        if style1 == style2:
+            return 0.0
+        elif style1 in ('italic', 'oblique') and \
+                style2 in ('italic', 'oblique'):
+            return 0.1
+        return 1.0
+
+    def score_variant(self, variant1, variant2):
+        """
+        Returns a match score between *variant1* and *variant2*.
+
+        An exact match returns 0.0, otherwise 1.0.
+        """
+        if variant1 == variant2:
+            return 0.0
+        else:
+            return 1.0
+
+    def score_stretch(self, stretch1, stretch2):
+        """
+        Returns a match score between *stretch1* and *stretch2*.
+
+        The result is the absolute value of the difference between the
+        CSS numeric values of *stretch1* and *stretch2*, normalized
+        between 0.0 and 1.0.
+        """
+        try:
+            stretchval1 = int(stretch1)
+        except ValueError:
+            stretchval1 = stretch_dict.get(stretch1, 500)
+        try:
+            stretchval2 = int(stretch2)
+        except ValueError:
+            stretchval2 = stretch_dict.get(stretch2, 500)
+        return abs(stretchval1 - stretchval2) / 1000.0
+
+    def score_weight(self, weight1, weight2):
+        """
+        Returns a match score between *weight1* and *weight2*.
+
+        The result is the absolute value of the difference between the
+        CSS numeric values of *weight1* and *weight2*, normalized
+        between 0.0 and 1.0.
+        """
+        try:
+            weightval1 = int(weight1)
+        except ValueError:
+            weightval1 = weight_dict.get(weight1, 500)
+        try:
+            weightval2 = int(weight2)
+        except ValueError:
+            weightval2 = weight_dict.get(weight2, 500)
+        return abs(weightval1 - weightval2) / 1000.0
+
+    def score_size(self, size1, size2):
+        """
+        Returns a match score between *size1* and *size2*.
+
+        If *size2* (the size specified in the font file) is 'scalable', this
+        function always returns 0.0, since any font size can be generated.
+
+        Otherwise, the result is the absolute distance between *size1* and
+        *size2*, normalized so that the usual range of font sizes (6pt -
+        72pt) will lie between 0.0 and 1.0.
+        """
+        if size2 == 'scalable':
+            return 0.0
+        # Size value should have already been
+        try:
+            sizeval1 = float(size1)
+        except ValueError:
+            sizeval1 = self.default_size * font_scalings(size1)
+        try:
+            sizeval2 = float(size2)
+        except ValueError:
+            return 1.0
+        return abs(sizeval1 - sizeval2) / 72.0
+
     def findfont(self, prop, fontext='ttf'):
         """
-        Search the font dictionary for a font that exactly or closely
-        matches the specified font properties.  See the
-        :class:`FontProperties` class for a description.
+        Search the font list for the font that most closely matches
+        the :class:`FontProperties` *prop*.
 
-        The properties are searched in the following order: name,
-        style, variant, weight, stretch, and size.  The font weight
-        always matches returning the closest weight, and the font size
-        always matches for scalable fonts.  An oblique style font will
-        be used inplace of a missing italic style font if present.
+        :meth:`findfont` performs a nearest neighbor search.  Each
+        font is given a similarity score to the target font
+        properties.  The first font with the highest score is
+        returned.  If no matches below a certain threshold are found,
+        the default font (usually Vera Sans) is returned.
+
+        The result is cached, so subsequent lookups don't have to
+        perform the O(n) nearest neighbor search.
+
         See the `W3C Cascading Style Sheet, Level 1
         <http://www.w3.org/TR/1998/REC-CSS2-19980512/>`_ documentation
         for a description of the font finding algorithm.
@@ -1078,108 +1186,45 @@
             return fname
 
         if fontext == 'afm':
-            fontdict = self.afmdict
+            font_cache = self.afm_lookup_cache
+            fontlist = self.afmlist
         else:
-            fontdict = self.ttfdict
+            font_cache = self.ttf_lookup_cache
+            fontlist = self.ttflist
 
-        original_name = prop.get_family()[0]
-        style         = prop.get_style()
-        variant       = prop.get_variant()
-        weight        = weight_as_number(prop.get_weight())
-        stretch       = prop.get_stretch()
-        size          = str(prop.get_size_in_points())
+        cached = font_cache.get(prop)
+        if cached:
+            return cached
 
-        def lookup_name(name):
-            try:
-                fname = fontdict[name][style][variant][weight][stretch][size]
-                verbose.report('\tfindfont cached %(name)s, %(style)s, 
%(variant)s, %(weight)s, %(stretch)s, %(size)s'%locals(), 'debug')
-                verbose.report('findfont returning %s'%fname, 'debug')
-                return fname
-            except KeyError:
-                pass
+        best_score = 1e64
+        best_font = None
+        for font in fontlist:
+            # Matching family should have highest priority, so it is multiplied
+            # by 10.0
+            score = \
+                self.score_family(prop.get_family(), font.name) * 10.0 + \
+                self.score_style(prop.get_style(), font.style) + \
+                self.score_variant(prop.get_variant(), font.variant) + \
+                self.score_weight(prop.get_weight(), font.weight) + \
+                self.score_stretch(prop.get_stretch(), font.stretch) + \
+                self.score_size(prop.get_size(), font.size)
+            if score < best_score:
+                best_score = score
+                best_font = font
 
-            fname = None
-            font = fontdict
-            if name in font:
-                font = font[name]
-            else:
-                verbose.report('\tfindfont failed %(name)s'%locals(), 'debug')
-                return None
+        if best_font is None or best_score > 10.0:
+            verbose.report('findfont: Could not match %s. Returning %s' %
+                           (prop, self.defaultFont))
+            result = self.defaultFont
+        else:
+            verbose.report('findfont: Matching %s to %s (%s) with score of %f' 
%
+                           (prop, best_font.name, best_font.fname, best_score))
+            result = best_font.fname
 
-            if style in font:
-                font = font[style]
-            elif style == 'italic' and 'oblique' in font:
-                font = font['oblique']
-            elif style == 'oblique' and 'italic' in font:
-                font = font['italic']
-            else:
-                verbose.report('\tfindfont failed %(name)s, 
%(style)s'%locals(), 'debug')
-                return None
+        font_cache[prop] = result
+        return result
 
-            if variant in font:
-                font = font[variant]
-            else:
-                verbose.report('\tfindfont failed %(name)s, %(style)s, 
%(variant)s'%locals(), 'debug')
-                return None
 
-            if weight not in font:
-                setWeights(font)
-            if weight not in font:
-                return None
-            font = font[weight]
-
-            if stretch in font:
-                stretch_font = font[stretch]
-                if 'scalable' in stretch_font:
-                    fname = stretch_font['scalable']
-                elif size in stretch_font:
-                    fname = stretch_font[size]
-
-            if fname is None:
-                for val in font.values():
-                    if 'scalable' in val:
-                        fname = val['scalable']
-                        break
-
-            if fname is None:
-                for val in font.values():
-                    if size in val:
-                        fname = val[size]
-                        break
-
-            if fname is None:
-                verbose.report('\tfindfont failed %(name)s, %(style)s, 
%(variant)s %(weight)s, %(stretch)s'%locals(), 'debug')
-            else:
-                fontkey = FontKey(",".join(prop.get_family()), style, variant, 
weight, stretch, size)
-                add_filename(fontdict, fontkey, fname)
-                verbose.report('\tfindfont found %(name)s, %(style)s, 
%(variant)s %(weight)s, %(stretch)s, %(size)s'%locals(), 'debug')
-                verbose.report('findfont returning %s'%fname, 'debug')
-            return fname
-
-        font_family_aliases = set(['serif', 'sans-serif', 'cursive',
-                                   'fantasy', 'monospace', 'sans'])
-
-        for name in prop.get_family():
-            if name in font_family_aliases:
-                if name == 'sans':
-                    name = 'sans-serif'
-                for name2 in rcParams['font.' + name]:
-                    fname = lookup_name(name2)
-                    if fname:
-                        break
-            else:
-                fname = lookup_name(name)
-            if fname:
-                break
-
-        if not fname:
-            fontkey = FontKey(",".join(prop.get_family()), style, variant, 
weight, stretch, size)
-            add_filename(fontdict, fontkey, self.defaultFont)
-            verbose.report('Could not match %s, %s, %s.  Returning %s' % 
(name, style, weight, self.defaultFont))
-            return self.defaultFont
-        return fname
-
-
 _is_opentype_cff_font_cache = {}
 def is_opentype_cff_font(filename):
     """
@@ -1232,7 +1277,7 @@
         return result
 
 else:
-    _fmcache = os.path.join(get_configdir(), 'fontManager.cache')
+    _fmcache = os.path.join(get_configdir(), 'fontList.cache')
 
     fontManager = None
 

Modified: trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py       2008-10-28 
12:51:39 UTC (rev 6341)
+++ trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py       2008-10-28 
18:10:51 UTC (rev 6342)
@@ -178,5 +178,4 @@
                 if val != []:
                     val = ','.join(val)
             props.append(":%s=%s" % (key, val))
-    print parse_fontconfig_pattern(''.join(props))
     return ''.join(props)

Modified: trunk/matplotlib/lib/matplotlib/text.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/text.py     2008-10-28 12:51:39 UTC (rev 
6341)
+++ trunk/matplotlib/lib/matplotlib/text.py     2008-10-28 18:10:51 UTC (rev 
6342)
@@ -445,9 +445,17 @@
         "Return the :class:`~font_manager.FontProperties` object"
         return self._fontproperties
 
+    def get_family(self):
+        "Return the list of font families used for font lookup"
+        return self._fontproperties.get_family()
+
+    def get_fontfamily(self):
+        'alias for get_family'
+        return self.get_family()
+
     def get_name(self):
         "Return the font name as string"
-        return self._fontproperties.get_family()[-1]  #  temporary hack.
+        return self._fontproperties.get_name()
 
     def get_style(self):
         "Return the font style as string"
@@ -457,26 +465,42 @@
         "Return the font size as integer"
         return self._fontproperties.get_size_in_points()
 
+    def get_variant(self):
+        "Return the font variant as a string"
+        return self._fontproperties.get_variant()
+
+    def get_fontvariant(self):
+        'alias for get_variant'
+        return self.get_variant()
+
     def get_weight(self):
-        "Get the font weight as string"
+        "Get the font weight as string or number"
         return self._fontproperties.get_weight()
 
     def get_fontname(self):
         'alias for get_name'
-        return self._fontproperties.get_family()[-1]  #  temporary hack.
+        return self.get_name()
 
     def get_fontstyle(self):
         'alias for get_style'
-        return self._fontproperties.get_style()
+        return self.get_style()
 
     def get_fontsize(self):
         'alias for get_size'
-        return self._fontproperties.get_size_in_points()
+        return self.get_size()
 
     def get_fontweight(self):
         'alias for get_weight'
-        return self._fontproperties.get_weight()
+        return self.get_weight()
 
+    def get_stretch(self):
+        'Get the font stretch as a string or number'
+        return self._fontproperties.get_stretch()
+
+    def get_fontstretch(self):
+        'alias for get_stretch'
+        return self.get_stretch()
+
     def get_ha(self):
         'alias for get_horizontalalignment'
         return self.get_horizontalalignment()
@@ -645,35 +669,35 @@
 
     def set_family(self, fontname):
         """
-        Set the font family
+        Set the font family.  May be either a single string, or a list
+        of strings in decreasing priority.  Each string may be either
+        a real font name or a generic font class name.  If the latter,
+        the specific font names will be looked up in the
+        :file:`matplotlibrc` file.
 
-        ACCEPTS: [ 'serif' | 'sans-serif' | 'cursive' | 'fantasy' | 
'monospace' ]
+        ACCEPTS: [ FONTNAME | 'serif' | 'sans-serif' | 'cursive' | 'fantasy' | 
'monospace' ]
         """
         self._fontproperties.set_family(fontname)
 
     def set_variant(self, variant):
         """
-        Set the font variant, eg,
+        Set the font variant, either 'normal' or 'small-caps'.
 
         ACCEPTS: [ 'normal' | 'small-caps' ]
         """
         self._fontproperties.set_variant(variant)
 
     def set_name(self, fontname):
-        """
-        Set the font name,
+        """alias for set_family"""
+        return self.set_family(fontname)
 
-        ACCEPTS: string eg, ['Sans' | 'Courier' | 'Helvetica' ...]
-        """
-        self._fontproperties.set_family(fontname)
-
     def set_fontname(self, fontname):
-        'alias for set_name'
+        """alias for set_family"""
         self.set_family(fontname)
 
     def set_style(self, fontstyle):
         """
-        Set the font style
+        Set the font style.
 
         ACCEPTS: [ 'normal' | 'italic' | 'oblique']
         """
@@ -681,33 +705,45 @@
 
     def set_fontstyle(self, fontstyle):
         'alias for set_style'
-        self._fontproperties.set_style(fontstyle)
+        return self.set_style(fontstyle)
 
     def set_size(self, fontsize):
         """
-        Set the font size, eg., 8, 10, 12, 14...
+        Set the font size.  May be either a size string, relative to
+        the default font size, or an absolute font size in points.
 
-        ACCEPTS: [ size in points | relative size eg 'smaller', 'x-large' ]
+        ACCEPTS: [ size in points | 'xx-small' | 'x-small' | 'small' | 
'medium' | 'large' | 'x-large' | 'xx-large' ]
         """
         self._fontproperties.set_size(fontsize)
 
     def set_fontsize(self, fontsize):
         'alias for set_size'
-        self._fontproperties.set_size(fontsize)
+        return self.set_size(fontsize)
 
+    def set_weight(self, weight):
+        """
+        Set the font weight.
+
+        ACCEPTS: [ a numeric value in range 0-1000 | 'ultralight' | 'light' | 
'normal' | 'regular' | 'book' | 'medium' | 'roman' | 'semibold' | 'demibold' | 
'demi' | 'bold' | 'heavy' | 'extra bold' | 'black' ]
+        """
+        self._fontproperties.set_weight(weight)
+
     def set_fontweight(self, weight):
         'alias for set_weight'
-        self._fontproperties.set_weight(weight)
+        return self.set_weight(weight)
 
-    def set_weight(self, weight):
+    def set_stretch(self, stretch):
         """
-        Set the font weight
+        Set the font stretch (horizontal condensation or expansion).
 
-        ACCEPTS: [ 'normal' | 'bold' | 'heavy' | 'light' | 'ultrabold' |
-        'ultralight']
+        ACCEPTS: [ a numeric value in range 0-1000 | 'ultra-condensed' | 
'extra-condensed' | 'condensed' | 'semi-condensed' | 'normal' | 'semi-expanded' 
| 'expanded' | 'extra-expanded' | 'ultra-expanded' ]
         """
-        self._fontproperties.set_weight(weight)
+        self._fontproperties.set_stretch(stretch)
 
+    def set_fontstretch(self, stretch):
+        'alias for set_stretch'
+        return self.set_stretch(stretch)
+
     def set_position(self, xy):
         """
         Set the (*x*, *y*) position of the text


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to