From: <md...@us...> - 2007-08-27 15:42:50
|
Revision: 3738 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3738&view=rev Author: mdboom Date: 2007-08-27 08:42:44 -0700 (Mon, 27 Aug 2007) Log Message: ----------- mathtext.* font rcParams now use fontconfig patterns to specify fonts. Support converting FontProperties objects to/from fontconfig patterns. Modified Paths: -------------- trunk/matplotlib/lib/matplotlib/config/mplconfig.py trunk/matplotlib/lib/matplotlib/config/mpltraits.py trunk/matplotlib/lib/matplotlib/config/rcsetup.py trunk/matplotlib/lib/matplotlib/font_manager.py trunk/matplotlib/lib/matplotlib/rcsetup.py trunk/matplotlib/lib/matplotlib/text.py Added Paths: ----------- trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py Modified: trunk/matplotlib/lib/matplotlib/config/mplconfig.py =================================================================== --- trunk/matplotlib/lib/matplotlib/config/mplconfig.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/config/mplconfig.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -161,14 +161,12 @@ dvipnghack = T.false class mathtext(TConfig): - handler = mplT.FontPropertiesHandler - proxy = handler.FontPropertiesProxy - cal = T.Trait(proxy(['cursive']), handler()) - rm = T.Trait(proxy(['serif']), handler()) - tt = T.Trait(proxy(['monospace']), handler()) - it = T.Trait(proxy(['serif'], style='oblique'), handler()) - bf = T.Trait(proxy(['serif'], weight='bold'), handler()) - sf = T.Trait(proxy(['sans-serif']), handler()) + cal = T.Trait('cursive' , mplT.FontconfigPatternHandler()) + rm = T.Trait('serif' , mplT.FontconfigPatternHandler()) + tt = T.Trait('monospace' , mplT.FontconfigPatternHandler()) + it = T.Trait('serif:oblique' , mplT.FontconfigPatternHandler()) + bf = T.Trait('serif:bold' , mplT.FontconfigPatternHandler()) + sf = T.Trait('sans' , mplT.FontconfigPatternHandler()) use_cm = T.true fallback_to_cm = T.true Modified: trunk/matplotlib/lib/matplotlib/config/mpltraits.py =================================================================== --- trunk/matplotlib/lib/matplotlib/config/mpltraits.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/config/mpltraits.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -4,6 +4,9 @@ # Matplotlib-specific imports import cutils +# For the fontconfig pattern parser +from matplotlib.fontconfig_pattern import parse_fontconfig_pattern + # stolen from cbook def is_string_like(obj): if hasattr(obj, 'shape'): return 0 # this is a workaround @@ -145,64 +148,14 @@ 'prism', 'prism_r', 'spectral', 'spectral_r', 'spring', 'spring_r', 'summer', 'summer_r', 'winter', 'winter_r'] -class FontPropertiesHandler(T.TraitHandler): - class FontPropertiesProxy(object): - # In order to build a FontProperties object, various rcParams must - # already be known in order to set default values. That means a - # FontProperties object can not be created from a config file, - # since it depends on other values in the same config file. This - # proxy class is used as a temporary storage area for the settings - # in the config file, and the full FontProperties class is created - # only when the class is first used. It is defined here rather than - # in font_manager.py to avoid a cyclical import. - def __init__(self, - family = None, - style = None, - variant= None, - weight = None, - stretch= None, - size = None, - fname = None, # if this is set, it's a hardcoded filename to use - ): - self.__family = family - self.__style = style - self.__variant = variant - self.__weight = weight - self.__stretch = stretch - self.__size = size - self.__fname = fname - - self.__child = None - - def __get_child(self): - if self.__child is None: - from matplotlib.font_manager import FontProperties - self.__child = FontProperties( - family = self.__family, - style = self.__style, - variant = self.__variant, - weight = self.__weight, - stretch = self.__stretch, - size = self.__size, - fname = self.__fname) - return self.__child - - def __getattr__(self, attr): - return getattr(self.__get_child(), attr) - +class FontconfigPatternHandler(T.TraitHandler): + """This validates a fontconfig pattern by using the FontconfigPatternParser. + The results are not actually used here, since we can't instantiate a + FontProperties object at config-parse time.""" def validate(self, object, name, value): - from matplotlib.font_manager import FontProperties - if is_string_like(value): - try: - proxy = eval("FontProperties(%s)" % value, - {}, {'FontProperties': self.FontPropertiesProxy}) - except: - pass - else: - return proxy - else: - return value - self.error(object, name, value) - + # This will throw a ValueError if value does not parse correctly + parse_fontconfig_pattern(value) + return value + def info(self): - return 'a FontProperties object or a string containing the parameters to the FontProperties constructor.' + return """A fontconfig pattern. See the fontconfig user manual for more information.""" Modified: trunk/matplotlib/lib/matplotlib/config/rcsetup.py =================================================================== --- trunk/matplotlib/lib/matplotlib/config/rcsetup.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/config/rcsetup.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -9,6 +9,7 @@ """ import os +from matplotlib.fontconfig_pattern import parse_fontconfig_pattern class ValidateInStrings: def __init__(self, key, valid, ignorecase=False): @@ -197,64 +198,10 @@ return float(s) except ValueError: raise ValueError('not a valid font size') - -class FontPropertiesProxy: - # In order to build a FontProperties object, various rcParams must - # already be known in order to set default values. That means a - # FontProperties object can not be created from a config file, - # since it depends on other values in the same config file. This - # proxy class is used as a temporary storage area for the settings - # in the config file, and the full FontProperties class is created - # only when the class is first used. It is defined here rather than - # in font_manager.py to avoid a cyclical import. - def __init__(self, - family = None, - style = None, - variant= None, - weight = None, - stretch= None, - size = None, - fname = None, # if this is set, it's a hardcoded filename to use - ): - self.__family = family - self.__style = style - self.__variant = variant - self.__weight = weight - self.__stretch = stretch - self.__size = size - self.__fname = fname - - self.__child = None - - def __get_child(self): - if self.__child is None: - from font_manager import FontProperties - self.__child = FontProperties( - family = self.__family, - style = self.__style, - variant = self.__variant, - weight = self.__weight, - stretch = self.__stretch, - size = self.__size, - fname = self.__fname) - return self.__child - - def __getattr__(self, attr): - return getattr(self.__get_child(), attr) def validate_font_properties(s): - parsed = False - try: - prop = eval(u'FontProperties(%s)' % s, - {}, {'FontProperties': FontPropertiesProxy}) - except: - pass - else: - parsed = isinstance(prop, FontPropertiesProxy) - if not parsed: - raise ValueError( - 'Mathtext font specifier must be a set of arguments to the FontProperty constructor.') - return prop + parse_fontconfig_pattern(s) + return s validate_markup = ValidateInStrings( 'markup', @@ -418,12 +365,12 @@ 'text.fontsize' : ['medium', validate_fontsize], 'text.markup' : ['plain', validate_markup], - 'mathtext.cal' : [FontPropertiesProxy(['cursive']), validate_font_properties], - 'mathtext.rm' : [FontPropertiesProxy(['serif']), validate_font_properties], - 'mathtext.tt' : [FontPropertiesProxy(['monospace']), validate_font_properties], - 'mathtext.it' : [FontPropertiesProxy(['serif'], style='oblique'), validate_font_properties], - 'mathtext.bf' : [FontPropertiesProxy(['serif'], weight='bold'), validate_font_properties], - 'mathtext.sf' : [FontPropertiesProxy(['sans-serif']), validate_font_properties], + 'mathtext.cal' : ['cursive', validate_font_properties], + 'mathtext.rm' : ['serif', validate_font_properties], + 'mathtext.tt' : ['monospace', validate_font_properties], + 'mathtext.it' : ['serif:italic', validate_font_properties], + 'mathtext.bf' : ['serif:bold', validate_font_properties], + 'mathtext.sf' : ['sans\-serif', validate_font_properties], 'mathtext.use_cm' : [True, validate_bool], 'mathtext.fallback_to_cm' : [True, validate_bool], Modified: trunk/matplotlib/lib/matplotlib/font_manager.py =================================================================== --- trunk/matplotlib/lib/matplotlib/font_manager.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/font_manager.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -34,10 +34,14 @@ """ import os, sys, glob, shutil +from sets import Set import matplotlib from matplotlib import afm from matplotlib import ft2font from matplotlib import rcParams, get_home, get_configdir +from matplotlib.cbook import is_string_like +from matplotlib.fontconfig_pattern import \ + parse_fontconfig_pattern, generate_fontconfig_pattern try: import cPickle as pickle @@ -48,7 +52,7 @@ 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} + 'xx-large': 1.728, 'larger': 1.2, 'smaller': 0.833} weight_dict = {'light': 200, 'normal': 400, 'regular': 400, 'book': 400, 'medium': 500, 'roman': 500, 'semibold': 600, 'demibold': 600, @@ -552,8 +556,7 @@ for j in [600, 700, 800, 900]: font[j] = temp[700] - -class FontProperties: +class FontProperties(object): """ A class for storing and manipulating font properties. @@ -582,32 +585,37 @@ The default font property for TrueType fonts is: sans-serif, normal, normal, normal, 400, scalable. - The preferred usage of font sizes is to use the absolute values, e.g. + The preferred usage of font sizes is to use the relative values, e.g. large, instead of absolute font sizes, e.g. 12. This approach allows all text sizes to be made larger or smaller based on the font manager's default font size, i.e. by using the set_default_size() method of the font manager. - Examples: + This class will also accept a fontconfig pattern, if it is the only + argument provided. fontconfig patterns are described here: - # Load default font properties - >>> p = FontProperties() - >>> p.get_family() - ['Bitstream Vera Sans', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucida', 'Arial', 'Helvetica', 'sans-serif'] + http://www.fontconfig.org/fontconfig-user.html - # Change font family to 'fantasy' - >>> p.set_family('fantasy') - >>> p.get_family() - ['Comic Sans MS', 'Chicago', 'Charcoal', 'Impact', 'Western', 'fantasy'] - - # Make these fonts highest priority in font family - >>> p.set_name(['foo', 'fantasy', 'bar', 'baz']) - Font name 'fantasy' is a font family. It is being deleted from the list. - >>> p.get_family() - ['foo', 'bar', 'baz', 'Comic Sans MS', 'Chicago', 'Charcoal', 'Impact', 'Western', 'fantasy'] - + Note that matplotlib's internal font manager and fontconfig use a + different algorithm to lookup fonts, so the results of the same pattern + may be different in matplotlib than in other applications that use + fontconfig. """ + class FontPropertiesSet(object): + """This class contains all of the default properties at the + class level, which are then overridden (only if provided) at + the instance level.""" + family = rcParams['font.' + rcParams['font.family']] + if is_string_like(family): + family = [family] + slant = rcParams['font.style'] + variant = rcParams['font.variant'] + weight = rcParams['font.weight'] + stretch = rcParams['font.stretch'] + size = [rcParams['font.size']] + file = None + def __init__(self, family = None, style = None, @@ -615,42 +623,51 @@ weight = None, stretch= None, size = None, - fname = None, # if this is set, it's a hardcoded filename to use + fname = None, # if this is set, it's a hardcoded filename to use + _init = None # used only by copy() ): + self.__props = self.FontPropertiesSet() - if family is None: family = rcParams['font.'+rcParams['font.family']] - if style is None: style = rcParams['font.style'] - if variant is None: variant= rcParams['font.variant'] - if weight is None: weight = rcParams['font.weight'] - if stretch is None: stretch= rcParams['font.stretch'] - if size is None: size = rcParams['font.size'] - + # This is used only by copy() + if _init is not None: + self.__props.__dict__.update(_init) + return + if isinstance(family, str): + # Treat family as a fontconfig pattern if it is the only + # parameter provided. + if (style is None and + variant is None and + weight is None and + stretch is None and + size is None and + fname is None): + self.__props.__dict__ = self._parse_fontconfig_pattern(family) + return family = [family] - self.__family = family - self.__style = style - self.__variant = variant - self.__weight = weight - self.__stretch = stretch - self.__size = size - self.__parent_size = fontManager.get_default_size() - self.fname = fname + self.set_family(family) + self.set_style(style) + self.set_variant(variant) + self.set_weight(weight) + self.set_stretch(stretch) + self.set_file(fname) + self.set_size(size) + + def _parse_fontconfig_pattern(self, pattern): + return parse_fontconfig_pattern(pattern) + def __hash__(self): - return hash( ( - tuple(self.__family), self.__style, self.__variant, - self.__weight, self.__stretch, self.__size, - self.__parent_size, self.fname)) + return hash(repr(self.__props)) def __str__(self): - return str((self.__family, self.__style, self.__variant, - self.__weight, self.__stretch, self.__size)) - + return self.get_fontconfig_pattern() + def get_family(self): """Return a list of font names that comprise the font family. """ - return self.__family + return self.__props.family def get_name(self): """Return the name of the font that best matches the font properties.""" @@ -658,120 +675,115 @@ def get_style(self): """Return the font style. Values are: normal, italic or oblique.""" - return self.__style + return self.__props.slant def get_variant(self): """Return the font variant. Values are: normal or small-caps.""" - return self.__variant + return self.__props.variant def get_weight(self): """ Return the font weight. See the FontProperties class for a a list of possible values. """ - return self.__weight + return self.__props.weight def get_stretch(self): """ Return the font stretch or width. Options are: normal, narrow, condensed, or wide. """ - return self.__stretch + return self.__props.stretch def get_size(self): """Return the font size.""" - return self.__size + return float(self.__props.size[0]) + def get_file(self): + return self.__props.file + + def get_fontconfig_pattern(self): + return generate_fontconfig_pattern(self.__props.__dict__) + def set_family(self, family): """ - Change the font family. Options are: serif, sans-serif, cursive, - fantasy, or monospace. + Change the font family. May be either an alias (generic name + is CSS parlance), such as: serif, sans-serif, cursive, + fantasy, or monospace, or a real font name. """ - try: - self.__family = rcParams['font.'+family] - if isinstance(self.__family, str): - self.__family = [self.__family] - except KeyError: - raise KeyError, '%s - use serif, sans-serif, cursive, fantasy, or monospace.' % family - - def set_name(self, names): - """ - Add one or more font names to the font family list. If the - font name is already in the list, then the font is given a higher - priority in the font family list. To change the font family, use the - set_family() method. - """ - - msg = "Font name '%s' is a font family. It is being deleted from the list." - font_family = ['serif', 'sans-serif', 'cursive', 'fantasy', - 'monospace'] - - if isinstance(names, str): - names = [names] - - # Remove family names from list of font names. - for name in names[:]: - if name.lower() in font_family: - verbose.report( msg % name) - while name in names: - names.remove(name.lower()) - - # Remove font names from family list. - for name in names: - while name in self.__family: - self.__family.remove(name) - - self.__family = names + self.__family - + if family is None: + self.__props.__dict__.pop('family', None) + else: + self.__props.family = family + def set_style(self, style): """Set the font style. Values are: normal, italic or oblique.""" - self.__style = style + if style is None: + self.__props.__dict__.pop('style', None) + else: + if style not in ('normal', 'italic', 'oblique'): + raise ValueError("style must be normal, italic or oblique") + self.__props.slant = style def set_variant(self, variant): """Set the font variant. Values are: normal or small-caps.""" - self.__variant = variant + if variant is None: + self.__props.__dict__.pop('variant', None) + else: + if variant not in ('normal', 'small-caps'): + raise ValueError("variant must be normal or small-caps") + self.__props.variant = variant def set_weight(self, weight): """ Set the font weight. See the FontProperties class for a a list of possible values. """ - self.__weight = weight + if weight is None: + self.__props.__dict__.pop('weight', None) + else: + if (weight not in weight_dict and + weight not in weight_dict.keys()): + raise ValueError("weight is invalid") + self.__props.weight = weight def set_stretch(self, stretch): """ Set the font stretch or width. Options are: normal, narrow, condensed, or wide. """ - self.__stretch = stretch + if stretch is None: + self.__props.__dict__.pop('stretch', None) + else: + self.__props.stretch = stretch def set_size(self, size): """Set the font size.""" - self.__size = size - - def get_size_in_points(self, parent_size=None): - """ - Return the size property as a numeric value. String values - are converted to their corresponding numeric value. - """ - if self.__size in font_scalings.keys(): - size = fontManager.get_default_size()*font_scalings[self.__size] - elif self.__size == 'larger': - size = self.__parent_size*1.2 - elif self.__size == 'smaller': - size = self.__parent_size/1.2 + if size is None: + self.__props.__dict__.pop('size', None) else: - size = self.__size - return float(size) + 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: + size = parent_size + if isinstance(size, (int, float)): + size = [size] + self.__props.size = size + def set_file(self, file): + self.__props.file = file + + get_size_in_points = get_size + + def set_fontconfig_pattern(self, pattern): + self.__props.__dict__ = self._parse_fontconfig_pattern(pattern) + def copy(self): """Return a deep copy of self""" - return FontProperties(self.__family, - self.__style, - self.__variant, - self.__weight, - self.__stretch, - self.__size) + return FontProperties(_init = self.__props.__dict__) def ttfdict_to_fnames(d): 'flatten a ttfdict to all the filenames it contains' @@ -905,8 +917,10 @@ documentation for a description of the font finding algorithm. """ debug = False - if prop.fname is not None: - fname = prop.fname + if is_string_like(prop): + prop = FontProperties(prop) + fname = prop.get_file() + if fname is not None: verbose.report('findfont returning %s'%fname, 'debug') return fname @@ -957,6 +971,8 @@ if not font.has_key(weight): setWeights(font) + if not font.has_key(weight): + return None font = font[weight] if font.has_key(stretch): @@ -981,16 +997,19 @@ if fname is None: verbose.report('\tfindfont failed %(name)s, %(style)s, %(variant)s %(weight)s, %(stretch)s'%locals(), 'debug') else: - fontkey = FontKey(original_name, style, variant, weight, stretch, size) + 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 = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] + 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: @@ -1001,9 +1020,9 @@ break if not fname: - fontkey = FontKey(original_name, style, variant, weight, stretch, size) + 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, variant, self.defaultFont)) + verbose.report('Could not match %s, %s, %s. Returning %s' % (name, style, weight, self.defaultFont)) return self.defaultFont return fname Added: trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py =================================================================== --- trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/fontconfig_pattern.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -0,0 +1,169 @@ +""" +A module for parsing a fontconfig pattern. + +This class is defined here because it must be available in: + - The old-style config framework (rcsetup.py) + - The traits-based config framework (mpltraits.py) + - The font manager (font_manager.py) + +It probably logically belongs in font_manager.py, but +placing it in any of these places would have created cyclical +dependency problems, or an undesired dependency on traits even +when the traits-based config framework is not used. + +See here for a rough specification of these patterns: +http://www.fontconfig.org/fontconfig-user.html + +Author : Michael Droettboom <md...@st...> +License : matplotlib license (PSF compatible) +""" +import re +from matplotlib.pyparsing import Literal, OneOrMore, ZeroOrMore, \ + Optional, Regex, StringEnd, ParseException + +family_punc = r'\\\-:,' +family_unescape = re.compile(r'\\([%s])' % family_punc).sub +family_escape = re.compile(r'([%s])' % family_punc).sub + +value_punc = r'\\=_:,' +value_unescape = re.compile(r'\\([%s])' % value_punc).sub +value_escape = re.compile(r'([%s])' % value_punc).sub + +class FontconfigPatternParser: + """A simple pyparsing-based parser for fontconfig-style patterns. + + See here for a rough specification of these patterns: + http://www.fontconfig.org/fontconfig-user.html + """ + + + _constants = { + 'thin' : ('weight', 'light'), + 'extralight' : ('weight', 'light'), + 'ultralight' : ('weight', 'light'), + 'light' : ('weight', 'light'), + 'book' : ('weight', 'book'), + 'regular' : ('weight', 'regular'), + 'normal' : ('weight', 'normal'), + 'medium' : ('weight', 'medium'), + 'demibold' : ('weight', 'demibold'), + 'semibold' : ('weight', 'semibold'), + 'bold' : ('weight', 'bold'), + 'extrabold' : ('weight', 'extra bold'), + 'black' : ('weight', 'black'), + 'heavy' : ('weight', 'heavy'), + 'roman' : ('slant', 'normal'), + 'italic' : ('slant', 'italic'), + 'oblique' : ('slant', 'oblique'), + 'ultracondensed' : ('width', 'ultra-condensed'), + 'extracondensed' : ('width', 'extra-condensed'), + 'condensed' : ('width', 'condensed'), + 'semicondensed' : ('width', 'semi-condensed'), + 'expanded' : ('width', 'expanded'), + 'extraexpanded' : ('width', 'extra-expanded'), + 'ultraexpanded' : ('width', 'ultra-expanded') + } + + def __init__(self): + family = Regex(r'([^%s]|(\\[%s]))*' % + (family_punc, family_punc)) \ + .setParseAction(self._family) + size = Regex(r'[0-9.]+') \ + .setParseAction(self._size) + name = Regex(r'[a-z]+') \ + .setParseAction(self._name) + value = Regex(r'([^%s]|(\\[%s]))*' % + (value_punc, value_punc)) \ + .setParseAction(self._value) + + families =(family + + ZeroOrMore( + Literal(',') + + family) + ).setParseAction(self._families) + + point_sizes =(size + + ZeroOrMore( + Literal(',') + + size) + ).setParseAction(self._point_sizes) + + property =( (name + + Literal('=') + + value) + | name + ).setParseAction(self._property) + + pattern =(Optional( + families) + + Optional( + Literal('-') + + point_sizes) + + ZeroOrMore( + Literal(':') + + property) + + StringEnd() + ) + + self._parser = pattern + self.ParseException = ParseException + + def parse(self, pattern): + props = self._properties = {} + try: + self._parser.parseString(pattern) + except self.ParseException, e: + raise ValueError("Could not parse font string: '%s'\n%s" % (pattern, e)) + + self._properties = None + return props + + def _family(self, s, loc, tokens): + return [family_unescape(r'\1', tokens[0])] + + def _size(self, s, loc, tokens): + return [float(tokens[0])] + + def _name(self, s, loc, tokens): + return [tokens[0]] + + def _value(self, s, loc, tokens): + return [value_unescape(r'\1', tokens[0])] + + def _families(self, s, loc, tokens): + self._properties['family'] = tokens + return [] + + def _point_sizes(self, s, loc, tokens): + self._properties['size'] = tokens + return [] + + def _property(self, s, loc, tokens): + if len(tokens) == 1: + if tokens[0] in self._constants: + key, val = self._constants[tokens[0]] + elif len(tokens) == 3: + key, op, val = tokens + self._properties[key] = val + return [] + +parse_fontconfig_pattern = FontconfigPatternParser().parse + +def generate_fontconfig_pattern(d): + """Given a dictionary of key/value pairs, generates a fontconfig pattern + string.""" + props = [] + families = '' + size = '' + for key, val in d.items(): + if key == 'family': + families = [family_escape(r'\\\1', name) for name in val] + families = ','.join(families) + elif key == 'size': + size = '-' + ','.join([str(x) for x in val]) + elif val is not None: + val = value_escape(r'\\\1', str(val)) + props.append(":%s=%s" % (key, val)) + props = ''.join(props) + + return ''.join([families, size, props]) Modified: trunk/matplotlib/lib/matplotlib/rcsetup.py =================================================================== --- trunk/matplotlib/lib/matplotlib/rcsetup.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/rcsetup.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -9,6 +9,7 @@ """ import os +from matplotlib.fontconfig_pattern import parse_fontconfig_pattern class ValidateInStrings: def __init__(self, key, valid, ignorecase=False): @@ -197,64 +198,10 @@ return float(s) except ValueError: raise ValueError('not a valid font size') - -class FontPropertiesProxy(object): - # In order to build a FontProperties object, various rcParams must - # already be known in order to set default values. That means a - # FontProperties object can not be created from a config file, - # since it depends on other values in the same config file. This - # proxy class is used as a temporary storage area for the settings - # in the config file, and the full FontProperties class is created - # only when the class is first used. It is defined here rather than - # in font_manager.py to avoid a cyclical import. - def __init__(self, - family = None, - style = None, - variant= None, - weight = None, - stretch= None, - size = None, - fname = None, # if this is set, it's a hardcoded filename to use - ): - self.__family = family - self.__style = style - self.__variant = variant - self.__weight = weight - self.__stretch = stretch - self.__size = size - self.__fname = fname - - self.__child = None - - def __get_child(self): - if self.__child is None: - from font_manager import FontProperties - self.__child = FontProperties( - family = self.__family, - style = self.__style, - variant = self.__variant, - weight = self.__weight, - stretch = self.__stretch, - size = self.__size, - fname = self.__fname) - return self.__child - - def __getattr__(self, attr): - return getattr(self.__get_child(), attr) def validate_font_properties(s): - parsed = False - try: - prop = eval(u'FontProperties(%s)' % s, - {}, {'FontProperties': FontPropertiesProxy}) - except: - pass - else: - parsed = isinstance(prop, FontPropertiesProxy) - if not parsed: - raise ValueError( - 'Mathtext font specifier must be a set of arguments to the FontProperty constructor.') - return prop + parse_fontconfig_pattern(s) + return s validate_markup = ValidateInStrings( 'markup', @@ -418,12 +365,12 @@ 'text.fontsize' : ['medium', validate_fontsize], 'text.markup' : ['plain', validate_markup], - 'mathtext.cal' : [FontPropertiesProxy(['cursive']), validate_font_properties], - 'mathtext.rm' : [FontPropertiesProxy(['serif']), validate_font_properties], - 'mathtext.tt' : [FontPropertiesProxy(['monospace']), validate_font_properties], - 'mathtext.it' : [FontPropertiesProxy(['serif'], style='oblique'), validate_font_properties], - 'mathtext.bf' : [FontPropertiesProxy(['serif'], weight='bold'), validate_font_properties], - 'mathtext.sf' : [FontPropertiesProxy(['sans-serif']), validate_font_properties], + 'mathtext.cal' : ['cursive', validate_font_properties], + 'mathtext.rm' : ['serif', validate_font_properties], + 'mathtext.tt' : ['monospace', validate_font_properties], + 'mathtext.it' : ['serif:italic', validate_font_properties], + 'mathtext.bf' : ['serif:bold', validate_font_properties], + 'mathtext.sf' : ['sans\-serif', validate_font_properties], 'mathtext.use_cm' : [True, validate_bool], 'mathtext.fallback_to_cm' : [True, validate_bool], Modified: trunk/matplotlib/lib/matplotlib/text.py =================================================================== --- trunk/matplotlib/lib/matplotlib/text.py 2007-08-26 06:56:07 UTC (rev 3737) +++ trunk/matplotlib/lib/matplotlib/text.py 2007-08-27 15:42:44 UTC (rev 3738) @@ -162,6 +162,7 @@ if color is None: color = rcParams['text.color'] if fontproperties is None: fontproperties=FontProperties() + elif is_string_like(fontproperties): fontproperties=FontProperties(fontproperties) self.set_text(text) self.set_color(color) @@ -649,11 +650,11 @@ ACCEPTS: string eg, ['Sans' | 'Courier' | 'Helvetica' ...] """ - self._fontproperties.set_name(fontname) + self._fontproperties.set_family(fontname) def set_fontname(self, fontname): 'alias for set_name' - self.set_name(fontname) + self.set_family(fontname) def set_style(self, fontstyle): """ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |