4242from threading import Timer
4343import warnings
4444
45+ import matplotlib as mpl
4546from matplotlib import afm , cbook , ft2font , rcParams , get_cachedir
4647from matplotlib .fontconfig_pattern import (
4748 parse_fontconfig_pattern , generate_fontconfig_pattern )
@@ -831,15 +832,23 @@ def copy(self):
831832class JSONEncoder (json .JSONEncoder ):
832833 def default (self , o ):
833834 if isinstance (o , FontManager ):
834- return dict (o .__dict__ , _class = 'FontManager' )
835+ return dict (o .__dict__ , __class__ = 'FontManager' )
835836 elif isinstance (o , FontEntry ):
836- return dict (o .__dict__ , _class = 'FontEntry' )
837+ d = dict (o .__dict__ , __class__ = 'FontEntry' )
838+ try :
839+ # Cache paths of fonts shipped with mpl relative to the mpl
840+ # data path, which helps in the presence of venvs.
841+ d ["fname" ] = str (
842+ Path (d ["fname" ]).relative_to (mpl .get_data_path ()))
843+ except ValueError :
844+ pass
845+ return d
837846 else :
838847 return super ().default (o )
839848
840849
841850def _json_decode (o ):
842- cls = o .pop ('_class ' , None )
851+ cls = o .pop ('__class__ ' , None )
843852 if cls is None :
844853 return o
845854 elif cls == 'FontManager' :
@@ -849,26 +858,37 @@ def _json_decode(o):
849858 elif cls == 'FontEntry' :
850859 r = FontEntry .__new__ (FontEntry )
851860 r .__dict__ .update (o )
861+ if not os .path .isabs (r .fname ):
862+ r .fname = os .path .join (mpl .get_data_path (), r .fname )
852863 return r
853864 else :
854- raise ValueError ("don't know how to deserialize _class =%s" % cls )
865+ raise ValueError ("don't know how to deserialize __class__ =%s" % cls )
855866
856867
857868def json_dump (data , filename ):
858- """Dumps a data structure as JSON in the named file.
859- Handles FontManager and its fields."""
869+ """
870+ Dumps a data structure as JSON in the named file.
860871
872+ Handles FontManager and its fields. File paths that are children of the
873+ Matplotlib data path (typically, fonts shipped with Matplotlib) are stored
874+ relative to that data path (to remain valid across virtualenvs).
875+ """
861876 with open (filename , 'w' ) as fh :
862877 try :
863878 json .dump (data , fh , cls = JSONEncoder , indent = 2 )
864879 except OSError as e :
865880 warnings .warn ('Could not save font_manager cache {}' .format (e ))
866881
867882
883+
868884def json_load (filename ):
869- """Loads a data structure as JSON from the named file.
870- Handles FontManager and its fields."""
885+ """
886+ Loads a data structure as JSON from the named file.
871887
888+ Handles FontManager and its fields. Relative file paths are interpreted
889+ as being relative to the Matplotlib data path, and transformed into
890+ absolute paths.
891+ """
872892 with open (filename , 'r' ) as fh :
873893 return json .load (fh , object_hook = _json_decode )
874894
@@ -954,30 +974,32 @@ def __init__(self, size=None, weight='normal'):
954974 _log .info ('font search path %s' , str (paths ))
955975 # Load TrueType fonts and create font dictionary.
956976
957- self .ttffiles = findSystemFonts (paths ) + findSystemFonts ()
958977 self .defaultFamily = {
959978 'ttf' : 'DejaVu Sans' ,
960979 'afm' : 'Helvetica' }
961980 self .defaultFont = {}
962981
963- for fname in self .ttffiles :
964- _log .debug ('trying fontname %s' , fname )
965- if fname .lower ().find ('DejaVuSans.ttf' )>= 0 :
966- self .defaultFont ['ttf' ] = fname
967- break
968- else :
969- # use anything
970- self .defaultFont ['ttf' ] = self .ttffiles [0 ]
971-
972- self .ttflist = createFontList (self .ttffiles )
973-
974- self .afmfiles = (findSystemFonts (paths , fontext = 'afm' )
975- + findSystemFonts (fontext = 'afm' ))
976- self .afmlist = createFontList (self .afmfiles , fontext = 'afm' )
977- if len (self .afmfiles ):
978- self .defaultFont ['afm' ] = self .afmfiles [0 ]
979- else :
980- self .defaultFont ['afm' ] = None
982+ ttffiles = findSystemFonts (paths ) + findSystemFonts ()
983+ self .defaultFont ['ttf' ] = next (
984+ (fname for fname in ttffiles
985+ if fname .lower ().endswith ("dejavusans.ttf" )),
986+ ttffiles [0 ])
987+ self .ttflist = createFontList (ttffiles )
988+
989+ afmfiles = (findSystemFonts (paths , fontext = 'afm' )
990+ + findSystemFonts (fontext = 'afm' ))
991+ self .afmlist = createFontList (afmfiles , fontext = 'afm' )
992+ self .defaultFont ['afm' ] = afmfiles [0 ] if afmfiles else None
993+
994+ @property
995+ @cbook .deprecated ("3.0" )
996+ def ttffiles (self ):
997+ return [font .fname for font in self .ttflist ]
998+
999+ @property
1000+ @cbook .deprecated ("3.0" )
1001+ def afmfiles (self ):
1002+ return [font .fname for font in self .afmlist ]
9811003
9821004 def get_default_weight (self ):
9831005 """
0 commit comments